char device driver

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了char device driver相关的知识,希望对你有一定的参考价值。

概览:
    第一步:注册设备号                                              信息#tail -f /var/log/message
        注册函数:
            register_chrdev_region() 或                             查看#lsmod
            alloc_chrdev_region()    或                             查看#cat /proc/devices
            register_chrdev()
        注销函数:
            unregist_chrdev_region() 或
            unregister_chrdev()
    
    第二步:初始化cdev并添加到系统
        初始化cdev
            静态初始化 cdev_init() 或
            动态初始化 cdev_alloc()
        添加到系统函数
            cdev_add()
        从系统删除函数
            cdev_del()
            
    第三步:创建设备节点
        创建类
            class_create()          将放于/sysfs                    查看#ls /sys/class
        删除类
            class_destroy()
        
        创建节点
            device_create() 或 class_device_create()  将存放于/dev  查看#ls /dev
        删除节点
            device_destroy() 或 class_device_destroy()
    
    第四步:简单示例

/***************************************************************************************************
                                   第一步:注册设备号
***************************************************************************************************/
Linux内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。
    该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:

  

[cpp] view plain copy
 
  1. static struct char_device_struct  
  2.     {  
  3.         struct char_device_struct *next;    // 指向散列冲突链表中的下一个元素的指针  
  4.         unsigned    int major;              // 主设备号  
  5.         unsigned    int baseminor;          // 起始次设备号  
  6.         int minorct;                        // 设备编号的范围大小  
  7.         char    name[64];                   // 处理该设备编号范围内的设备驱动的名称  
  8.         struct file_operations *fops;       // 没有使用  
  9.         struct cdev *cdev;                  // 指向字符设备驱动程序描述符的指针  
  10.     }*chrdevs[CHRDEV_MAJOR_HASH_SIZE];  


   

 

    1 每一个主设备有一个会分配一个此结构,可以有多个次设备号。次设备是依次递增的。
    2 内核提供了5个函数来来管理字符设备编号。
    
            register_chrdev_region()        指定初始值
            alloc_chrdev_region()           动态分配
            register_chrdev()               指定设备号
            他们都会调用 __register_chrdev_region() 来注册一组设备编号范围(一个char_device_struct结构),我们使用其中一个即可。
            
            unregist_chrdev_region()        释放都用此函数
            unregister_chrdev()             都调用了 __unregister_chrdev_region() 来注销设备

        注册:
               register_chrdev_region(dev_t first,unsigned int count,char *name)
                first :要分配的设备编号范围的初始值(次设备号常设为0);
                count :连续编号范围.
                Name  :编号相关联的设备名称. (/proc/devices);


            int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
                *dev        :存放返回的设备号
                firstminor  :第一个次设备号的号数,常为0;

            int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
                major :要注册的设备号, 若为0则自动分配一个
                name  :设备名
                *fops :以后再聊

        释放:
            void unregister_chrdev(unsigned int major, const char *name);
            void unregister_chrdev_region(dev_t from, unsigned count);
    3 详细分析

       

[cpp] view plain copy
 
  1. /** 
  2.  * register_chrdev_region() - register a range of device numbers 
  3.  * @from: the first in the desired range of device numbers; must include 
  4.  *        the major number. 
  5.  * @count: the number of consecutive device numbers required 
  6.  * @name: the name of the device or driver. 
  7.  * 
  8.  * Return value is zero on success, a negative error code on failure. 
  9.  */  
  10. int register_chrdev_region(dev_t from, unsigned count, const char *name)  
  11. {  
  12.     struct char_device_struct *cd;  
  13.     dev_t to = from + count;  
  14.     dev_t n, next;  
  15.  /**  
  16.     这个for循环主要是判断次设备号是否超过下一个主设备号。  
  17.     如果超过(next<to)那么就只申请next-n个次设备号,也就是说,只申请一个主设备号对应的最大的次设备号的数量。  
  18.     如果没有超过(next>to),那么就是申请用户穿过来的次设备数量,还是next-n,但是这个next=to的  
  19.     其实这里可以不用for循环写的,这里的for循环也只执行了一次  
  20.     还就是char dev主设备号和次设备号的大小又一个宏来控制,有8|8型也有12|20型  
  21. */  
  22.        for (n = from; n < to; n = next) {  
  23.         next = MKDEV(MAJOR(n)+1, 0);  
  24.         if (next > to)  
  25.             next = to;  
  26.         cd = __register_chrdev_region(MAJOR(n), MINOR(n),  
  27.                    next - n, name);  
  28.         if (IS_ERR(cd))  
  29.             goto fail;  
  30.     }  
  31.     return 0;  
  32. fail:  
  33.     to = n;  
  34.     for (n = from; n < to; n = next) {  
  35.         next = MKDEV(MAJOR(n)+1, 0);  
  36.         kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));  
  37.     }  
  38.     return PTR_ERR(cd);  
  39. }  

 

 

 

