linux内核register_chrdev_region()系列函数

原创 2010年05月27日 11:06:00

内核中所有已分配的字符设备编号都记录在一个名为 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);

}

Linux内核register_chrdev_region()系列函数

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。其中,...
  • qq_27312943
  • qq_27312943
  • 2015-11-22 18:44:37
  • 288

linux内核register_chrdev_region()系列函数

内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:   static struct ...
  • yuweixian
  • yuweixian
  • 2010-05-11 12:29:00
  • 245

浅析字符设备驱动程序__register_chrdev_region

在 2.4 的内核我们使用 register_chrdev(0, "hello", &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 fi...
  • lizuobin2
  • lizuobin2
  • 2016-09-29 01:15:30
  • 3496

Linux字符设备注册函数 register_chrdev详解

Linux字符设备注册函数 register_chrdev详解   当我们需要注册字符设备的时候,需要module_init()中调用register_chrdev() 注册。 下面主要介绍接口的实现...
  • wuheshi
  • wuheshi
  • 2016-10-24 18:03:28
  • 6535

register_chrdev_region()系列函数剖析

原文: http://www.cnblogs.com/armlinux/archive/2010/09/12/2396919.html 内核提供了三个函数来注册一组字符设备编号,这三个函数分...
  • chj90220
  • chj90220
  • 2013-12-10 13:47:11
  • 642

__register_chrdev_region函数解析

/* * Register a single major with a specified minor range. * * If major == 0 this functions will ...
  • wu572522799
  • wu572522799
  • 2017-08-23 21:15:33
  • 49

__register_chrdev_region分析(简单字符设备驱动在内核的工作原理)

__register_chrdev_region分析 、简单字符设备驱动在内核的工作原理
  • TongxinV
  • TongxinV
  • 2017-01-24 13:36:50
  • 556

Linux字符设备驱动之register_chrdev_region()系列

1.内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个char_device_struct 结构,它的定义如下: static struct cha...
  • avrmcu1
  • avrmcu1
  • 2013-11-25 01:01:37
  • 513

androidstudio register_chrdev_region

转载来自http://www.cnblogs.com/androidstudio/p/5296530.html androidstudio register_chr...
  • qq_20678703
  • qq_20678703
  • 2016-09-02 10:53:56
  • 408

内存管理之函数mm_init解读之mem_init

linux kernel000000000000
  • sunlei0625
  • sunlei0625
  • 2017-02-28 19:29:35
  • 1169
收藏助手
不良信息举报
您举报文章:linux内核register_chrdev_region()系列函数
举报原因:
原因补充:

(最多只允许输入30个字)