linux设备驱动--字符设备模型

来源:互联网 发布:编程器 烧录器 编辑:程序博客网 时间:2024/05/17 09:38

linux设备驱动--字符设备模型

最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步。

作者:liufei_learning(转载请注明出处)

email:flying0216@foxmail.com 

IT学习交流群:160855096

 

开发环境:Win7(主机)+ VisualBox + ubuntu10.10(虚拟机) + TQ2440开发板(2.6.30.4内核)

功能:     1.linux设备驱动--LED驱动 用cdev实现led驱动的编写

           2.学习字符设备模型(转载)

目录:     1.cdev实现led驱动的编写

                              1)实现及源码

                     2cdev结构分析

             2.学习字符设备模型

                    1)字符设备模型

                    2)字符设备的设备号

                   3)文件系统中对字符设备文件的访问


cdev实现led驱动的编写

在上一节代码基础上修改的,详细看linux设备驱动--LED驱动

 

[cpp] view plaincopyprint?
  1. /* 
  2.  * tq2440_leds.c 
  3.  * 
  4.  *  Created on: 2011-11-29 
  5.  *      Author: liufei_learning 
  6.  * 
  7.  */  
  8.   
  9. #include <linux/module.h>  
  10. #include <linux/init.h>  
  11. #include <linux/fs.h>  
  12. #include <mach/hardware.h>  
  13. #include <mach/regs-gpio.h>  
  14. #include <linux/device.h>  
  15. #include <linux/cdev.h>  
  16. #include <linux/types.h>  
  17. #include <linux/kdev_t.h>  
  18.   
  19. #define DEVICE_NAME "tq2440_leds"    //设备名称  
  20. #define LED_MAJOR   231                //主设备号  
  21. #define IOCTL_LED_ON    1                 //LED亮状态  
  22. #define IOCTL_LED_OFF   0                //LED灭状态  
  23.   
  24. static led_major = LED_MAJOR;  
  25. struct cdev led_cdev;   
  26. //控制LED的IO口  
  27. static unsigned long led_table[] =  
  28. {  
  29.     S3C2410_GPB5,  
  30.     S3C2410_GPB6,  
  31.     S3C2410_GPB7,  
  32.     S3C2410_GPB8,  
  33. };  
  34.   
  35. //LED IO口的模式  
  36. static unsigned int led_cfg_table[] =  
  37. {  
  38.     S3C2410_GPB5_OUTP,  
  39.     S3C2410_GPB6_OUTP,  
  40.     S3C2410_GPB7_OUTP,  
  41.     S3C2410_GPB8_OUTP,  
  42. };  
  43.   
  44. static struct class *leds_class;  
  45.   
  46. static int __init leds_open(struct inode *inode, struct file *file)  
  47. {  
  48.     return 0;  
  49. }  
  50.   
  51. static int __init leds_ioctl(struct inode *inode, struct file *file,  
  52.         unsigned int cmd, unsigned long arg)  
  53. {  
  54.     //检测是第几个LED,因开发板上只有4个,索引从0开始  
  55.     if(arg < 0 || arg > 3)  
  56.     {  
  57.         return -EINVAL;  
  58.     }  
  59.   
  60.     //判断LED要执行哪种状态  
  61.     switch(cmd)  
  62.     {  
  63.         case IOCTL_LED_ON:  
  64.         {  
  65.             s3c2410_gpio_setpin(led_table[arg], ~(IOCTL_LED_ON));  
  66.             break;  
  67.         }  
  68.         case IOCTL_LED_OFF:  
  69.         {  
  70.             s3c2410_gpio_setpin(led_table[arg], ~(IOCTL_LED_OFF));  
  71.             break;  
  72.         }  
  73.         default:  
  74.         {  
  75.             return -EINVAL;  
  76.         }  
  77.     }  
  78.   
  79.     return 0;  
  80. }  
  81.   
  82. static struct file_operations led_fops =  
  83. {  
  84.     .owner = THIS_MODULE,  
  85.     .open = leds_open,  
  86.     .ioctl = leds_ioctl,  
  87. };  
  88.   
  89. static int __init leds_init(void)  
  90. {  
  91.     int ret;  
  92.     int i;  
  93.     for(i = 0; i < 4; i++)  
  94.     {  
  95.         //初始化各IO口为输出模式  
  96.         s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);  
  97.         //由原理图可知LED电路是共阳极的(即各IO口输出低电平0才会点亮)  
  98.         //这里初始化为1,不让LED点亮  
  99.         s3c2410_gpio_setpin(led_table[i], ~(IOCTL_LED_OFF));  
  100.     }  
  101.   
  102.   
  103.   <span style="color:#ff0000;">  dev_t devno = MKDEV(led_major, 0);  
  104.     /* 静态申请设备号*/  
  105.     if (led_major)  
  106.       ret = register_chrdev_region(devno, 2, DEVICE_NAME);  
  107.     else /* 动态分配设备号 */  
  108.     {  
  109.       ret = alloc_chrdev_region(&devno, 0, 2, DEVICE_NAME);  
  110.       led_major = MAJOR(devno);  
  111.     }   
  112.     if (ret < 0)  
  113.       return ret;  
  114.        
  115.     /*初始化cdev结构*/  
  116.     cdev_init(&led_cdev, &led_fops);  
  117.     led_cdev.owner = THIS_MODULE;  
  118.     led_cdev.ops = &led_fops;  
  119.     
  120.     /* 注册字符设备 */  
  121.     cdev_add(&led_cdev, MKDEV(led_major, 0), 1);</span>  
  122.       
  123.   
  124.     //注册一个类,使mdev可以在/dev/下面建立设备节点  
  125.     leds_class = class_create(THIS_MODULE, DEVICE_NAME);  
  126.     if( IS_ERR(leds_class) )  
  127.     {  
  128.         printk("creat leds_class failed!");  
  129.         return -1;  
  130.     }  
  131.   
  132.     //创建一个设备节点,节点名字为DEVICE_NAME  
  133.     device_create(leds_class, NULL, MKDEV(led_major, 0), NULL, DEVICE_NAME);  
  134.     printk(DEVICE_NAME "initialized!");  
  135.     return 0;  
  136. }  
  137.   
  138. static void __init leds_exit(void)  
  139. {  
  140.     cdev_del(&led_cdev); /*注销设备*/  
  141.     unregister_chrdev_region(MKDEV(led_major, 0), 2); /*释放设备号*/  
  142.     //删除设备节点  
  143.     device_destroy(leds_class, MKDEV(led_major, 0));  
  144.     //注销类  
  145.     class_destroy(leds_class);  
  146. }  
  147.   
  148. module_init( leds_init);  
  149. module_exit( leds_exit);  
  150.   
  151. MODULE_LICENSE("GPL");  
  152. MODULE_AUTHOR("liufei_learning");  
  153.   
  154. MODULE_DESCRIPTION("tq2440 leds driver");  


