register_chrdev_region
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了register_chrdev_region相关的知识,希望对你有一定的参考价值。
1 /** 2 * register_chrdev_region() - register a range of device numbers 3 * @from: the first in the desired range of device numbers; must include 4 * the major number. 5 * @count: the number of consecutive device numbers required 6 * @name: the name of the device or driver. 7 * 8 * Return value is zero on success, a negative error code on failure. 9 */ 10 11 /*register_chrdev_region函数的功能是在已知起始设备号的情况下去申请一组连续的设备号*/ 12 int register_chrdev_region(dev_t from, unsigned count, const char *name) 13 { 14 struct char_device_struct *cd; 15 dev_t to = from + count; 16 dev_t n, next; 17 18 for (n = from; n < to; n = next) { /*每次申请256个设备号*/ 19 next = MKDEV(MAJOR(n)+1, 0); /*先得到下一个设备号(其实也是一个设备号,只不过此时的次设备号为0)并存储于next中*/ 20 if (next > to) /*溢出的情况*/ /*判断在from的基础上再追加count个设备(dev_t to = from+count)是否已经溢出到下一个主设备号*/ 21 next = to; 22 /* 23 如果没有溢出(next小于to),那么整个for语句就只执行个一次__register_chrdev_region函数; 24 否则当设备号溢出时,会把当前溢出的设备号范围划分为几个小范围,分别调用__register_chrdev_region函数。 25 */ 26 cd = __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name); /*核心函数*/ 27 if (IS_ERR(cd)) /*判断错误码*/ 28 goto fail; 29 } 30 return 0; 31 fail: /*当任何一次分配失败的时候,释放所有已经申请的设备号*/ 32 to = n; /*此时n=已经注册好的设备号*/ 33 for (n = from; n < to; n = next) { 34 next = MKDEV(MAJOR(n)+1, 0); 35 kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); /*核心函数*/ 36 } 37 return PTR_ERR(cd); 38 }
1 MKDEV(ma,mi) 2 #define MKDEV(ma,mi) ((ma)<<8 | (mi))
所以MKDEV(5, 0),最后得到的值为【5*(2的8次幂)+0】,等于1280。
2.dev_t
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
3.MAJOR 和 MINOR
#define MAJOR(dev) ((dev)>>8) #define MINOR(dev) ((dev) & 0xff)
4.major_to_index
static inline int major_to_index(int major) { return major % MAX_PROBE_HASH;//MAX_PROBE_HASH=256 }
5.ERR_PTR,通过指针的方式传递错误码
linux/err.h /* * Kernel pointers have redundant information, so we can use a * scheme where we can return either an error code or a dentry * pointer with the same return value. * * This should be a per-architecture thing, to allow different * error and pointer decisions. */ static inline void *ERR_PTR(long error) { return (void *) error; }
6.IS_ERR 判断错误码
static inline long IS_ERR(const void *ptr) { return unlikely((unsigned long)ptr > (unsigned long)-1000L); }
1 1.struct char_device_struct结构 2 fs/char_dev.c 3 static struct char_device_struct { 4 struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针 5 unsigned int major; // 主设备号 6 unsigned int baseminor; // 起始次设备号 7 int minorct; // 设备编号的范围大小 8 const char *name; // 处理该设备编号范围内的设备驱动的名称 9 struct file_operations *fops; // 没有使用 10 struct cdev *cdev; /* will die指向字符设备驱动程序描述符的指针*/ 11 } *chrdevs[MAX_PROBE_HASH]; 12 13 2.__register_chrdev_region 14 /* 15 *例子:__register_chrdev_region(5, 0, 256, “ville”); 16 * 17 * Register a single major with a specified minor range. 18 * 19 * If major == 0 this functions will dynamically allocate a major and return 20 * its number. 21 * 22 * If major > 0 this function will attempt to reserve the passed range of 23 * minors and will return zero on success. 24 * 25 * Returns a -ve errno on failure. 26 */ 27 static struct char_device_struct * 28 __register_chrdev_region(unsigned int major/*5*/, unsigned int baseminor/*0*/, 29 int minorct/*256*/, const char *name/*ville*/) 30 { 31 struct char_device_struct *cd, **cp; 32 int i; 33 int ret = 0; 34 35 cd = kmalloc(sizeof(struct char_device_struct), GFP_KERNEL);/*slab分配一个char_device_struct变量*/ 36 if (cd == NULL) 37 return ERR_PTR(-ENOMEM); 38 39 memset(cd, 0, sizeof(struct char_device_struct));/*将刚刚分配的变量的内存区清零*/ 40 41 write_lock_irq(&chrdevs_lock);/*关中断,禁止内核抢占+读写锁*/ 42 43 /* temporary */ 44 if (major == 0) {/*分配一个主设备号!*/ 45 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { 46 if (chrdevs[i] == NULL) 47 break; 48 } 49 50 if (i == 0) { 51 ret = -EBUSY; 52 goto out; 53 } 54 major = i; 55 ret = major; 56 } 57 58 cd->major = major; 59 cd->baseminor = baseminor; 60 cd->minorct = minorct;/*申请设备号的个数*/ 61 cd->name = name; 62 /****************以上为第一部分,处理char_device_struct变量的分配和初始化************/ 63 /****************以下为第二部分,将char_device_struct变量注册到内核*****************/ 64 i = major_to_index(major);/*将major对256取余数,得到可以存放char_device_struct在chrdevs中的索引*/ 65 66 /* 67 *退出循环: 68 (1)chrdevs[i]为空 69 (2)chrdevs[i]的主设备号大于major 70 (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor 71 */ 72 for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) 73 if ((*cp)->major > major || 74 ((*cp)->major == major && (*cp)->baseminor >= baseminor)) 75 break; 76 /* 77 *如果*cp不空,并且*cp的major与要申请的major相同, 78 *此时,如果(*cp)->baseminor < baseminor + minorct,就会发生冲突 79 *因为和已经分配了的设备号冲突了。 80 *出错! 81 */ 82 if (*cp && (*cp)->major == major && 83 (*cp)->baseminor < baseminor + minorct) { 84 ret = -EBUSY; 85 goto out; 86 } 87 88 /* 89 *所要申请的设备号可以满足 90 */ 91 cd->next = *cp;/*按照主设备号从小到达的顺序排列*/ 92 *cp = cd; 93 write_unlock_irq(&chrdevs_lock); 94 return cd; 95 out: 96 write_unlock_irq(&chrdevs_lock); 97 kfree(cd); 98 return ERR_PTR(ret); 99 }
总结: 大体上分为两个步骤: 1.char_device_struct类型变量的分配以及初始化 2.将char_device_struct变量注册到内核 1.char_device_struct类型变量的分配以及初始化 (1)首先,调用 kmalloc 分配一个 char_device_struct 变量cd。 检查返回值,进行错误处理。 (2)将分配的char_device_struct变量的内存区清零memset。 (3)获取chrdevs_lock读写锁,并且关闭中断,禁止内核抢占,write_lock_irq。 (4)如果传入的主设备号major不为0,跳转到第(7)步。 (5)这时,major为0,首先需要分配一个合适的主设备号。 将 i 赋值成 ARRAY_SIZE(chrdevs)-1,其中的 chrdevs 是包含有256个char_device_struct *类型的数组, 然后递减 i 的值,直到在chrdevs数组中出现 NULL。当chrdevs数组中不存在空值的时候, ret = -EBUSY; goto out; (6)到达这里,就表明主设备号major已经有合法的值了,接着进行char_device_struct变量的初始化。 设置major, baseminor, minorct以及name。 2.将char_device_struct变量注册到内核 (7)将 i 赋值成 major_to_index(major) 将major对256取余数,得到可以存放char_device_struct在chrdevs中的索引 (8)进入循环,在chrdevs[i]的链表中找到一个合适位置。 退出循环的条件: (1)chrdevs[i]为空。 (2)chrdevs[i]的主设备号大于major。 (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor。 注意:cp = &(*cp)->next,cp是char_device_struct **类型,(*cp)->next是一个char_device_struct * 类型,所以&(*cp)->next,就得到一个char_device_struct **,并且这时候由于是指针,所以 对cp赋值,就相当于对链表中的元素的next字段进行操作。 (9)进行冲突检查,因为退出循环的情况可能造成设备号冲突(产生交集)。 如果*cp不空,并且*cp的major与要申请的major相同,此时,如果(*cp)->baseminor < baseminor + minorct, 就会发生冲突,因为和已经分配了的设备号冲突了。出错就跳转到ret = -EBUSY; goto out; (10)到这里,内核可以满足设备号的申请,将cd链接到链表中。 (11)释放chrdevs_lock读写锁,开中断,开内核抢占。 (12)返回加入链表的char_device_struct变量cd。 (13)out出错退出 a.释放chrdevs_lock读写锁,开中断,开内核抢占。 b.释放char_device_struct变量cd,kfree。 c.返回错误信息 3.__unregister_chrdev_region static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct) { struct char_device_struct *cd = NULL, **cp; int i = major_to_index(major); write_lock_irq(&chrdevs_lock); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major == major && (*cp)->baseminor == baseminor && (*cp)->minorct == minorct) break; if (*cp) { cd = *cp; *cp = cd->next; } write_unlock_irq(&chrdevs_lock); return cd; } 总结: (1)注意只有major,baseminor和minorct全都相同,才能标示一个 char_device_struct 变量。 (2)在__unregister_chrdev_region之中,并不释放内存。 三.总结 1.首先,内核使用char_device_struct 结构表示一次字符设备号的申请(针对__register_chrdev_region来说)。 其中单纯对于字符设备号管理而言,最重要的就是 struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针 unsigned int major; // 主设备号 unsigned int baseminor; // 起始次设备号 int minorct; // 设备编号的范围大小 2.内核对字符设备号的集中管理体现在chrdevs数组中,它也是一个散列表,使用chrdevs_lock读写锁进行保护, 操作的时候,需要关闭中断,关闭内核抢占。 散列函数:major_to_index(major),单纯的进行 major%256 使用链表处理冲突。 3.对于字符设备号申请,有可能出现,申请范围很大超出256的情况,这时候,就会在一次register_chrdev_region调用中, 产生多个char_device_struct的情况。其中只有最后一个minorct小于等于256,其余的全都等于256。 4.alloc_chrdev_region 动态的分配设备号 char_dev.c a.参数: dev: 仅仅作为输出参数,成功分配后将保存已分配的第一个设备编号。 baseminor: 被请求的第一个次设备号,通常是0。 count: 所要分配的设备号的个数。 name: 和所分配的设备号范围相对应的设备名称。 b.返回值: 成功返回0,失败返回负的错误编码。 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; } 通过以上分析很好理解。 5.unregister_chrdev_region释放字符设备号 char_dev.c a.参数: from: 所要释放的第一个设备号。 count: 释放设备号的个数。 b.返回值: void unregister_chrdev_region(dev_t from, unsigned count) { 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; kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } } 通过对__unregister_chrdev_region的分析,很好理解这个函数。
以上是关于register_chrdev_region的主要内容,如果未能解决你的问题,请参考以下文章
驱动学习之register_chrdev_region函数和cdev结构体
register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动
字符设备驱动: register_chrdev和register_chrdev_region