linux 字符设备驱动框架

1410阅读 0评论2013-07-16 stone548534
分类:LINUX


  1. 一、字符设备结构
  2. 1.内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev。
  3. struct cdev {
  4.    struct kobject kobj;//每个 cdev 都是一个 kobject
  5.    struct module *owner;//指向实现驱动的模块
  6.    const struct file_operations *ops; //操纵这个字符设备文件的方法
  7.    struct list_head list;//与 cdev对应的字符设备文件的 inode->i_devices 的链表头
  8.    dev_t dev;//起始设备编号
  9.    unsigned int count;//设备范围号大小
  10. };

  11. 2.内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:
  12. static struct char_device_struct {
  13.    struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
  14.    unsigned int major; // 主设备号
  15.    unsigned int baseminor; // 起始次设备号
  16.    int minorct; // 设备编号的范围大小
  17.    char name[64]; // 处理该设备编号范围内的设备驱动的名称
  18.    struct file_operations *fops; // 没有使用
  19.    struct cdev *cdev; // 指向字符设备驱动程序描述符的指针
  20. } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
  21. 注意,内核并不是为每一个字符设备编号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 char_device_struct 结构。chrdevs 散列表的大小是255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。同一个散列桶中的字符设备编号范围是按起始次设备号递增排序的。

  22. 3.kobj_map结构体是用来管理设备号及其对应的设备的。 内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
  23. kobj_map()函数就是将指定的设备号加入到该数组,kobj_lookup()则查找该结构体,然后返回对应设备号的kobject对象,利用该kobject对象,我们可以得到包含它的对象如cdev。
  24. struct probe *probes[255];
  25. struct kobj_map {
  26.     struct probe {
  27.      struct probe *next; //这样形成了链表结构
  28.      dev_t dev; //设备号
  29.      unsigned long range; //设备号的范围
  30.      struct module *owner;
  31.      kobj_probe_t *get;
  32.      int (*lock) (dev_t, void *);
  33.      void *data; //指向struct cdev对象
  34.     } *probes[255];
  35.     struct mutex *lock;
  36. }

  37. 二、字符设备的注册
  38. 1.一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
  39. (1)静态内存定义初始化:
  40. struct cdev my_cdev;
  41. cdev_init(&my_cdev, &fops);
  42. my_cdev.owner = THIS_MODULE;
  43. (2)动态内存定义初始化:
  44. struct cdev *my_cdev = cdev_alloc();
  45. my_cdev->ops = &fops;
  46. my_cdev->owner = THIS_MODULE;
  47. 两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

  48. 2.注册一个独立的cdev设备的基本过程如下:
  49. (1)、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)
  50. struct cdev *my_cdev = cdev_alloc();
  51. my_cdev->ops=&my_ops;

  52. (2)、初始化struct cdev
  53. void cdev_init(struct cdev *cdev, const struct file_operations *fops)

  54. (3)、初始化cdev.owner
  55. cdev.owner = THIS_MODULE;

  56. (4)、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)
  57. int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  58. //p是 cdev 结构, dev是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形. 例如, 设想 SCSI 磁带驱动, 它允许用户空间来选择操作模式(例如密度), 通过安排多个次编号给每一个物理设备.
  59. //在使用 cdev_add 是有几个重要事情要记住. 第一个是这个调用可能失败. 如果它返回一个负的错误码, 你的设备没有增加到系统中. 它几乎会一直成功, 但是, 并且带起了其他的点: cdev_add 一返回, 你的设备就是"活的"并且内核可以调用它的操作. 除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add.

  60. (5)源码:
  61. int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  62. {
  63.     int error;
  64.     
  65.     p->dev = dev;
  66.     p->count = count;
  67.     
  68.     //将cdev结构添加到cdev_map的数组中
  69.     error = kobj_map(cdev_map, dev, count, NULL,exact_match, exact_lock, p);
  70.     if (error)
  71.         return error;
  72.     //父kobject结构计数加1
  73.     kobject_get(p->kobj.parent);    
  74.     return 0;
  75. }

  76. //内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。
  77. //kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,
  78. //根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
  79. int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range, struct module *module, kobj_probe_t *probe, int (*lock)(dev_t, void *), void *data)
  80. {
  81.         //dev_t的前12位为主设备号,后20位为次设备号。
  82.         //n = MAJOR(dev + range - 1) - MAJOR(dev) + 1 表示设备号范围(dev, dev+range)中不同的主设备号的个数。通常n的值为1。
  83.     unsigned n = MAJOR(dev+range-1) - MAJOR(dev) + 1;
  84.     unsigned index = MAJOR(dev);//主设备号
  85.     unsigned i;
  86.     struct probe *p;

  87.     if (n > 255)//若n > 255,则超出了kobj_map中probes数组的大小
  88.         n = 255;
  89.     p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);//分配n个struct probe
  90.     if(p == NULL)
  91.         return -ENOMEM;
  92.        
  93.     for(i = 0; i < n; i++, p++) {//用函数的参数初始化probe
  94.         p->owner = module;
  95.         p->get = probe;
  96.         p->lock = lock;
  97.         p->dev = dev;
  98.         p->range = range;
  99.         p->data = data;//保存的是cdev结构
  100.     }
  101.     mutex_lock(domain->lock);
  102.     
  103.     //从for循环可以看出kobj_map中的probes数组中每个元素为一个struct probe链表的头指针。
  104.     for(i = 0, p-=n; i < n; i++, p++, index++) {
  105.         struct probe **s = &domain->probes[index % 255];//从数组中找到主设备号为index的probe结构链表,在此链表中每个probe结构都是相同的主设备号index
  106.         //链表中的元素是按照range值从小到大排列的。while循环即是找出该将p插入的位置。
  107.         while(*s && (*s)->range < range)
  108.             s = &(*s)->next;
  109.         p->next = *s;
  110.         *s = p;//插入链表
  111.     }
  112.     mutex_unlock(domain->lock);
  113.     return 0;
  114. }

  115. 三、分配设备号
  116. 内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。这三个函数都会调用一个共用的__register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。
  117. register_chrdev_region( ) //分配指定的设备号范围
  118. alloc_chrdev_region( ) //动态分配设备范围
  119. register_chrdev( )//申请指定的设备号,并且将其注册到字符设备驱动模型中.是一个老式分配设备编号范围的函数

  120. //内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构
  121. static struct char_device_struct * __register_chrdev_region(unsigned int major,unsigned int baseminor, int minorct, const char *name)
  122. {
  123.    struct char_device_struct *cd, **cp;
  124.    int ret = 0;
  125.    int i;
  126.    cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//分配一个新的 char_device_struct 结构
  127.    if (cd == NULL)
  128.        return ERR_PTR(-ENOMEM);
  129.    mutex_lock(&chrdevs_lock);
  130.    
  131.    //如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。
  132.    //动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。
  133.    //所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
  134.    if (major == 0) {
  135.         for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--)
  136.            if (chrdevs[i] == NULL)
  137.                break;
  138.        if (i == 0) {
  139.            ret = -EBUSY;
  140.            goto out;
  141.        }
  142.        major = i;
  143.        ret = major;
  144.    }
  145.    //根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
  146.    cd->major = major;
  147.    cd->baseminor = baseminor;
  148.    cd->minorct = minorct;
  149.    strncpy(cd->name,name, 64);
  150.    i = major_to_index(major);
  151.    
  152.    //计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
  153.    for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
  154.        if ((*cp)->major > major ||((*cp)->major == major && ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) ))
  155.            break;
  156.    /* Check for overlapping minor ranges. */
  157.    if (*cp && (*cp)->major == major) {
  158.        int old_min = (*cp)->baseminor;
  159.        int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
  160.        int new_min = baseminor;
  161.        int new_max = baseminor + minorct - 1;
  162.        /* New driver overlaps from the left. */
  163.        if (new_max >= old_min && new_max <= old_max) {
  164.            ret = -EBUSY;
  165.            goto out;
  166.        }
  167.        /* New driver overlaps from the right. */
  168.        if (new_min <= old_max && new_min >= old_min) {
  169.            ret = -EBUSY;
  170.            goto out;
  171.        }
  172.    }
  173.    //将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。
  174.    cd->next = *cp;
  175.    *cp = cd;
  176.    mutex_unlock(&chrdevs_lock);
  177.    return cd;
  178. out:
  179.    mutex_unlock(&chrdevs_lock);
  180.    kfree(cd);
  181.    return ERR_PTR(ret);
  182. }

  183. .注册cdev设备老方法
  184. 1.如果你深入浏览 2.6 内核的大量驱动代码, 你可能注意到有许多字符驱动不使用我们刚刚描述过的 cdev 接口. 你见到的是还没有更新到 2.6 内核接口的老代码. 因为那个代码实际上能用, 这个更新可能很长时间不会发生. 为完整, 我们描述老的字符设备注册接口, 但是新代码不应当使用它; 这个机制在将来内核中可能会消失.
  185. 注册一个字符设备的经典方法是使用:
  186. int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
  187. 这里, major 是感兴趣的主设备号, name 是驱动的名子(出现在 /proc/devices), fops 是缺省的 file_operations 结构. 一个对 register_chrdev 的调用为给定的主编号注册 0 - 255 的次编号, 并且为每一个建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号.register_chrdev函数的major参数如果等于0,则表示采用系统动态分配的主设备号。
  188. 它所做的事情为:
  189. (1). 注册设备号, 通过调用 __register_chrdev_region() 来实现
  190. (2). 分配一个cdev, 通过调用 cdev_alloc() 来实现
  191. (3). 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现
  192. (4). 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,这一步在新的接口中并不需要.

  193. 2.如果你使用 register_chrdev, 从系统中去除你的设备的正确的函数是:
  194. int unregister_chrdev(unsigned int major, const char *name);//major 和 name 必须和传递给 register_chrdev 的相同, 否则调用会失败.

  195. 五、字符设备驱动模板
  196. (1)设置驱动文件操作结构体
  197. static struct file_operations XXX_fops =
  198. {
  199.    .owner = THIS_MODULE,
  200.    .read = xxx_read,
  201.    .write = xxx_write,
  202.    .ioctl = xxx_ioctl,
  203.     ...
  204. };
  205. (2)编写字符设备驱动模块加载与卸载函数
  206. static int _ _init xxx_init(void){//模块加载--〉申请设备号,添加设备
  207.     ...
  208.     cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化cdev
  209.     xxx_dev.cdev.owner = THIS_MODULE;
  210.     
  211.     if (xxx_major){//获取字符设备号
  212.         register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
  213.     }
  214.     else{
  215.         alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
  216.     }
  217.     ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
  218.     ...//可能申请中断号request_irq
  219. }

  220. static void _ _exit xxx_exit(void){/*设备驱动模块卸载函数*/
  221.     unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号
  222.     cdev_del(&xxx_dev.cdev); //注销设备
  223.     ...//释放中断号free_irq
  224. }

上一篇:没有了
下一篇:TCP、UDP、IP 协议分析