老接口是直接使用register_chrdev函数来进行字符设备号的申请和驱动注册。
新的接口则要先使用register_chrdev_region/alloc_chrdev_region分配设备号
再使用cdev_alloc申请内存存放字符设备信息
最后通过cdev_add把申请到的存放字符设备信息的指针放到全局存放所有字符设备信息的一个表中
注:使用cdev_init和使用cdev_alloc的异同可以查看我的上一篇博客。
为了弄清老接口和新街口的区别,查看具体代码实现。
首先看老接口的实现:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
可以发现 老接口实际是调用的内核内部的一个函数 __register_chrdev,参数分别是(主设备号,次设备号开始,次设备号个数,设备名称,文件描述符)
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->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; }仔细分析__register_chrdev这个函数发现,其内部还是调用了__register_chrdev_region,cdev_alloc,cdev_add这个三个函数。唯一不同的老的接口是次设备号只有一个固定的0
接下来再看一下,__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) { //majo为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; strlcpy(cd->name, name, sizeof(cd->name));
//到这里说明肯定有主设备号了,要么用户定义,要么系统以及分配好i = major_to_index(major); // i = major % CHRDEV_MAJOR_HASH_SIZE //这里主要是通过哈希表来快速查找 cp值的,唯一要说明的是每一个chrdevs中都有一个next指针,用单向链表串器相同主设备号,不同的次设备号的字符设备去驱动
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_chrdev
2、register_chrdev_region
cdev_alloc/cdev_init
cdev_add
3、alloc_chrdev_region
cdev_alloc/cdev_init
cdev_add
三种的区别分别如下
第一种:老的字符设备驱动注册函数,不支持次设备号的注册
第二种:新的字符设备驱动注册函数,由写驱动的人定义主字符设备号
第三种:新的字符设备驱动注册函数,由系统自动分配主字符设备号