在2.4的内核我们使用 register_chrdev来进行字符设备设备驱动(file_operations)的注册和主设备号的分配以及两者的绑定,这种方式每一个主设备号只能绑定一个设备驱动程序,也就是说内核最多支持 255(不一定是255个) 个字符设备驱动程序。但并不代表它只支持255个字符设备,因为有些字符设备可以使用同一个驱动程序。当两个设备使用同一个驱动时,他们的主设备号肯定相同,这是 register_chrdev 函数已经定死的,但次设备号必须不同。也就是说主设备号是在驱动注册时指定的,设备想要使用该驱动就要保证主设备号和自己想要的驱动的主设备号相同,其次一个驱动有可能对应多个设备,此时次设备号就是用来区分使用该驱动的这些设备的。
在2.6的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升。也就是说某个主设备号下的部分次设备号是在驱动注册时指定的,设备想要使用该驱动就要保证主设备号和自己想要的驱动的主设备号相同,并且次设备号也满足驱动注册时指定的范围。其次一个驱动有可能对应多个设备,此时次设备号依然是用来区分使用该驱动的这些设备的,不同的是次设备号有了一个特定的范围,超出这个范围就不在对应这个驱动程序。同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数。
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); }
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
__register_chrdev_region
cdev_alloc
cdev_add
fs/char_dev.c
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
fs/char_dev.c
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; }
alloc_chrdev_region
__register_chrdev_region
通过上面的分析,可以发现老接口和新接口并没有本质上的区别。只不过是老接口进行了单一化的封装,使得部分功能无法得到实现;而新接口相当于没有做封装,故可操作性更强。
fs/char_dev.c
static struct char_device_struct { struct char_device_struct *next;// 指向散列冲突链表中的下一个元素的指针 unsigned int major;// 主设备号 unsigned int baseminor;// 起始次设备号 int minorct;// 设备编号的范围大小 char name[64];// 处理该设备编号范围内的设备驱动的名称 struct cdev *cdev;// 指向字符设备驱动程序描述符的指针 /* will die */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
include/linux/fs.h
#define CHRDEV_MAJOR_HASH_SIZE 255
内核中的每一个字符设备驱动程序都由一个 char_device_struct 结构体来描述,包含主设备号、起始次设备号、次设备号个数等信息。
内核使用 chrdevs 这个指针数组来管理所有的字符设备驱动程序,数组范围 0-255 ,看上去好像还是只支持 255 个字符设备驱动程序,其实并不然,每一个 char_device_struct 结构都含有一个 next 指针,它可以指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠。故每一个char_device_struct变量代表次设备号连续的一组设备,并且对应一个设备驱动程序。
fs/char_dev.c
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; strlcpy(cd->name, name, sizeof(cd->name)); 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); }
参考文章:https://blog.csdn.net/lizuobin2/article/details/52695533