在kernel中字符设备用结构体struct char_device_struct表示,其定义如下:
#define CHRDEV_MAJOR_HASH_SIZE 255 /*kernel/include/linux/fs.h*/
static struct char_device_struct{/*kernel/fs*/
structchar_device_struct *next;/*指向主设备号相同、次设备号范围相互不重叠的兄弟节点*/
unsignedint major;/*主设备号*/
unsignedint baseminor;/*起始次设备号*/
intminorct;/*次设备号数*/
charname[64];/*设备名称*/
structcdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
chrdevs指针数组的每个元素代表主设备号相同的一类设备,其实际上是一个链表头,指向char_device_struct的指针,而每一个 char_device_struct 结构都含有一个 next指针,它指向其兄弟字符设备,兄弟设备的主设备号相同,各自的次设备号范围相互不重叠。chrdevs指针数组组织方式如下图所示:
说明:
(1)主设备号0比较特殊,其表示需要给设备重新分配主设备号
(2)注册设备时,允许申请的主设备号大于等于CHRDEV_MAJOR_HASH_SIZE(255),
在处理时,主设备号的计算采用取余计算:
static inline intmajor_to_index(unsigned major) {
return major %CHRDEV_MAJOR_HASH_SIZE;
}
(3)次设备号区间[0,255]
Linux内核有两个分配设备号的函数register_chrdev_region、alloc_chrdev_region这两个函数最终都会调用__register_chrdev_region函数。
* Register a single majorwith a specified minor range.
* If major == 0 thisfunctions will dynamically allocate a major and return its number.
* If major > 0 thisfunction will attempt to reserve the passed range of minors and will returnzero on success.
* Returns a -ve errno onfailure.
*/
static struct char_device_struct *__register_chrdev_region(unsigned intmajor, unsigned int baseminor,int minorct, const char *name)
{
structchar_device_struct *cd, **cp;
intret = 0;
inti;
//给char_device_struct结构体分配内存,用于保存新的设备
cd= kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if(cd == NULL)
returnERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
//如果主设备号为0,则给设备重新分配一个主设备号
if(major == 0) {
//从元素序号最大的开始,寻找未使用的主设备号
for(i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if(chrdevs[i] == NULL)
break;
}
//i==0,说明没有未使用的主设备号,返回错误
if(i == 0) {
ret= -EBUSY;
gotoout;
}
major= i;
ret= major;
}
//填充char_device_struct结构体
cd->major= major;
cd->baseminor= baseminor;
cd->minorct= minorct;
strlcpy(cd->name,name, sizeof(cd->name));
//主设备号采用求余操作,主要是针对主设备号大于等于CHRDEV_MAJOR_HASH_SIZE的进行处理
i= major_to_index(major); // return major %CHRDEV_MAJOR_HASH_SIZE
for(cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
//正常情况下,不会满足if条件,不知作者出于什么目的,可以省略这段代码
if((*cp)->major > major || ((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct> baseminor))))
break;
//检查次设备号是否和兄弟设备重合,*cp实际上就是chrdevs[i],假设有A[Amin,Amax]、B[Bmin,Bmax]两个设
//备,A是已经存在的设备,B是要添加的设备,若两个设备次设备号重叠,则存在三种情况:
//(1)在B设备号区间的左侧重叠,则有Amin=<Bmax<=Amax;
//(2)在B设备号区间的右侧重叠,则有Amin=<Bmin<=Amax;
//(3)B设备号区间包含在A设备号区间,满足上述(1)、(2)两个条件,因此三种情况可以简化为上述两种情况。
if(*cp && (*cp)->major == major) {
intold_min = (*cp)->baseminor;
intold_max = (*cp)->baseminor + (*cp)->minorct - 1;
intnew_min = baseminor;
intnew_max = baseminor + minorct - 1;
/*在新设备次设备号区间左侧重叠 */
if(new_max >= old_min && new_max <= old_max) {
ret= -EBUSY;
gotoout;
}
/*在新设备次设备号区间右侧重叠*/
if(new_min <= old_max && new_min >= old_min) {
ret= -EBUSY;
gotoout;
}
}
//以下两行更新链表头,即更新chrdevs[i]
cd->next= *cp;
*cp= cd;
mutex_unlock(&chrdevs_lock);
returncd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
returnERR_PTR(ret);
}