分析:
Linux/cdev.h

在Linux内核中使用cdev结构体描述字符设备。该结构体是所有字符设备的抽象,其包含了大量字符设备所共有的特性。cdev结构体定义如下:

 

[cpp] view plaincopyprint?
  1. struct cdev {  
  2.     struct kobject kobj;         
  3.                            /*内嵌的kobject结构,用于内核设备驱动模型的管理*/  
  4.     struct module *owner;    
  5.                            /*指向包含该结构的模块的指针,用于引用计数*/  
  6.     const struct file_operations *ops;      /*指向字符设备操作函数集的指针*/  
  7.     struct list_head list;       
  8.                            /*该结构将使用该驱动的字符设备连接成一个链表*/  
  9.     dev_t dev;              /*该字符设备的起始设备号,一个设备可能有多个设备号*/  
  10.     unsigned int count;     /*使用该字符设备驱动的设备数量*/  
  11. };  


 

cdev结构中的kobj结构用于内核管理字符设备,驱动开发人员一般不使用该成员。ops是指向file_operations结构的指针,该结构定义了操作字符设备的函数。dev就是用来存储字符设备所申请的设备号。count表示目前有多少个字符设备在使用该驱动程序。当使用rmmod卸载模块时,如果count成员不为0,那么系统不允许卸载模块。

list结构是一个双向链表,用于将其他结构体连接成一个双向链表。structlist_head {

    struct list_head *next, *prev;

};

cdev结构体的list成员连接到了inode结构体i_devices成员。其中i_devices也是一个list_head结构。这样,使cdev结构与inode结点组成了一个双向链表。inode结构体表示/dev目录下的设备文件。

每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应的字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点。这样可以通过inode结点的i_cdev字段找到cdev字符结构体。通过cdevops指针,就能找到设备A的操作函数。

 

可以使用如下宏调用来获得主、次设备号:

MAJOR(dev_t dev)

MINOR(dev_t dev)

 

一个 cdev一般它有两种定义初始化方式:静态的和动态的。

静态内存定义初始化:

struct cdevmy_cdev;

cdev_init(&my_cdev,&fops);

my_cdev.owner= THIS_MODULE;

 

动态内存定义初始化:

struct cdev*my_cdev = cdev_alloc();

my_cdev->ops= &fops;

my_cdev->owner= THIS_MODULE;

两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

下面贴出了两个函数的代码,以具体看一下它们之间的差异。

 

[cpp] view plaincopyprint?
  1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)  
  2.  530{  
  3.  531       memset(cdev, 0, sizeof *cdev);  
  4.  532       INIT_LIST_HEAD(&cdev->list);  
  5.  533       kobject_init(&cdev->kobj, &ktype_cdev_default);  
  6.  534       cdev->ops = fops;  
  7.  535}  
  8.    
  9. struct cdev*cdev_alloc(void)  
  10.  512{  
  11.  513       struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);  
  12.  514       if (p) {  
  13.  515               INIT_LIST_HEAD(&p->list);  
  14.  516                kobject_init(&p->kobj,&ktype_cdev_dynamic);  
  15.  517       }  
  16.  518       return p;  
  17.  519}  


