以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、旧接口register_chrdev()函数
上文说到,旧接口register_chrdev()函数内部同时完成了设备号的分配与驱动的注册,现在我们来分析是否真的如此。
1、函数的调用层次关系
|..............register_chrdev
|....................__register_chrdev
|................................__register_chrdev_region(申请设备号)
|................................dev_alloc(这下面三个是在注册设备驱动)
|................................其他代码(效果如同cdev_init)
|................................cdev_add
2、函数内部代码概览
(1)register_chrdev()函数
此函数定义在/include/linux/fs.h文件中。函数的内容如下:
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) {return __register_chrdev(major, 0, 256, name, fops); }
(2)__register_chrdev()函数
此函数定义在/fs/char_dev.c文件中。此函数的内容如下:
/*** __register_chrdev() - create and register a cdev occupying a range of minors* @major: major device number or 0 for dynamic allocation* @baseminor: first of the requested range of minor numbers* @count: the number of minor numbers required* @name: name of this range of devices* @fops: file operations associated with this devices** If @major == 0 this functions will dynamically allocate a major and return* its number.** If @major > 0 this function will attempt to reserve a device with the given* major number and will return zero on success.** Returns a -ve errno on failure.** The name of this device has nothing to do with the name of the device in* /dev. It only helps to keep track of the different owners of devices. If* your module name has only one type of devices it's ok to use e.g. the name* of the module here.*/ int __register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops) {struct char_device_struct *cd;struct cdev *cdev;int err = -ENOMEM;cd = __register_chrdev_region(major, baseminor, count, name);//获取设备号if (IS_ERR(cd))return PTR_ERR(cd);cdev = cdev_alloc(); if (!cdev)goto out2;//对cdev结构体(设备的象征)成员的填充,尤其是ops(驱动的实体函数指针)的填充,//体现了设备与驱动的绑定与关联//后面再用cdev_add函数注册设备时,其实就相当于注册驱动,因为设备已经和驱动关联//这两句代码相当于cdev_init()函数的作用。cdev->owner = fops->owner; cdev->ops = fops;kobject_set_name(&cdev->kobj, "%s", name);err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);if (err)goto out;cd->cdev = cdev;return major ? 0 : cd->major; out:kobject_put(&cdev->kobj); out2:kfree(__unregister_chrdev_region(cd->major, baseminor, count));return err; }
(3)__register_chrdev_region()函数
此函数定义在/fs/char_dev.c文件中。此函数的内容见本文的第三点。
(4)dev_alloc()函数、cdev_add()函数
这些函数的说明,见博客:字符设备驱动高级篇1——注册字符设备驱动的新接口
二、新接口函数
新接口函数包括许多,这里只介绍分配设备号的函数,包括register_chrdev_region()函数和alloc_chrdev_region()函数,前者用于获取指定设备号,后者让内核自动分配设备号。
1、函数的调用关系
|..............register_chrdev_region
|....................__register_chrdev_region
或者
|..............alloc_chrdev_region
|....................__register_chrdev_region
可见内部都调用了__register_chrdev_region这个函数。
2、函数内部代码概览
(1)register_chrdev_region()函数
此函数定义在/fs/char_dev.c文件中。此函数的内容如下:
/*** register_chrdev_region() - register a range of device numbers* @from: the first in the desired range of device numbers; must include* the major number.* @count: the number of consecutive device numbers required* @name: the name of the device or driver.** Return value is zero on success, a negative error code on failure.*/ 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函数。
(2)alloc_chrdev_region()函数
此函数定义在/fs/char_dev.c文件中。此函数的内容如下:
/*** alloc_chrdev_region() - register a range of char device numbers* @dev: output parameter for first assigned number* @baseminor: first of the requested range of minor numbers* @count: the number of minor numbers required* @name: the name of the associated device or driver** Allocates a range of char device numbers. The major number will be* chosen dynamically, and returned (along with the first minor number)* in @dev. Returns zero or a negative error code.*/ int 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; }
可知此函数最终调用了__register_chrdev_region函数。
三、分析__register_chrdev_region函数
老接口register_chrdev函数调用关系如下:
|..............register_chrdev
|....................__register_chrdev
|................................__register_chrdev_region
|................................dev_alloc
|................................cdev_add
新街口register_chrdev_region、alloc_chrdev_region调用关系如下:
|..............register_chrdev_region
|....................__register_chrdev_region
或者
|..............alloc_chrdev_region
|....................__register_chrdev_region
从上面的分析得知,新接口和老接口内部都调用了__register_chrdev_region()函数。此函数用于向内核申请设备号。现在我们来重点分析函数__register_chrdev_region。
__register_chrdev_region()函数定义在/fs/char_dev.c文件中,内容如下:
/** Register a single major with a specified minor range.** If major == 0 this functions will dynamically allocate a major and return* its number.** If major > 0 this function will attempt to reserve the passed range of* minors and will return zero on success.** Returns a -ve errno on failure.*/ 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;// 分配一个新的 char_device_struct 结构,并用 0 填充。cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);if (cd == NULL)return ERR_PTR(-ENOMEM);mutex_lock(&chrdevs_lock);/* temporary */if (major == 0) {//如果申请的设备编号范围的主设备号为 0for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {//从最后一个倒着来找if (chrdevs[i] == NULL) //如果为空(这整个指针数组什么时候赋值为空的)break;}if (i == 0) {ret = -EBUSY;goto out;}major = i; //主设备号就是这个ret = major;}//根据参数设置struct char_device_struct变量的值cd->major = major; //如果为0,则分配刚才得到的//如果不为0,则分配自身设置的cd->baseminor = baseminor;cd->minorct = minorct;strlcpy(cd->name, name, sizeof(cd->name));i = major_to_index(major);//major % BLKDEV_MAJOR_HASH_SIZE;即major%255//这里从自身设置编号时的角度考虑,major为0时这步没意义?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); }
(1)函数说明
/** Register a single major with a specified minor range.* If major == 0 this functions will dynamically allocate a major and return its number.* If major > 0 this function will attempt to reserve the passed range of* minors and will return zero on success.* Returns a -ve errno on failure.*/
(2)参数说明
1)参数 major
如果major大于0,则major表示想要申请的主设备号,函数返回0表示申请成功。
如果major等于0,则表示让内核自动分配主设备号,函数成功则返回所分配的主设备号。
2)参数 baseminor
表示次设备号的起始编号。
3)参数 minorct
表示次设备号的数目。
4)参数 name
表示设备(或者说驱动)的名字。
5)返回值类型:struct 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 cdev *cdev; //指向 字符设备驱动程序描述符 的指针 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; //#define CHRDEV_MAJOR_HASH_SIZE 255