文章目录
-
- 一、chrdevs哈希表
- 二、、 __register_chrdev_region 相关函数分析
一、chrdevs哈希表
用于管理设备号。
定义在
ebf-buster-linux/fs/char_dev.c
。
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];
// #define CHRDEV_MAJOR_HASH_SIZE 255 fs.h
示意图:

注意上图中的一个节点可以表示
一个主设备号,一段次设备号
。
二、、 __register_chrdev_region 相关函数分析
此函数用于向 linux kernel 注册一个主设备号,一个或多个次设备号。
定义在
char_dev.c
。
/* 参数含义依次为:
* 主设备号,次设备号起始,次设备号数量,设备名字
*/
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);
/* 若主设备号为0,由kernel分配一个设备号 */
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));
/* 计算在哈希表中的位置,就是对 255 进行取余 */
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;
/*
* 主设备号相同的情况下需要判断次设备号是否冲突
* 里面两个 if 判断
*/
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哈希表
中,防止设备号冲突
分析结论:
哈希表中每个元素都是一个链表。
链表中的每一个元素似乎可以表示一个主设备号,一段次设备号(多个设备)
相关的其他函数
int register_chrdev_region(dev_t from, unsigned count, const char *name);
此函数内部会调用 __register_chrdev_region。
申请自己指定的一段设备号。这段设备号可能会包含多个主设备号。
而 __register_chrdev_region 每次调用只能申请一个主设备号,一个或多个次设备号。
因此在此函数内部要进行判断,分次调用 __register_chrdev_region。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
由系统分配一个设备号,并调用 __register_chrdev_region 进行注册。
static struct char_device_struct *
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct);
此函数用于注销一段设备号
在哈希表 chrdevs 中找到对应的节点,删除之。
由于之前是kzalloc动态申请,此时不需要手动free了。