本文引用的内核代码参考来自版本。
在Linux系统中,每个注册到系统的设备都有一个编号,这个编号便是Linux系统中的设备号。
设备号作为一种系统资源,需要加以管理。否则,如果设备号与驱动程序对应关系错误,就会引起混乱或引起潜在的问题。
通过查看/proc/devices文件可以得到系统中注册的设备,第一列为主设备号,第二列为设备名称
$cat/proc/devicesCharacterdevices:1mem4/dev/vc/04tty4ttyS5/dev/tty5/dev/console5/dev/ptmx5ttyprintk6lp7vcs10misc13input21sgBlockdevices:7loop8sd9md11sr65sd66sd设备号的构成
一个设备号由主设备号和次设备号构成。
主设备号对应设备驱动程序,同一类设备一般使用相同的主设备号。
次设备号由驱动程序使用,驱动程序用来描述使用该驱动的设备的序号,序号一般从0开始。
Linux设备号用dev_t类型的变量进行标识,这是一个32位无符号整数,内核源码定义为:
/*include/linux/*/typedefu32__kernel_dev_t;typedef__kernel_dev_tdev_t;
主设备号用dev_t的高12位表示,次设备号用dev_t低20位表示。
内核提供了几个宏定义,供驱动程序操作设备号时使用:
/*include/linux/kdev_*/defineMINORMASK((1UMINORBITS)-1)defineMINOR(dev)((unsignedint)((dev)MINORMASK))defineCHRDEV_MAJOR_HASH_SIZE255staticstructchar_device_struct{structchar_device_struct*next;/*链表指针*/unsignedintmajor;/*主设备号*/unsignedintbaseminor;/*次设备号*/intminorct;/*此设备号个数*/charname[64];/*设备名称*/structcdev*cdev;/*willdie*/}*chrdevs[CHRDEV_MAJOR_HASH_SIZE];定义结构体的同时,还定义了一个全局性的指针数组chrdevs,是内核用来分配和管理设备号的。数组中的每一个元素都是指向structchar_device_struct类型的指针。
函数register_chrdev_region()的主要功能是将驱动程序要使用的设备号记录到chrdevs数组中。
__register_chrdev_region()
核心处理函数__register_chrdev_region()内部,首先会分配一个structchar_device_struct类型的指针cd,然后对其进行初始化(已经去除无关代码):
staticstructchar_device_struct*__register_chrdev_region(unsignedintmajor,unsignedintbaseminor,intminorct,constchar*name){cd=kzalloc(sizeof(structchar_device_struct),GFP_KERNEL);if(cd==NULL)returnERR_PTR(-ENOMEM);/*根据主设备号计算索引,搜索chrdevs数组,判断主设备号是否可用*/i=major_to_index(major);for(curr=chrdevs[i];curr;prev=curr,curr=curr-next){if(curr-majormajor)continue;if(curr-majormajor)break;if(curr-baseminor+curr-minorct=baseminor)continue;if(curr-baseminor=baseminor+minorct)break;gotoout;}/*初始化信息*/cd-major=major;cd-baseminor=baseminor;cd-minorct=minorct;strlcpy(cd-name,name,sizeof(cd-name));/*将分配的cd加入到chrdevs[i]中*/if(!prev){cd-next=curr;chrdevs[i]=cd;}else{cd-next=prev-next;prev-next=cd;}}函数申请完内存资源后,开始扫描chrdevs数组,确保当前注册的设备号可用。如果设备号占用,函数返回错误码,即调用失败。
如果设备号可用,则用设备号和名字信息初始化。初始化完成后,将structchar_device_struct加入到内核管理设备号的链表中。
alloc_chrdev_region()此函数由内核动态分配设备号,该函数的内核源码如下,关键部分已加注释:
/*_*/intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name){structchar_device_struct*cd;/*向内核注册设备号*/cd=__register_chrdev_region(0,baseminor,count,name);if(IS_ERR(cd))returnPTR_ERR(cd);/*得到动态获取的首个设备号*/*dev=MKDEV(cd-major,cd-baseminor);return0;}这个函数的核心处理也是由函数__register_chrdev_region()实现的。
与register_chrdev_region()相比,alloc_chrdev_region()在调用__register_chrdev_region()时,第一个参数为0。此时__register_chrdev_region()处理流程代码如下,
staticstructchar_device_struct*__register_chrdev_region(unsignedintmajor,unsignedintbaseminor,intminorct,constchar*name){cd=kzalloc(sizeof(structchar_device_struct),GFP_KERNEL);if(cd==NULL)returnERR_PTR(-ENOMEM);/*查找可用的主设备号*/f(major==0){ret=find_dynamic_major();if(ret0){pr_err("CHRDEV\"%s\"dynamicallocationregionisfull\n",name);gotoout;}major=ret;}}在分配完成structchar_device_struct内存资源之后,通过find_dynamic_major()查找可用的主设备号。后续处理与register_chrdev_region()函数调用处理相同。
设备号分配成功后,将structchar_device_struct类型指针返回给alloc_chrdev_region()函数。然后再通过如下代码将新分派的设备号返回给alloc_chrdev_region()调用者:
*dev=MKDEV(cd-major,cd-baseminor);小结
本文主要介绍了以下几点内容:
设备号是如何构成的,以及对其操作的宏定义。
register_chrdev_region()和alloc_chrdev_region()实现细节。
记录设备号相关信息的关键数据结构structchar_device_struct。
内核通过chrdevs数组来跟踪系统中设备号的使用情况。