设备管理之字符设备

2015-6-20 chenhui 设备驱动

所谓字符设备,就是以字符为单位来进行传输数据的设备。字符设备使用 struct cdev 结构来进行描述。

 

 

kobj :内嵌的kobject。

owner :所属的模块。

ops :设备驱动使用的文件操作表。

list :系统中所有字符设备都用这个字段连成一个队列。

dev :设备号。

count :设备号范围大小。

我们可以调用cdev_alloc() 来为字符设备分配一个字符设备描述符。

 

 

515~520 行,申请字符设备描述符并初始化他。

当我们使用 cdev_alloc() 得到一个字符设备描述符后,可以调用 cdev_add() 来把他注册他(把他注册到一个叫做 cdev_map 的对象内,而不是设备模型)。

 

:欲注册的字符设备。

devcount :字符设备的设备号和设备号范围。(关于设备号的分配我们在后面再讲)

463~464 行,初始化字符设备的设备号和设备号范围。

465 行,执行真正的注册操作。

 


 

在讲这个函数之前,我们有必要先了解一下 struct kobj_map 这个结构。

 


 

kobj_map 结构的主体是存放 struct probe 对象指针的一个数组,lock字段是保护他的一个锁。

那么这个数组又是干嘛的?实际上这个数组是一个哈希表(散列表),我们可以使用字符设备的主设备号来对他进行索引。比如说主设备号为 的字符设备,那么他就存储在 cdev_map->probes[2] 这个表项上,也就是说 cdev_map->probes[2] 这个 probe 对象就是用来描述主设备号 的。这个 cdev_map 是谁?我们可以回去看一下 cdev_add(),他传给 kobj_map() 的第一个参数就是 cdev_map,也就是说,cdev_map 就是字符设备所使用的 kobj_map 对象,字符设备的设备号信息全部存放在 cdev_map 里。


然后我们再来对 struct probe 结构进行解释。


next :因为 probes[255] 是个哈希表,所以可能会有多个主设备号定位到他这里,那么这些主设备号的 struct probes 对象就会在哈希表项里通过 next 字段连成一个队列。


dev :当前设备号。

range :设备号范围。


data :设备号范围内拥有者的私有数据。对于字符设备来说,他指向字符设备的 cdev 描述符。

 

然后我们来分析 kobj_map() 这个函数。


37 行,在讲这句代码之前,我们有必要先说一下一些必要的背景知识。首先是设备号。所谓的设备号,就是设备的一个“身份证号”,每个设备都拥有独一无二的设备号。设备号本身分为主设备号和次设备号,主设备号对应一个驱动程序,而次设备号则对应一个驱动程序所操作的设备,这是什么意思?我们可以拿一个典型的例子来说明:nand flash 作为一个块设备,他通常被分为多个分区,而这些分区实际上都被看成单独的设备。对于这些设备(分区)而言,因为他们的操作方式都是一样的,所以我们只需要使用一个驱动程序来控制他们,那么这个时候这些设备所使用的主设备号都是这个驱动程序的主设备号,而他们的次设备号则由分区号递增。实际上主设备号和次设备号并不分开存放,而是一起存放在一个整数里,主设备号存放在整数的高12位,而低20位则用于存放次设备号,次设备号的大小就是所谓的设备号范围。主次设备号之间的关系会导致一种现象:那就是次设备号不能超过 0xfffff,一旦超过那么就会发生溢出,即进位导致的主设备号递增。当然,主设备号的递增并不犯法,因为在内核的角度来看,次设备号大小就是设备号的范围,他有没有溢出内核并不关心(当然他会对溢出做出反应)。背景知识说完之后,我们再来说这句代码。MAJOR(dev + range - 1) 是什么意思? MAJOR(x) 实际上就是返回 这个值右移 20 位后得到的值(也就是主设备号)。也就是说,只要 range - 1 小于 (1 << 20),那么 永远等于 1 ( 相当于 MAJOR(dev) - MAJOR(dev) + 1 ),因为只要小于这个值,无论他是何值,在右移20位后都会消失。一般情况下,n都为1,因为很少几乎没有人把设备号范围弄到 1<<20 这么大(即溢出)。那么这个 到底是什么?我们之前说过,次设备号是可能会溢出的,溢出后主设备号被递增,而这里的 n,其实指的就是当前设备号范围所使用的主设备号个数。多数情况下次设备号不会溢出,所以 都为 1;如果次设备号溢出一次,那么 2,因为他多占用了一个主设备号。


42~43 行,主设备号个数不允许超过 255


45~48 行,我们说过,我们使用 struct probe 结构在 kobj_map 对象里描述一个主设备号,那么我们上面已经算出了本次设备号范围所使用的主设备号个数,那么自然也需要那个数量的 struct probe 结构来描述他们。这里就是为那些主设备号分配 struct probe 结构。


50~57 行,初始化所有的 struct probe 结构。