[cpp] view plain copy
 
  1. /* 
  2.  * Register a single major with a specified minor range. 
  3.  * 
  4.  * If major == 0 this functions will dynamically allocate a major and return 
  5.  * its number. 
  6.  * 
  7.  * If major > 0 this function will attempt to reserve the passed range of 
  8.  * minors and will return zero on success. 
  9.  * 
  10.  * Returns a -ve errno on failure. 
  11.  */  
  12. static struct char_device_struct *<span style="font-size:12px;"><span style="font-family:SimSun;"> </span></span>__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)  
  13. {  
  14.     struct char_device_struct *cd, **cp;  
  15.     int ret = 0;  
  16.     int i;  
  17.   
  18.     cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);  
  19.     if (cd == NULL)  
  20.         return ERR_PTR(-ENOMEM);  
  21.   
  22.     mutex_lock(&chrdevs_lock);  
  23.   
  24.     /* temporary */  
  25.     if (major == 0) {  
  26.         for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {  
  27.             if (chrdevs[i] == NULL)  
  28.                 break;  
  29.         }  
  30.   
  31.         if (i == 0) {  
  32.             ret = -EBUSY;  
  33.             goto out;  
  34.         }  
  35.         major = i;  
  36.         ret = major;  
  37.     }  
  38.   
  39.     cd->major = major;  
  40.     cd->baseminor = baseminor;  
  41.     cd->minorct = minorct;  
  42.     strlcpy(cd->name, name, sizeof(cd->name));  
  43.   
  44.     i = major_to_index(major);  
  45.   
  46.     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)  
  47.         if ((*cp)->major > major ||  
  48.             ((*cp)->major == major &&  
  49.              (((*cp)->baseminor >= baseminor) ||  
  50.               ((*cp)->baseminor + (*cp)->minorct > baseminor))))  
  51.             break;  
  52.   
  53.     /* Check for overlapping minor ranges.  */  
  54.     if (*cp && (*cp)->major == major) {  
  55.         int old_min = (*cp)->baseminor;  
  56.         int old_max = (*cp)->baseminor + (*cp)->minorct - 1;  
  57.         int new_min = baseminor;  
  58.         int new_max = baseminor + minorct - 1;  
  59.   
  60.         /* New driver overlaps from the left.  */  
  61.         if (new_max >= old_min && new_max <= old_max) {  
  62.             ret = -EBUSY;  
  63.             goto out;  
  64.         }  
  65.   
  66.         /* New driver overlaps from the right.  */  
  67.         if (new_min <= old_max && new_min >= old_min) {  
  68.             ret = -EBUSY;  
  69.             goto out;  
  70.         }  
  71.     }  
  72.   
  73.     cd->next = *cp;  
  74.     *cp = cd;  
  75.     mutex_unlock(&chrdevs_lock);  
  76.     return cd;  
  77. out:  
  78.     mutex_unlock(&chrdevs_lock);  
  79.     kfree(cd);  
  80.     return ERR_PTR(ret);  
  81. }  

函数 __register_chrdev_region() 主要执行以下步骤:
1. 分配一个新的 char_device_struct 结构,并用 0 填充。
2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。

 

所有的字符设备驱动描述都放在chrdevs这个数组中。
static struct char_device_struct {
    struct char_device_struct *next;
    unsigned int major;
    unsigned int baseminor;
    int minorct;
    char name[64];
    struct cdev *cdev;        /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];


    4 参考:感谢原著 (有此6个函数的源码及解说)。
        http://blog.csdn.net/iLetLet/article/details/6180314

/***************************************************************************************************
                            第二步:初始化 cdev 并添加到系统
***************************************************************************************************/
1.内核中每个字符设备都对应一个 cdev 结构的变量,定义如下:
    linux-2.6.22/include/linux/cdev.h
    struct cdev 
    {
        struct kobject kobj;                //每个 cdev 都是一个 kobject
        struct module *owner;               //指向实现驱动的模块
        const struct file_operations *ops;  //操纵这个字符设备文件的方法
        struct list_head list;              //与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
        dev_t dev;                          //起始设备编号
        unsigned int count;                 //设备范围号大小
    };

2. 初始化cdev :有两种定义初始化方式:
    
    方式1:静态内存定义初始化:
        struct cdev my_cdev;
        cdev_init(&my_cdev, &fops);
        my_cdev.owner = THIS_MODULE;
        
    方式2:动态内存定义初始化:
        struct cdev *my_cdev = cdev_alloc();
        my_cdev->ops = &fops;
        my_cdev->owner = THIS_MODULE;


    下面是2函数的具体代码:
    

