字符设备如何分配/注销设备号

前一篇对cdev结构体及初始化做了简单介绍

Linux内核有两个分配设备号的函数

 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 (n = from; n < to; n = next) {
17         next = MKDEV(MAJOR(n)+1, 0);
18         if (next > to)
19             next = to;
20         cd = __register_chrdev_region(MAJOR(n), MINOR(n),
21                    next - n, name);
22         if (IS_ERR(cd))
23             goto fail;
24     }
25     return 0;
26 fail:
27     to = n;
28     for (n = from; n < to; n = next) {
29         next = MKDEV(MAJOR(n)+1, 0);
30         kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
31     }
32     return PTR_ERR(cd);
33 }
register_chrdev_region()
 1 /**
 2  * alloc_chrdev_region() - register a range of char device numbers
 3  * @dev: output parameter for first assigned number
 4  * @baseminor: first of the requested range of minor numbers
 5  * @count: the number of minor numbers required
 6  * @name: the name of the associated device or driver
 7  *
 8  * Allocates a range of char device numbers.  The major number will be
 9  * chosen dynamically, and returned (along with the first minor number)
10  * in @dev.  Returns zero or a negative error code.
11  */
12 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
13             const char *name)
14 {
15     struct char_device_struct *cd;
16     cd = __register_chrdev_region(0, baseminor, count, name);
17     if (IS_ERR(cd))
18         return PTR_ERR(cd);
19     *dev = MKDEV(cd->major, cd->baseminor);
20     return 0;
21 }
alloc_chrdev_region()

1. 这两个函数最终都会调用__register_chrdev_region()函数。并且会用到一个重要的 struct char_device_struct结构体。

  1 #define CHRDEV_MAJOR_HASH_SIZE    255
  2 /*定义char_device_struct结构体并声明一个全局的指针数组*/
  3 static struct char_device_struct {
  4     struct char_device_struct *next;
  5     unsigned int major;
  6     unsigned int baseminor;
  7     int minorct;
  8     char name[64];
  9     struct cdev *cdev;        /* will die */
 10 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; 
 11 /*
 12  * Register a single major with a specified minor range.
 13  *
 14  * If major == 0 this functions will dynamically allocate a major and return
 15  * its number.
 16  *
 17  * If major > 0 this function will attempt to reserve the passed range of
 18  * minors and will return zero on success.
 19  *
 20  * Returns a -ve errno on failure.
 21  */
 22 static struct char_device_struct *
 23 __register_chrdev_region(unsigned int major, unsigned int baseminor,
 24                int minorct, const char *name)
 25 {
 26     struct char_device_struct *cd, **cp;
 27     int ret = 0;
 28     int i;
 29 
 30     cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
 31     if (cd == NULL)
 32         return ERR_PTR(-ENOMEM);
 33 
 34     mutex_lock(&chrdevs_lock);
 35 
 36     /*--当调用alloc_chrdev_region时,传入的major为0,自动分配设备,
 37     *但是有一个缺点就是其分配的设备号只能在255内,而且并没有使用hash表,
 38     *从而大大减少了linux所支持的设备号数。
 39     */
 40     if (major == 0) 
 41     {
 42         for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) 
 43         {
 44             if (chrdevs[i] == NULL)
 45                 break;
 46         }
 47 
 48         if (i == 0) 
 49         {
 50             ret = -EBUSY;
 51             goto out;
 52         }
 53         major = i;
 54         ret = major;
 55     }
 56 
 57     cd->major = major;
 58     cd->baseminor = baseminor;
 59     cd->minorct = minorct;
 60     strlcpy(cd->name, name, sizeof(cd->name));
 61 
 62     i = major_to_index(major);
 63 
 64     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
 65         if (
 66                     (*cp)->major > major       ||
 67                     
 68                     (
 69                             (*cp)->major == major    &&
 70                              (
 71                                      (*cp)->baseminor >= baseminor    ||
 72                                       (*cp)->baseminor + (*cp)->minorct > baseminor
 73                              )
 74                     )
 75            )
 76             break;
 77 
 78     /* Check for overlapping minor ranges.  */
 79     if (*cp && (*cp)->major == major) 
 80     {
 81         int old_min = (*cp)->baseminor;
 82         int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
 83         int new_min = baseminor;
 84         int new_max = baseminor + minorct - 1;
 85 
 86         /* New driver overlaps from the left.  */
 87         if (new_max >= old_min && new_max <= old_max) {
 88             ret = -EBUSY;
 89             goto out;
 90         }
 91 
 92         /* New driver overlaps from the right.  */
 93         if (new_min <= old_max && new_min >= old_min) {
 94             ret = -EBUSY;
 95             goto out;
 96         }
 97     }
 98 
 99     cd->next = *cp;
100     *cp = cd;
101     mutex_unlock(&chrdevs_lock);
102     return cd;
103 out:
104     mutex_unlock(&chrdevs_lock);
105     kfree(cd);
106     return ERR_PTR(ret);
107 }

2. 在分析函数前先参考关于hash表的知识

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

3. 分析如何指定分配设备号

其中用的的一个函数定义如下,该函数获得数组的索引,其中major相当于hash表的key,len=255。

下面分析的major=128,383,638都对应数组的第128项,添加在chardevs[128]开始的链表中。

1 static inline int major_to_index(unsigned major)
2 {
3     return major % CHRDEV_MAJOR_HASH_SIZE;
4 }

 3.1 指定major=128,分配链表中的第一个设备号,对应循环中*cp==NULL退出循环。

3.2 指定major=638,分配第二个设备号,对应循环中*cp==NULL退出循环。

3.3 指定major=383,对应循环中(*cp)->major > major 的条件,跳出循环。

4 注销设备号,设备号是系统中宝贵的资源,模块卸载时要注销设备号。即根据主次设备号找到对应的char_device_struct结构体并从链表中删除

 1 static struct char_device_struct *
 2 __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
 3 {
 4     struct char_device_struct *cd = NULL, **cp;
 5     int i = major_to_index(major);
 6 
 7     mutex_lock(&chrdevs_lock);
 8     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
 9     {
10         if ((*cp)->major == major &&
11             (*cp)->baseminor == baseminor &&
12             (*cp)->minorct == minorct)
13             break;
14     }
15     if (*cp) {
16         cd = *cp;
17         *cp = cd->next;
18     }
19     mutex_unlock(&chrdevs_lock);
20     return cd;
21 }

 

posted @ 2016-11-05 09:23  bluebluebluesky  阅读(774)  评论(0编辑  收藏  举报