由此可见,两个函数完成都功能基本一致,只是cdev_init() 还多赋了一个 cdev->ops 的值。

 

初始化 cdev后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。

[cpp] view plaincopyprint?
  1. intcdev_add(struct cdev *p, dev_t dev, unsigned count)  
  2. {  
  3.   p->dev = dev;  
  4.   p->count = count;  
  5.   return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);  
  6. }  

 

关于 kobj_map()函数就不展开了,我只是大致讲一下它的原理。内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev结构变量,从而取出其中的 ops 字段。

 

当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用cdev_del() 函数来释放 cdev 占用的内存。

[cpp] view plaincopyprint?
  1. voidcdev_del(struct cdev *p)  
  2. {  
  3.   cdev_unmap(p->dev, p->count);  
  4.   kobject_put(&p->kobj);  
  5. }  

其中cdev_unmap() 调用 kobj_unmap() 来释放 cdev_map 散列表中的对象。kobject_put() 释放 cdev 结构本身。

 

学习字符设备模型

基础数据结构

[cpp] view plaincopyprint?
  1. structcdev {  
  2.     struct kobject kobj;         
  3.                            /*内嵌的kobject结构,用于内核设备驱动模型的管理*/  
  4.     struct module *owner;    
  5.                            /*指向包含该结构的模块的指针,用于引用计数*/  
  6.     const struct file_operations *ops;      /*指向字符设备操作函数集的指针*/  
  7.     struct list_head list;       
  8.                            /*该结构将使用该驱动的字符设备连接成一个链表*/  
  9.     dev_t dev;              /*该字符设备的起始设备号,一个设备可能有多个设备号*/  
  10.     unsigned int count;     /*使用该字符设备驱动的设备数量*/  
  11. };  
  12. structkobj_map {  
  13.   20       struct probe {  
  14.   21                struct probe *next;  
  15.   22                dev_t dev;  
  16.   23                unsigned long range;  
  17.   24                struct module *owner;  
  18.   25                kobj_probe_t *get;  
  19.   26                int (*lock)(dev_t, void *);  
  20.   27                void *data;  
  21.   28       } *probes[255];  
  22.   29       struct mutex *lock;  
  23.   30};  
  24.    
  25.    
  26. staticstruct char_device_struct {  
  27.   53       struct char_device_struct *next;  
  28.   54       unsigned int major;  
  29.   55       unsigned int baseminor;  
  30.   56       int minorct;  
  31.   57       char name[64];  
  32.   58       struct cdev *cdev;              /*will die */  
  33.   59} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];  
  34. #defineCHRDEV_MAJOR_HASH_SIZE        255  
 

字符设备模型

每个字符驱动由一个 cdev 结构来表示.

在设备驱动模型(device driver model)中, 使用 (kobject mapping domain)来记录字符设备驱动.

这是由 struct kobj_map 结构来表示的. 它内嵌了255个struct probe指针数组

kobj_map由全局变量 cdev_map 引用: static struct kobj_map *cdev_map;


相关函数说明:

cdev_alloc() 用来创建一个cdev的对象

cdev_add() 用来将cdev对象添加到驱动模型中,其主要是通过kobj_map()来实现的.

kobj_map() 会创建一个probe对象,然后将其插入cdev_map中的某一项中,并关联probe->data 指向cdev

struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev,int *index)

根据设备号,在cdev_map中查找其cdev对象内嵌的kobject.(probe->data->kobj),返回的是cdev的kobject

 

2. 字符设备的设备号

字符设备的主,次设备号的分配:

全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 structchar_device_struct的元素.

每一个对应一个相应的主设备号.

如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中.

这样,通过chrdevs数组,我们就可以知道分配了哪些设备号.


相关函数:

register_chrdev_region( ) 分配指定的设备号范围

alloc_chrdev_region( ) 动态分配设备范围

他们都主要是通过调用函数__register_chrdev_region() 来实现的

要注意,这两个函数仅仅是注册设备号! 如果要和cdev关联起来,还要调用cdev_add()

register_chrdev( ) 申请指定的设备号,并且将其注册到字符设备驱动模型中.

它所做的事情为:

1. 注册设备号, 通过调用 __register_chrdev_region() 来实现

2. 分配一个cdev, 通过调用 cdev_alloc() 来实现

3. 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现

4. 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev.由于register_chrdev()是老的接口,这一步在新的接口中并不需要.


3. 文件系统中对字符设备文件的访问

对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL,则说明该设备文件没有被打开.

由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表


首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open.

(最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open.这一系列的调用过程,本文暂不讨论)

int chrdev_open(struct inode * inode, struct file * filp)

chrdev_open()所做的事情可以概括如下:

1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup()来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.

2. 设置inode->i_cdev , 指向找到的cdev.

3. 将inode添加到cdev->list的链表中.

4. 使用cdev的ops 设置file对象的f_op

5. 如果ops中定义了open方法,则调用该open方法

6. 返回.

执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read,write等操作,就会执行cdev的相应操作.

 

此处3个分析转载自http://blog.csdn.net/cuijianzhongswust/article/details/6887993

0 0