59~65 行,在上面的循环中,p已经指向了已经申请的 probe 内存的最后 + 1,这里让他重新指回内存开始处。然后开始循环,取出当前主设备号(index)在其对应的 kobj_map 里的位置(这里要注意,算出的是设备号范围所占用的主设备号个数,真正的主设备号是 indexindex每次循环都会递增,所以每次循环时 index 都会及时更新到当前需要处理的主设备号),至于这个 index % 255,是因为这个数组是一个哈希表,他的哈希算法就是直接对255取余。


61~62 行,我们说过,由于哈希表的缺点,多个主设备号可能会被挂到同一个哈希表项并形成一个链表(就比如说主设备号 和 257,他们对 255 取余后得到的结果都是2,所以他们会被存放在同一个哈希表项)。既然多个主设备号会被挂到同一个哈希表项,那么我们干脆也对他们进行排序,而排序就要有一个标准,这里的标准就是按照设备号范围从小到大排列。所以这里要遍历链表,直到找到一个比当前设备号范围要大的一个设备号,那么把他取出来之后怎么做呢?我们首先在63 行让新设备号的 next 字段指向他,然后在 64 行把新设备号的 probe 对象写入哈希表项,这样就实现了新设备号的插入。

 

设备号的注册。

我们使用 struct char_device_struct 结构来为字符设备描述一个设备号。

我们可以调用__register_chrdev_region() 来分配一个新的设备号。


 

major :主设备号。

baseminor :次设备号开始值。他一般为 

minor :设备号范围(其实就是次设备号个数)。

name ;设备名。

内核通过 chrdevs 数组来管理设备号,他实际上是个哈希表。

 

 

chrdevs 这个数组存储着 struct char_device_struct 结构的指针,我们就暂且称它为设备号范围描述符吧。那么这个结构又是干嘛的呢?实际上,这个结构是内核用来管理(分配/释放)设备号的核心结构。每当有设备号被分配时,那么就会分配出一个 char_device_struct 对象并填充到 chrdevs 数组的相应位置。


next chrdevs[] 数组是个哈希表,那么他和之前我们讲 kobj_map() 时那个哈希表是一样的,即多个设备号可能会定位到同一个表项,那此时要把他们用链表连接起来,这里就是通过 next 字段实现的。

major :主设备号。


baseminor :次设备号开始值。

minorct :次设备号个数(就是设备号范围)。



下面我们来分析__register_chrdev_region() 是如何分配一个设备号范围的。


105~107 行,分配一个char_device_struct 对象。


111~123 行,如果 major 0,就说明我们需要内核来选出一个设备号进行分配。选一个未用的设备号非常简单,直接在112~115 行遍历chrdevs 数组找到一个为空的数组项就可以了。如果遍历完 chrdev 数组还没有找到合适的设备号,那么就在 118~119 行报错退出。


130 行,计算主设备号的哈希表表索引,其实就是主设备号 % 255


132~137 行,我们说过,多个设备号(范围)可能会被定位到同一个哈希表项,而这些设备号则会连成一个链表,既然他们形成链表就肯定有一个排序方法,他就是:从主设备号从小到大排列;如果主设备号相同,那么就从次设备号开始值(baseminor)从小到大排列。这里就是找到当前设备号范围在链表里应该所在的位置。

 

 

141~157 行,我们之前在 132~137 行找到了当前设备号范围在链表里应该所在的位置,但是在那找是有问题的。比如说,当前设备号范围的主设备号已经被其他驱动程序使用了(即主设备号相同),那么他就会判断次设备号开始值是否比另一个设备号范围的次设备号开始值要小(如果主设备号相同,则根据次设备号开始值(baseminor)从小到大排列),如果成立,那么最起码在 132~137 这里是认为他是合法的设备号范围的。但是他们实际上有一个问题,即:尽管当前设备号范围的次设备号开始值比另一个设备号范围的次设备号开始值要小,但是他不能保证当前设备号范围是否涉及到了另一个设备号范围(比如说当前次设备号开始值为 3;另一个次设备号开始值为5。那么如果当前次设备号的个数为2,那实际上当前设备号范围就已经越界了(3+2=55==5),他的最后一个次设备号跑到了另外一个设备号范围里,这是不允许的),所以这里就是查看当前设备号范围是否越界。


142~143 行,取出另一个设备号范围的第一个次设备号和最后一个次设备号。

144~145 行,取出当前设备号范围的第一个次设备号和最后一个次设备号。

147~150 行,如果当前设备号范围的最后一个次设备号比另一个设备号范围的第一个次设备号范围要大,且比他的最后一个次设备号范围要小。那我们可以断定,设备号范围越界,报错退出。

152~154 行,如果当前设备号范围的第一个次设备号小于另一个设备号范围的最后一个次设备号,且比他的第一个次设备号大。那么我们可以断定,设备号范围越界,报错退出。


157 行,到了这里就说明,设备号范围是可用的。

158~159 行,把新设备号范围的 next 指向另一个设备号范围并把他插入链表,这样就成功了。



发表评论:

Copyright ©2015-2016 freehui All rights reserved