register_chrdev,register_chrdev_region和alloc_chrdev_region的关系和区别
register_chrdev,register_chrdev_region和alloc_chrdev_region最终都是调用的__register_chrdev_region函数。
而与之相关的主设备号可以通过/proc/devices
来查看。
相关代码如下:
register_chrdev源码解析:
从0-255中查找可用的设备号,并返回。
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; }
完整代码:
1.自动获取主设备号:
/* hello.c */ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> // MODULE_LICENSE("leo BSD/GPL"); static const struct file_operations hello_fops = { .owner = THIS_MODULE, .unlocked_ioctl = NULL, .open = NULL, }; int major; #define HELLO_MAJOR (66) static int hello_init(void) { printk(KERN_ALERT "Hello, world\n"); printk("Hello, world\n"); major = register_chrdev(0, "hello_world", &hello_fops); printk(KERN_WARNING "hello_world: register character device %d.", major); if (major < 0) printk(KERN_WARNING "hello_world: Failed to register character device."); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Hello world\n"); printk("Goodbye, Hello world\n"); printk(KERN_WARNING "hello_world: unregister character device %d.", major); unregister_chrdev(major, "hello_world"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("bigcatio"); MODULE_DESCRIPTION("A good start"); MODULE_VERSION("0.0.1");
2.指定主设备号:
/* hello.c */ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> // MODULE_LICENSE("leo BSD/GPL"); static const struct file_operations hello_fops = { .owner = THIS_MODULE, .unlocked_ioctl = NULL, .open = NULL, }; int major; #define HELLO_MAJOR (66) static int hello_init(void) { printk(KERN_ALERT "Hello, world\n"); printk("Hello, world\n"); major = register_chrdev(HELLO_MAJOR, "hello_world", &hello_fops); printk(KERN_WARNING "hello_world: register character device %d.", HELLO_MAJOR); if (major < 0) printk(KERN_WARNING "hello_world: Failed to register character device."); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Hello world\n"); printk("Goodbye, Hello world\n"); printk(KERN_WARNING "hello_world: unregister character device %d.", HELLO_MAJOR); unregister_chrdev(HELLO_MAJOR, "hello_world"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("bigcatio"); MODULE_DESCRIPTION("A good start"); MODULE_VERSION("0.0.1");
register_chrdev_region源码解析:
从from开始往后的count个设备号中,查找n个可用的设备号并返回。
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); }
完整代码:
/* hello.c */ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> // MODULE_LICENSE("leo BSD/GPL"); static const struct file_operations hello_fops = { .owner = THIS_MODULE, .unlocked_ioctl = NULL, .open = NULL, }; int major; #define HELLO_MAJOR (66) static struct cdev hello_device_cdev; #define MAX_MINORS 256 static int hello_init(void) { int retval; dev_t dev = MKDEV(HELLO_MAJOR, 0); if ((retval = register_chrdev_region(dev, MAX_MINORS, "hello_world")) != 0) { pr_err("hello_world: unable to get major %d\n", HELLO_MAJOR); return retval; } cdev_init(&hello_device_cdev, &hello_fops); if ((retval = cdev_add(&hello_device_cdev, dev, MAX_MINORS)) != 0) { pr_err("hello_world: unable register character device\n"); } return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Hello world\n"); printk("Goodbye, Hello world\n"); printk(KERN_WARNING "hello_world: unregister character device %d.", HELLO_MAJOR); cdev_del(&hello_device_cdev); unregister_chrdev_region(MKDEV(HELLO_MAJOR, 0), MAX_MINORS); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("bigcatio"); MODULE_DESCRIPTION("A good start"); MODULE_VERSION("0.0.1");
alloc_chrdev_region源码解析:
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; }
完整代码:
/* hello.c */ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> // MODULE_LICENSE("leo BSD/GPL"); static const struct file_operations hello_fops = { .owner = THIS_MODULE, .unlocked_ioctl = NULL, .open = NULL, }; int major; static struct cdev hello_device_cdev; #define MAX_MINORS 256 dev_t dev = 0; static int hello_init(void) { int ret; int retval; /* Get a range of minor numbers (starting with 0) to work with */ ret = alloc_chrdev_region(&dev, 0, MAX_MINORS, "hello_world"); if (ret < 0) { pr_err("hello_world: alloc_chrdev_region() failed\n"); return 0; } cdev_init(&hello_device_cdev, &hello_fops); if ((retval = cdev_add(&hello_device_cdev, dev, MAX_MINORS)) != 0) { pr_err("hello_world: unable register character device\n"); } printk(KERN_WARNING "hello_world: register character device %d.", MAJOR(dev)); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Hello world\n"); printk("Goodbye, Hello world\n"); printk(KERN_WARNING "hello_world: unregister character device %d.", MAJOR(dev)); cdev_del(&hello_device_cdev); unregister_chrdev_region(dev, MAX_MINORS); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("bigcatio"); MODULE_DESCRIPTION("A good start"); MODULE_VERSION("0.0.1");
下面进一步剖析_register_chrdev_region:
_register_chrdev_region通过调用find_dynamic_major找到可用的主设备号major,
/* * 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; cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); if (major == 0) { ret = find_dynamic_major(); if (ret < 0) { pr_err("CHRDEV \"%s\" dynamic allocation region is full\n", name); goto out; } major = ret; } if (major >= CHRDEV_MAJOR_MAX) { pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n", name, major, CHRDEV_MAJOR_MAX-1); ret = -EINVAL; goto out; } 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); }
主设备号为0时,会动态寻找可用的空闲的主设备号。
内核中字符设备号最多可以到512,但实际最多能够容纳255个设备,这是由*chrdevs[CHRDEV_MAJOR_HASH_SIZE]决定的,chrdevs中的每一个非空元素都对应着一个字符设备。
查找可用的主设备号:
find_dynamic_major会优先从254开始向CHRDEV_MAJOR_DYN_END(234)寻找可用的主设备号,当返现可用的设备号后则返回该值,否则继续利用哈希值(对255%)从511向384,共127个设备号进行查询,若未使用,则返回该设备号,否则返回失败。
可知,Linux下字符设备的主设备号的范围{129······254}
#define CHRDEV_MAJOR_HASH_SIZE 255 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]; /* index in the above */ static inline int major_to_index(unsigned major) { return major % CHRDEV_MAJOR_HASH_SIZE; } /* fs/char_dev.c */ #define CHRDEV_MAJOR_MAX 512 /* Marks the bottom of the first segment of free char majors */ #define CHRDEV_MAJOR_DYN_END 234 /* Marks the top and bottom of the second segment of free char majors */ #define CHRDEV_MAJOR_DYN_EXT_START 511 #define CHRDEV_MAJOR_DYN_EXT_END 384 static int find_dynamic_major(void) { int i; struct char_device_struct *cd; for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) { if (chrdevs[i] == NULL) return i; } for (i = CHRDEV_MAJOR_DYN_EXT_START; i >= CHRDEV_MAJOR_DYN_EXT_END; i--) { for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next) if (cd->major == i) break; if (cd == NULL) return i; } return -EBUSY; }