[cpp] view plain copy
 
  1. struct cdev *cdev_alloc(void)       //它主要完成了空间的申请和简单的初始化操作;  
  2.      {  
  3.          struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);  
  4.          if (p)  
  5.          {  
  6.              INIT_LIST_HEAD(&p->list);  
  7.              kobject_init(&p->kobj, &ktype_cdev_dynamic);  
  8.          }  
  9.          return p;  
  10.      }  
  11.   
  12.      void cdev_init(struct cdev *cdev, const struct file_operations *fops)  
  13.      {    
  14.          memset(cdev, 0, sizeof *cdev);  //主要是对空间起到一个清零作用并较之cdev_alloc多了一个ops的赋值操作  
  15.          INIT_LIST_HEAD(&cdev->list);  
  16.          kobject_init(&cdev->kobj, &ktype_cdev_default);  
  17.          cdev->ops = fops;  
  18.      }  



 

3. 添加cdev到系统
    为此可以调用 cdev_add() 函数。传入cdev结构的指针,起始设备编号,以及设备编号范围。
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
        p->dev = dev;
        p->count = count;
        return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
    }

    释放时使用 cdev_del()函数来释放cdev占用的内存。
    void cdev_del(struct cdev *p)
    {
        cdev_unmap(p->dev, p->count);   //释放 cdev_map 散列表中的对象
        kobject_put(&p->kobj);          //释放 cdev 结构本身。
    }

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

/***************************************************************************************************
                            第三步:创建设备节点
***************************************************************************************************/
    方法一:利用mknod命令手动创建设备节点。
    方法二:实际上Linux内核为我们提供了一组函数,可以在模块加载的时候在/dev目录下创建相应设备节点,在卸载时可删除该节点。

    原理:
        1 内核中定义了struct class结构体,它对应一个类。
        2 先调用class_create()函数,可以用它来创建一个类,这个类将存放于sysfs下面.
        3 再调用device_create()函数,从而在/dev目录下创建相应的设备节点。
        4 卸载模块对应的函数是 device_destroy 和 class_destroy()
        注:2.6 以后的版本使用device_create(),之前的版本使用的class_device_create()。

    详解:       

     

[cpp] view plain copy
 
  1. 1:class结构:  
  2.            include/linux/device.h  
  3.            struct class  
  4.            {  
  5.                const   char        *name;  
  6.                struct module       *owner;  
  7.                struct kset         subsys;  
  8.                struct list_head    devices;  
  9.                struct list_head    interfaces;  
  10.                struct kset         class_dirs;  
  11.                struct semaphore sem;       /* locks    children, devices, interfaces */  
  12.                struct class_attribute  *class_attrs;  
  13.                struct device_attribute *dev_attrs;  
  14.     
  15.                int    (*dev_uevent)   (struct device *dev,struct kobj_uevent_env *env);  
  16.                void   (*class_release)(struct class *class);  
  17.                void   (*dev_release)  (struct device   *dev);  
  18.                int    (*suspend)      (struct  device *dev, pm_message_t state);  
  19.                int    (*resume)       (struct device *dev);  
  20.            };  
  21.        2:class_create()  
  22.            class_create()在/drivers/base/class.c中实现:  
  23.            struct class    *class_create(struct module *owner, //  指定类的所有者是哪个模块  
  24.                                          const char *name)     //  指定类名  
  25.            {  
  26.                struct class *cls;  
  27.                int retval;  
  28.                cls =   kzalloc(sizeof(*cls), GFP_KERNEL);  
  29.                if (!cls)  
  30.                {  
  31.                    retval =    -ENOMEM;  
  32.                    goto    error;  
  33.                }  
  34.         
  35.                cls->name   = name;  
  36.                cls->owner = owner;  
  37.                cls->class_release = class_create_release;  
  38.         
  39.                retval = class_register(cls);  
  40.                if (retval)  
  41.                   goto error;  
  42.                return cls;  
  43.                error:  
  44.                    kfree(cls);  
  45.                    return ERR_PTR(retval);  
  46.            }  
  47.             
  48.        3:device_create()函数在/drivers/base/core.c中实现:  
  49.            struct device *device_create(struct class *class,   //指定所要创建的设备所从属的类  
  50.                                        struct devicev *parent, //这个设备的父设备,如果没有就指定为NULL  
  51.                                        dev_t devt,             //设备号  
  52.                                        const char *fmt,        //设备名称  
  53.                                        ...)                    //从设备号  
  54.            {  
  55.                va_list vargs;  
  56.                struct  device *dev;  
  57.                va_start(vargs, fmt);  
  58.                dev = device_create_vargs(class, parent, devt,  NULL, fmt, vargs);  
  59.                va_end(vargs);  
  60.                return  dev;  
  61.   
  62.            }  



 

 

/***************************************************************************************************
                                   第四步:例子
***************************************************************************************************/

 

[cpp] view plain copy
 
    1. #include <linux/module.h>  
    2. #include <linux/kernel.h>  
    3. #include <linux/init.h>  
    4. #include <linux/fs.h>  以上是关于char device driver的主要内容,如果未能解决你的问题,请参考以下文章

      char device

      Linux MTD (Memory Technology Device) subsystem analysis -For Atheros char device

      cdev_add和device_create函数之间的区别?

      打开适配器并捕获数据包

      Windows设备驱动判断

      07 字符设备