Linux内核用主设备号来定位对应的设备驱动程序,而次设备号则由驱动程序使用,用来标识它所管理的若干同类设备
Linux用dev_t类型变量来标识一个设备号,这是一个32位无符号整数
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MAJOR用来从一个dev_t类型的设备号中提取出主设备号
MINOR用来提取次设备号
MINORBITS用来表示次设备号占用的位数,随着版本更迭,可能会变化。
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);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
上面这个函数是用来向内核注册一个设备号
①:首先定义一个struct char_device_struct *cd;这个指针将在__register_chrdev_region函数中被分配内存。
②:定义了一个dev_t类型的to,这个值等于from+count,表示要分配的最大设备号,count为子设备的个数。
③:声明了两个dev_t类型变量n,next。
④:下面是一个for循环,首先是n=from,n肯定是小于to,因为count至少是1,然后next = MKDEV(MAJOR(n)+1, 0);接下来的判断很重要,如果next>to,就把next = to,这样本次循环结束后,for循环就退出了,因为 n == next == to。如果next<=to,这就说明,本次要分配的子设备号数量太大,不可能全部都在一个主设备号下。这样,循环可能会进行多次。ok,这个搞清楚了,下面分析__register_chrdev_region();
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
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;
}
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
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;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
首先这是一个static关键词修饰的静态成员函数,Linux的老套路了,供提供给外界的API函数所调用。返回值是struct char_device_struct *类型指针。第一个参数major为主设备号,第二个参数是次设备号(0),第三个为次设备号的个数,第四个为设备号的名字
①:定义一个struct char_device_struct *cd = NULL,供分配出内存后返回所用,还有一个二重指针**cp。然后为cd分配内存,如果分配失败,直接返回一个负值。如果major == 0,意思是不指定设备号,由系统分配,这里暂不讨论。然后用主设备号,次设备号起始编号,次设备号个数等为cd的各个成员赋值,然后把name拷贝给cd->name
②:i等于major_to_index(major);就是把major模上一个255,具体原因下面讲。
③:这里有一个chrdevs对象,完整定义如下:
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];
这是一个struct char_device_struct类型的指针数组。内核中用这个指针数组实现一个哈希表。其中key为主设备号,key_value为struct char_devices_struct类型的指针,CHRDEV_MAJOR_HASH_SIZE为255,这也解释了为什么上面的i要等于mojor%255。
④:下面又是一个for循环,这个for循环做了什么呢?首先把cp赋值为哈希表中i对应的对象的指针,如果*cp为空,直接跳出循环,把cd插入到哈希表中,如果*cp不为空,他就找到一个*cp,满足下面两种情况之一
1.*cp->major>major;
2. ((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor)))
这个条件代表了chrdevs的插入规则,即设备号小的,插到设备号大的前面,设备号相等的,起始次设备号小的或者起始次设备号小于要找的对象的最大次设备号的,也插到前面
⑤:当找到的*cp存在并且它的major等于要插入的cd的major,要判断它们的次设备号范围内有没有重叠,如果由重叠,返回错误代码-EBUSY
下面分析alloc_chrdev_region,
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;
}
这个函数还是调用的__register_chrdev_region,只不过major参数传的是0,正好对应上面我们略过不提的major == 0的情况,它分配设备号的逻辑是,首先从哈希表chrdevs的左后一项开始,找到一个键值为空的位置,如果找不到,返回-EBUSY,然后令major等于i,这就为字符设备申请了一个主设备号。