浅析字符设备驱动程序__register_chrdev_region
2016-09-29 01:15
459 查看
在 2.4 的内核我们使用 register_chrdev(0, "hello", &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256
个字符设备驱动程序。
在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数,本文将从它入手来分析内核是如何管理字符设备驱动程序的。
内核中的每一个字符设备驱动程序都由一个 char_device_struct 结构体来描述,包含主设备号、起始次设备号、次设备号个数等信息。
内核使用 chrdevs 这个指针数组来管理所有的字符设备驱动程序,数组范围 0-255 ,看上去好像还是只支持 256 个字符设备驱动程序,其实并不然,每一个 char_device_struct 结构都含有一个 next 指针,它可以指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠。
一、register_chrdev
二、register_chrdev_region
三、__register_chrdev_region
第一次
alloc_chrdev_region(&devid, 0, 2, "hello");
major = MAJOR(devid);
__register_chrdev_region(unsigned int major = 0, unsigned int baseminor = 0,int minorct = 2, const char *name)
chrdevs[i] == null
cp = &chrdevs[i] -> *cp == chrdevs[i] == null
cd->next = *cp == null (*cp 内容是空的,它的内容是别人的地址)
cp == chrdevs[i] = cd (指针和指针赋值,将cd指向的实体的地址赋给 *cp 也就是 chrdevs[i])
第二次
register_chrdev_region(devid, 1, "hello2");
__register_chrdev_region(unsigned int major = 0, unsigned int baseminor = 2,int minorct = 1, const char *name)
if 语句条件不成立,因此,cp = &(*cp)->next ,*cp == (*cp)->next == chrdevs[i]->next == null 跳出 for 循环
cp = &chrdevs[i] -> (*cp) == chrdevs[i] -> (*cp)->next == chrdevs[i]->next == null
cd->next = *cp; cd->next 指向了上一次分配的实例
cp = cd -> chrdevs[i] = cd 指针和指针之间的赋值,chrdevs[i] 就指向了新分配的实例,新分配的实例.next 指向上一次分配的实例
相当于从链表头部插入了一个节点,此时,再来看这个图应该更清晰了。
四、字符设备驱动程序模板
个字符设备驱动程序。
在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数,本文将从它入手来分析内核是如何管理字符设备驱动程序的。
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];
内核中的每一个字符设备驱动程序都由一个 char_device_struct 结构体来描述,包含主设备号、起始次设备号、次设备号个数等信息。
内核使用 chrdevs 这个指针数组来管理所有的字符设备驱动程序,数组范围 0-255 ,看上去好像还是只支持 256 个字符设备驱动程序,其实并不然,每一个 char_device_struct 结构都含有一个 next 指针,它可以指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠。
一、register_chrdev
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; cd = __register_chrdev_region(major, baseminor, count, name); cdev = cdev_alloc(); cdev->owner = fops->owner; cdev->ops = fops; kobject_set_name(&cdev->kobj, "%s", name); err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); cd->cdev = cdev; return major ? 0 : cd->major; }它调用了 __register_chrdev_region 并强制指定了起始次设备号为0,256个,把一个主设备号下的所有次设备号都申请光了。同时它还封装了 cdev_init 和 cdev_add ,倒是很省事。
二、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); } return 0; }register_chrdev_region 则是根据要求的范围进行申请,同时我们需要手动 cdev_init cdev_add 。
三、__register_chrdev_region
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { // **p 是 char_device_struct 类型实例 // *p 是 char_device_struct 实例的指针 // p 是 char_device_struct 实例的指针的指针 struct char_device_struct *cd, **cp; int ret = 0; int i; cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); mutex_lock(&chrdevs_lock); /* * 如果major为0则分配一个没有使用的主设备号 * 注意,从 chrdevs[255] 开始向下查找 */ 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)); // return major % CHRDEV_MAJOR_HASH_SIZE; i = major_to_index(major); /* * 不要分析直接分析 for 循环 * 拿实例来分析不容易晕 * */ // *(a.next) 是 next 实例 // *(a->next) // *((*a)->next) 是 next 实例 // (*cp)->next 是 next 实例的指针,这个指针存在于 **cp 中 // cp 是 chrdevs[i] 的指针,chrdevs[i]本身就是个指针 // cp == &chrdevs[i].next for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || // 正常情况下 If 语句不成立 ((*cp)->major == major && (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor))) ) break; /* 如果有重叠部分,正常情况下应该不重叠 */ 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; } } // 第一次时 *cp == chrdevs[i] == null cd->next = *cp; *cp = cd; // 第一次时,*cp == chrdevs[i] 指向 新分配的 char_device_struct 结构 mutex_unlock(&chrdevs_lock); return cd; }直接分析代码有些吃力,拿个例子来分析。
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); major = MAJOR(devid); devid = MKDEV(major, 2); register_chrdev_region(devid, 1, "hello2");
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(&devid, 0, 2, "hello");
major = MAJOR(devid);
__register_chrdev_region(unsigned int major = 0, unsigned int baseminor = 0,int minorct = 2, const char *name)
chrdevs[i] == null
cp = &chrdevs[i] -> *cp == chrdevs[i] == null
cd->next = *cp == null (*cp 内容是空的,它的内容是别人的地址)
cp == chrdevs[i] = cd (指针和指针赋值,将cd指向的实体的地址赋给 *cp 也就是 chrdevs[i])
第二次
register_chrdev_region(devid, 1, "hello2");
__register_chrdev_region(unsigned int major = 0, unsigned int baseminor = 2,int minorct = 1, const char *name)
if 语句条件不成立,因此,cp = &(*cp)->next ,*cp == (*cp)->next == chrdevs[i]->next == null 跳出 for 循环
cp = &chrdevs[i] -> (*cp) == chrdevs[i] -> (*cp)->next == chrdevs[i]->next == null
cd->next = *cp; cd->next 指向了上一次分配的实例
cp = cd -> chrdevs[i] = cd 指针和指针之间的赋值,chrdevs[i] 就指向了新分配的实例,新分配的实例.next 指向上一次分配的实例
相当于从链表头部插入了一个节点,此时,再来看这个图应该更清晰了。
四、字符设备驱动程序模板
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <linux/poll.h> #include <linux/cdev.h> /* 1. 确定主设备号 */ static int major; static int hello_open(struct inode *inode, struct file *file) { printk("hello_open\n"); return 0; } static int hello2_open(struct inode *inode, struct file *file) { printk("hello2_open\n"); return 0; } /* 2. 构造file_operations */ static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, }; static struct file_operations hello2_fops = { .owner = THIS_MODULE, .open = hello2_open, }; #define HELLO_CNT 2 static struct cdev hello_cdev; static struct cdev hello2_cdev; static struct class *cls; static int hello_init(void) { dev_t devid; /* 3. 告诉内核 */ #if 0 // 老方法 /* (major, 0), (major, 1), ..., (major, 255)都对应hello_fops */ major = register_chrdev(0, "hello", &hello_fops); #else // 新方法 if (major) { devid = MKDEV(major, 0); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */ register_chrdev_region(devid, HELLO_CNT, "hello"); } else { /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */ alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); major = MAJOR(devid); } cdev_init(&hello_cdev, &hello_fops); cdev_add(&hello_cdev, devid, HELLO_CNT); devid = MKDEV(major, 2); register_chrdev_region(devid, 1, "hello2"); cdev_init(&hello2_cdev, &hello2_fops); cdev_add(&hello2_cdev, devid, 1); #endif cls = class_create(THIS_MODULE, "hello"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */ class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */ class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */ class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */ return 0; } static void hello_exit(void) { class_device_destroy(cls, MKDEV(major, 0)); class_device_destroy(cls, MKDEV(major, 1)); class_device_destroy(cls, MKDEV(major, 2)); class_device_destroy(cls, MKDEV(major, 3)); class_destroy(cls); cdev_del(&hello_cdev); unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT); cdev_del(&hello2_cdev); unregister_chrdev_region(MKDEV(major, 2), 1); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
相关文章推荐
- 【整理】--【字符设备】分配设备号register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
- Linux字符设备驱动之register_chrdev_region()系列
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()函数的区别
- 字符设备驱动之register_chrdev_region()系列
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
- 字符设备分配和释放设备编号register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
- 字符设备之register_chrdev与register_chrdev_region(转)
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
- Linux字符设备驱动之register_chrdev_region()系列
- 字符设备驱动: register_chrdev和register_chrdev_region
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
- Linux字符设备驱动之register_chrdev_region()系列
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev() (转载)
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev() .
- 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
- Linux字符设备驱动之register_chrdev_region()系列
- 字符设备之register_chrdev与register_chrdev_region
- Linux字符设备驱动之register_chrdev_region()