初始化字符设备子系统chrdev_init
c
void __init chrdev_init(void)
{
/*
* Keep cdev_subsys around because (and only because) the kobj_map code
* depends on the rwsem it contains. We don't make it public in sysfs,
* however.
*/
subsystem_init(&cdev_subsys);
cdev_map = kobj_map_init(base_probe, &cdev_subsys);
}
函数功能
chrdev_init
函数用于初始化字符设备子系统,创建字符设备的管理基础设施。它是Linux设备驱动框架中字符设备管理的基础初始化函数。
代码逐段解释
第1行:函数声明
c
void __init chrdev_init(void)
- 返回类型 :
void
- 无返回值 __init
: 表示该函数只在内核初始化阶段使用,初始化完成后内存会被释放- 参数: 无参数
第9行:初始化字符设备子系统
c
subsystem_init(&cdev_subsys);
cdev_subsys
: 全局的字符设备子系统结构体subsystem_init()
: 初始化内核子系统的函数
第10行:初始化字符设备映射
c
cdev_map = kobj_map_init(base_probe, &cdev_subsys);
cdev_map
: 全局的字符设备映射结构指针kobj_map_init()
: 初始化内核对象映射的函数base_probe
: 基础探测函数指针&cdev_subsys
: 字符设备子系统指针
详细机制分析
字符设备管理架构
字符设备管理 cdev_map设备映射 cdev_subsys子系统 设备号哈希表 base_probe探测函数 并发保护机制 读写信号量rwsem 内核对象管理 快速设备号查找 处理未知设备 线程安全操作
设备号映射原理
c
// 字符设备注册过程:
1. 驱动调用register_chrdev()
2. 在cdev_map哈希表中添加条目
3. 设备号 → 驱动映射被建立
// 设备打开过程:
1. 应用程序open("/dev/xxx")
2. VFS根据设备号在cdev_map中查找
3. 找到对应的字符设备驱动
4. 调用驱动的open方法
内核子系统的初始化subsystem_init
c
void subsystem_init(struct subsystem * s)
{
init_rwsem(&s->rwsem);
kset_init(&s->kset);
}
void kset_init(struct kset * k)
{
kobject_init(&k->kobj);
INIT_LIST_HEAD(&k->list);
}
void kobject_init(struct kobject * kobj)
{
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->kset = kset_get(kobj->kset);
}
整体功能
这些函数共同完成了Linux内核子系统的初始化,建立了内核对象管理的基础设施,用于管理字符设备、总线、设备类别等内核子系统
代码逐段解释
subsystem_init
函数
第1行:函数声明
c
void subsystem_init(struct subsystem * s)
- 返回类型 :
void
- 无返回值 - 参数 :
struct subsystem * s
- 要初始化的子系统指针
第2-3行:初始化读写信号量
c
init_rwsem(&s->rwsem);
init_rwsem(&s->rwsem)
: 初始化子系统的读写信号量- 作用: 提供并发保护,允许多个读者或单个写者访问子系统
第4行:初始化kset
c
kset_init(&s->kset);
}
kset_init(&s->kset)
: 调用kset
初始化函数s->kset
: 子系统内嵌的kset
结构
kset_init
函数
第1行:函数声明
c
void kset_init(struct kset * k)
- 参数 :
struct kset * k
- 要初始化的kset
指针
第2-3行:初始化kobject
c
kobject_init(&k->kobj);
kobject_init(&k->kobj)
: 初始化kset
内嵌的kobject
kset
本身也是一个kobject
,具有引用计数和链表管理
第4-5行:初始化链表
c
INIT_LIST_HEAD(&k->list);
}
INIT_LIST_HEAD(&k->list)
: 初始化kset
的对象链表头- 这个链表用于链接该
kset
包含的所有kobject
kobject_init
函数
第1行:函数声明
c
void kobject_init(struct kobject * kobj)
- 参数 :
struct kobject * kobj
- 要初始化的kobject
指针
第2-3行:初始化引用计数
c
kref_init(&kobj->kref);
kref_init(&kobj->kref)
: 初始化kobject
的引用计数kref
结构: 包含atomic_t计数器,用于对象生命周期管理
第4-5行:初始化链表
c
INIT_LIST_HEAD(&kobj->entry);
INIT_LIST_HEAD(&kobj->entry)
: 初始化kobject
的链表节点- 用于将
kobject
链接到父kset
的链表中
第6-7行:处理kset
关联
c
kobj->kset = kset_get(kobj->kset);
}
kobj->kset = kset_get(kobj->kset)
:- 如果
kobject
有关联的kset
,增加该kset
的引用计数 kset_get()
: 增加kset
引用计数并返回kset
指针
- 如果
初始化内核对象映射结构kobj_map_init
c
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe,
struct subsystem *s)
{
struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
struct probe *base = kmalloc(sizeof(struct probe), GFP_KERNEL);
int i;
if ((p == NULL) || (base == NULL)) {
kfree(p);
kfree(base);
return NULL;
}
memset(base, 0, sizeof(struct probe));
base->dev = 1;
base->range = ~0;
base->get = base_probe;
for (i = 0; i < 255; i++)
p->probes[i] = base;
p->sem = &s->rwsem;
return p;
}
函数功能
kobj_map_init
函数用于初始化内核对象映射结构,创建一个设备号到内核对象的映射表。它是字符设备管理的基础设施,用于快速查找设备号对应的设备驱动
代码逐段解释
第1-2行:函数声明
c
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe,
struct subsystem *s)
- 返回类型 :
struct kobj_map *
- 返回初始化的kobj_map
指针,失败返回NULL - 参数 :
kobj_probe_t *base_probe
- 基础探测函数指针struct subsystem *s
- 关联的子系统指针
第3-5行:内存分配
c
struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
struct probe *base = kmalloc(sizeof(struct probe), GFP_KERNEL);
int i;
p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL)
: 分配kobj_map
结构内存base = kmalloc(sizeof(struct probe), GFP_KERNEL)
: 分配基础探测节点内存int i
: 循环计数器
第7-11行:内存分配失败检查
c
if ((p == NULL) || (base == NULL)) {
kfree(p);
kfree(base);
return NULL;
}
- 条件: 检查p或base任一分配失败
- 清理 : 释放可能已分配的内存
kfree(p)
: 释放kobj_map
内存kfree(base)
: 释放probe节点内存
- 返回 :
NULL
表示初始化失败
第13-15行:初始化基础探测节点
c
memset(base, 0, sizeof(struct probe));
base->dev = 1;
base->range = ~0;
基础探测节点设置:
memset(base, 0, sizeof(struct probe))
: 清零整个probe结构base->dev = 1
: 设置设备号为1base->range = ~0
: 设置设备号范围为最大值(全1)
第16行:设置探测函数
c
base->get = base_probe;
base->get = base_probe
: 设置基础探测函数- 当设备号未注册时,会调用此函数进行探测
第17-18行:初始化哈希表数组
c
for (i = 0; i < 255; i++)
p->probes[i] = base;
哈希表初始化:
- 循环: 遍历255个哈希桶(索引0-254)
- 赋值: 每个哈希桶都指向同一个基础探测节点
第19行:设置信号量指针
c
p->sem = &s->rwsem;
p->sem = &s->rwsem
: 设置保护信号量指针- 使用子系统的读写信号量来保护映射表的并发访问
第20行:返回结果
c
return p;
- 返回成功初始化的
kobj_map
指针
详细数据结构分析
kobj_map
结构
struct kobj_map struct probe *probes255 struct rw_semaphore *sem 哈希表数组 并发保护信号量 255个哈希桶 使用子系统的rwsem 每个桶指向probe链表 读写锁保护
probe 结构
c
// 典型的probe结构定义:
struct probe {
struct probe *next; // 下一个探测节点
dev_t dev; // 起始设备号
unsigned long range; // 设备号范围
struct module *owner; // 所属模块
kobj_probe_t *get; // 探测函数指针
int (*lock)(dev_t, void *); // 锁函数
void *data; // 私有数据
};
初始化后的哈希表状态
probes[0] → base (dev=1, range=~0, get=base_probe)
probes[1] → base (dev=1, range=~0, get=base_probe)
probes[2] → base (dev=1, range=~0, get=base_probe)
...
probes[254] → base (dev=1, range=~0, get=base_probe)
实际使用场景
在字符设备初始化中
c
// chrdev_init中的调用:
cdev_map = kobj_map_init(base_probe, &cdev_subsys);
// 参数:
- base_probe: 基础探测函数
- &cdev_subsys: 字符设备子系统
设备注册过程
c
// 设备驱动注册时:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
// 在cdev_map中添加probe节点
// 替换对应哈希桶中的base节点
}
设备查找过程
c
// 打开字符设备时:
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
{
// 计算哈希桶索引
// 遍历probe链表查找匹配设备
// 调用get函数获取kobject
}
总结
kobj_map_init
函数建立了Linux设备号管理的核心基础设施:
- 快速查找: 通过哈希表实现设备号到驱动的快速映射
- 动态扩展: 支持运行时的设备注册和注销
- 并发安全: 使用读写信号量保护数据结构
- 错误处理: 完善的资源分配失败处理
- 默认处理: 通过基础探测节点处理未注册设备
这个函数是Linux字符设备驱动框架的基石,为设备文件的打开、操作和关闭提供了高效的查找机制,确保了系统能够正确地将设备号映射到对应的驱动实现