linux内核register_chrdev_region()系列函数

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

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

注意,内核并不是为每一个字符设备编号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 char_device_struct 结构。chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。同一个散列桶中的字符设备编号范围是按起始次设备号递增排序的。

 

注册
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()alloc_chrdev_region() register_chrdev()。这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。

所以下面先来看一下 __register_chrdev_region() 函数的实现代码。

static struct char_device_struct *

__register_chrdev_region(unsigned int major, unsigned int baseminor,

                        int minorct, const char *name)

{

       struct char_device_struct *cd, **cp;

       int ret = 0;

       int i;

 

       cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

       if (cd == NULL)

              return ERR_PTR(-ENOMEM);

 

       mutex_lock(&chrdevs_lock);

 

       /* temporary */

       if (major == 0) {

              for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

                     if (chrdevs[i] == NULL)

                            break;

              }

 

              if (i == 0) {

                     ret = -EBUSY;

                     goto out;

              }

              major = i;

              ret = major;

       }

 

       cd->major = major;

       cd->baseminor = baseminor;

       cd->minorct = minorct;

       strncpy(cd->name,name, 64);

 

       i = major_to_index(major);

 

       for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)

              if ((*cp)->major > major ||

                  ((*cp)->major == major &&

                   (((*cp)->baseminor >= baseminor) ||

                    ((*cp)->baseminor + (*cp)->minorct > baseminor))))

                     break;

 

       /* Check for overlapping minor ranges.  */

       if (*cp && (*cp)->major == major) {

              int old_min = (*cp)->baseminor;

              int old_max = (*cp)->baseminor + (*cp)->minorct - 1;

              int new_min = baseminor;

              int new_max = baseminor + minorct - 1;

 

              /* New driver overlaps from the left.  */

              if (new_max >= old_min && new_max <= old_max) {

                     ret = -EBUSY;

                     goto out;

              }

 

              /* New driver overlaps from the right.  */

              if (new_min <= old_max && new_min >= old_min) {

                     ret = -EBUSY;

                     goto out;

              }

       }

 

       cd->next = *cp;

       *cp = cd;

       mutex_unlock(&chrdevs_lock);

       return cd;

out:

       mutex_unlock(&chrdevs_lock);

       kfree(cd);

       return ERR_PTR(ret);

}

函数 __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 结构的地址。

 

分析完 __register_chrdev_region() 后,我们来一个个看那三个注册函数

int register_chrdev_region(dev_t from, unsigned count, const char *name)

{

       struct char_device_struct *cd;

       dev_t to = from + count;

       dev_t n, next;

 

       for (n = from; n < to; n = next) {

              next = MKDEV(MAJOR(n)+1, 0);

              if (next > to)

                     next = to;

              cd = __register_chrdev_region(MAJOR(n), MINOR(n),

                            next - n, name);

              if (IS_ERR(cd))

                     goto fail;

       }

       return 0;

fail:

       to = n;

       for (n = from; n < to; n = next) {

              next = MKDEV(MAJOR(n)+1, 0);

              kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));

       }

       return PTR_ERR(cd);

}

register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。这里, from 是你要分配的起始设备编号. from 的次编号部分常常是 0, 但是没有要求是那个效果. count 是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个次编号; 但是只要你要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices sysfs .

nt alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

                     const char *name)

{

       struct char_device_struct *cd;

       cd = __register_chrdev_region(0, baseminor, count, name);

       if (IS_ERR(cd))

              return PTR_ERR(cd);

       *dev = MKDEV(cd->major, cd->baseminor);

       return 0;

}

alloc_chrdev_region() 函数用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。

 

int register_chrdev(unsigned int major, const char *name,

                  const struct file_operations *fops)

{

       struct char_device_struct *cd;

       struct cdev *cdev;

       char *s;

       int err = -ENOMEM;

 

       cd = __register_chrdev_region(major, 0, 256, name);

       if (IS_ERR(cd))

              return PTR_ERR(cd);

      

       cdev = cdev_alloc();

       if (!cdev)

              goto out2;

 

       cdev->owner = fops->owner;

       cdev->ops = fops;

       kobject_set_name(&cdev->kobj, "%s", name);

       for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))

              *s = '!';

             

       err = cdev_add(cdev, MKDEV(cd->major, 0), 256);

       if (err)

              goto out;

 

       cd->cdev = cdev;

 

       return major ? 0 : cd->major;

out:

       kobject_put(&cdev->kobj);

out2:

       kfree(__unregister_chrdev_region(cd->major, 0, 256));

       return err;

}

最后一个 register_chrdev() 是一个老式分配设备编号范围的函数。它分配一个单独主设备号和 0 ~ 255 的次设备号范围。如果申请的主设备号为 0 则动态分配一个。该函数还需传入一个 file_operations 结构的指针,函数内部自动分配了一个新的 cdev 结构。

 

注销
和注册分配字符设备编号范围类似,内核提供了两个注销字符设备编号范围的函数,分别是 unregister_chrdev_region() unregister_chrdev() 。它们都调用了 __unregister_chrdev_region() 函数。

static struct char_device_struct *

__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)

{

       struct char_device_struct *cd = NULL, **cp;

       int i = major_to_index(major);

 

       mutex_lock(&chrdevs_lock);

       for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)

              if ((*cp)->major == major &&

                  (*cp)->baseminor == baseminor &&

                  (*cp)->minorct == minorct)

                     break;

       if (*cp) {

              cd = *cp;

              *cp = cd->next;

       }

       mutex_unlock(&chrdevs_lock);

       return cd;

}

 

void unregister_chrdev_region(dev_t from, unsigned count)

{

       dev_t to = from + count;

       dev_t n, next;

 

       for (n = from; n < to; n = next) {

              next = MKDEV(MAJOR(n)+1, 0);

              if (next > to)

                     next = to;

              kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));

       }

}

 

void unregister_chrdev(unsigned int major, const char *name)

{

       struct char_device_struct *cd;

       cd = __unregister_chrdev_region(major, 0, 256);

       if (cd && cd->cdev)

              cdev_del(cd->cdev);

       kfree(cd);

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值