Linux中初始化字符设备子系统chrdev_init的实现

初始化字符设备子系统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;

基础探测节点设置:

  1. memset(base, 0, sizeof(struct probe)): 清零整个probe结构
  2. base->dev = 1: 设置设备号为1
  3. base->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设备号管理的核心基础设施:

  1. 快速查找: 通过哈希表实现设备号到驱动的快速映射
  2. 动态扩展: 支持运行时的设备注册和注销
  3. 并发安全: 使用读写信号量保护数据结构
  4. 错误处理: 完善的资源分配失败处理
  5. 默认处理: 通过基础探测节点处理未注册设备

这个函数是Linux字符设备驱动框架的基石,为设备文件的打开、操作和关闭提供了高效的查找机制,确保了系统能够正确地将设备号映射到对应的驱动实现

相关推荐
秉承初心3 小时前
Linux中Expect脚本和Shell的脚本核心特点解析、以及比对分析和应用场景
linux·运维·服务器·sh·exp
脏脏a3 小时前
【Linux】Linux:sudo 白名单配置与 GCC/G++ 编译器使用指南
linux·运维·服务器
Ahern_4 小时前
崖山数据库安装部署
linux·数据库
BS_Li4 小时前
【Linux系统编程】权限的概念
linux·权限
cellurw4 小时前
Day67 Linux I²C 总线与设备驱动架构、开发流程与调试
linux·c语言·架构
天朝八阿哥4 小时前
Bye~~ win10!
linux·windows
孙同学_4 小时前
【Linux篇】软链接vs硬链接:Linux文件系统中的两种引用机制
linux·运维·服务器
hour_go5 小时前
解决Linux系统中“undeclared identifier“问题的完整指南
linux·运维·服务器
天赐细莲5 小时前
(Linux) WSL 通过 VSCode 连接不执行 profile 问题(登录Shell问题)
linux·运维·vscode