Linux设备模型基础:kobject、kset、sysfs文件系统

说实话,我刚接触Linux设备模型那会儿,也是一头雾水。什么kobject、kset,听着就像天书。但后来我慢慢发现,这东西说白了就是内核用来管理设备的一套“户口本系统”。你想想看,一个系统里插了那么多设备,CPU怎么知道谁是谁?怎么跟它们通信?设备模型就是干这个的。

今天咱们就聊聊这个基础中的基础——kobject、kset和sysfs。我保证,看完这一章,你再看内核里的设备代码,会感觉亲切很多。

1. 为什么需要设备模型?

早期的Linux内核,设备管理其实挺乱的。每个驱动自己管自己的,没有统一规范。后来Linus他们觉得不行,得搞个统一的框架。于是就有了设备模型。

设备模型的核心目标就三个:

  • 统一表示:所有设备都用同一种方式描述
  • 层次化管理:设备之间有父子关系,比如USB控制器下面挂USB设备
  • 用户空间可见:通过sysfs,你在用户态也能看到设备树

我在项目中遇到过一个问题:一个嵌入式板子上,两个驱动都声称控制了同一个GPIO。查了半天,就是因为没有用设备模型来管理资源。从那以后,我写驱动必先搭好设备模型框架。

2. kobject:设备模型的最小单元

kobject,全称是kernel object。它是设备模型里最小的“砖块”。你可以把它理解成每个设备在内核里的“身份证”。

看下它的结构体定义:

struct kobject {
    const char      *name;        // 名字,比如"ttyS0"
    struct list_head entry;       // 链表节点,用于挂到kset里
    struct kobject   *parent;     // 父对象,形成层次结构
    struct kset      *kset;       // 所属的kset
    struct kobj_type *ktype;      // 类型,决定了sysfs里的行为
    struct kernfs_node *sd;       // sysfs目录项
    struct kref       kref;       // 引用计数
    unsigned int state_initialized:1;
    unsigned int state_in_sysfs:1;
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1;
};

这里我重点说几个字段:

  • name:就是你在sysfs里看到的目录名
  • parent:指向父对象。比如一个USB键盘,它的parent就是USB控制器
  • kref:引用计数。这个很重要,防止对象被提前释放
我的小技巧:调试kobject时,我习惯在/sys/kernel/下看看有哪些kobject。比如/sys/kernel/uevent_helper,就是个典型的kobject示例。

3. kset:把kobject组织起来

kset,就是kobject的集合。你可以把它理解成一个“文件夹”,里面放了一堆同类型的kobject。

举个例子:

struct kset {
    struct list_head list;        // 链表头,挂载所有kobject
    spinlock_t      list_lock;
    struct kobject   kobj;        // kset本身也是一个kobject
    const struct kset_uevent_ops *uevent_ops;
};

注意看,kset里面包含了一个kobject成员。这说明什么?说明kset本身也是kobject,也能在sysfs里创建目录。这就是设备模型的精妙之处——递归统一。

我记得有一次调试一个PCIe驱动的热插拔问题。设备插上去后,sysfs里没有出现对应的目录。查到最后,发现是kset的uevent_ops没注册好,导致内核没发uevent事件给用户空间。嗯,这种坑踩过一次就记住了。

4. sysfs:用户空间看内核的窗口

sysfs是一个虚拟文件系统,通常挂载在/sys目录下。它把内核里的kobject层次结构,映射成了文件系统的目录树。

典型的sysfs目录结构:

/sys/
├── block/        # 块设备
├── bus/          # 总线
├── class/        # 设备类
├── dev/          # 设备号
├── devices/      # 所有设备
├── firmware/     # 固件
├── fs/           # 文件系统
├── hypervisor/   # 虚拟机
├── kernel/       # 内核参数
├── module/       # 内核模块
└── power/        # 电源管理

我个人觉得,sysfs最牛的地方在于:你在用户态cat一个文件,就能触发内核里的回调函数。比如:

# 查看CPU信息
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq

# 修改LED亮度
echo 255 > /sys/class/leds/led0/brightness

这背后就是kobject的sysfs操作接口在起作用。

5. 设备模型的层次结构

说了这么多,咱们画张图来总结一下。下面这张SVG图,展示了kobject、kset和sysfs之间的关系:

Linux设备模型层次结构 用户空间 (User Space) 应用程序通过 sysfs 文件系统访问设备信息 sysfs 文件系统 /sys/bus/ /sys/devices/ /sys/class/ 等目录 内核对象层 (Kernel Objects) kset (对象集合) 如:bus_kset, class_kset kobject (内核对象) 如:每个设备一个kobject kobj_type (类型) 定义sysfs操作接口 硬件设备层 (Hardware Devices) PCI设备、USB设备、平台设备、I2C设备等 包含 关联

从这张图可以看得很清楚:用户空间通过sysfs访问内核对象,内核对象通过kobject/kset组织成层次结构,最终映射到实际的硬件设备。

6. 实战:创建一个简单的kobject

光说不练假把式。咱们写个简单的内核模块,创建一个kobject:

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>

static struct kobject *my_kobj;

static int __init my_kobject_init(void)
{
    int ret;
    
    // 在/sys/kernel/下创建一个名为"my_kobject"的目录
    my_kobj = kobject_create_and_add("my_kobject", kernel_kobj);
    if (!my_kobj) {
        pr_err("Failed to create kobject\n");
        return -ENOMEM;
    }
    
    pr_info("kobject created at /sys/kernel/my_kobject\n");
    return 0;
}

static void __exit my_kobject_exit(void)
{
    kobject_put(my_kobj);
    pr_info("kobject removed\n");
}

module_init(my_kobject_init);
module_exit(my_kobject_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple kobject example");

编译加载后,你会在/sys/kernel/下看到my_kobject目录。虽然里面是空的,但这就是设备模型的基础。

核心要点:kobject_create_and_add() 做了两件事——创建kobject,然后把它注册到sysfs。对应的销毁函数是kobject_put(),它会减少引用计数,当计数为0时自动释放。

7. 避坑指南

我这些年踩过的坑,分享几个给你:

  • 引用计数泄漏:每次调用kobject_get(),必须对应一个kobject_put()。我曾经漏了一次,结果那个kobject占用的内存一直释放不了。
  • 命名冲突:同一个parent下,kobject的名字必须唯一。如果你创建了两个同名的kobject,第二个会失败。
  • sysfs权限:默认创建的sysfs文件是root只读的。如果你想让普通用户也能读写,记得设置合适的权限位。
特别注意:不要在中断上下文里创建或销毁kobject!因为kobject_add()可能会睡眠。我刚开始写驱动时犯过这个错,结果内核直接panic了。

8. 小结

设备模型这东西,说白了就是内核用来管理设备的“骨架”。kobject是骨头,kset是关节,sysfs是皮肤——用户通过皮肤能看到骨架的样子。

我个人觉得,理解设备模型最好的方法就是多去/sys目录下逛逛。看看每个目录对应什么设备,想想它们之间的父子关系。看得多了,自然就懂了。

下一章咱们会深入platform驱动,看看设备模型怎么跟具体的硬件驱动结合起来。到时候你会发现,今天学的这些基础,全都能用上。


可可精品资料库