对应内核版本为Linux3.0.1,为OK6410开发板附带的源代码。
添加一个字符设备时首先要调用以下接口申请一个设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
或者
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
下面我们来看看这两个接口的内核代码,位置fs/char_dev.c:
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);
}
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来申请序列号,下面我们重点讲解这个函数,先列出代码:
/*
* Register a single major with a specified minor range.
*
* If major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If major > 0 this function will attempt to reserve the passed range of
* minors and will return zero on success.
*
* Returns a -ve errno on failure.
*/
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) { //给定的主设备号为0时,就动态查找一个没有被占用的主设备号来作为新的设备号
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL) //chrdevs是存储所有设备号结构体的指针数组,数组的长度为255,
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i; //用数组索引做主设备号
ret = major;
}
//把设备号信息写入
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);
}
从上面的代码可以看出,所有的设备号结构体存储在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对象的指针,结构体的第一个变量也是一个struct char_device_struct指针,这样的话数组的每个元素实际上是一个单向链表头。
主设备号的范围是(0~255) << 8,可以映射到数组存储的255个链表中((0<<8)号映射到数组的第一个链表,(1<<8)号映射到第二个链表。。。(254<<8)号映射到第255个链表,(255<<8)号映射到第一个链表)。
设备号结构体在链表中的位置是这样决定的:
1.先比主设备号,小的在前
2.如果主设备号相等,比次设备号,小的在前
3.如果次设备号也相等,再将次设备号加上要申请的设备号个数比较,小的在前
如果设备号有冲突则申请失败。