4、sysfs文件系统:sysfs架构、kobject与kset、属性文件创建、设备模型关联

4.1 sysfs 到底是什么?

说实话,很多做驱动开发的朋友,一开始都被 sysfs 搞晕过。

它看起来就是个虚拟文件系统,挂在 /sys 目录下。但它的本质,其实是内核对象模型(kobject)在用户态的一个「投影窗口」。

我个人习惯把 sysfs 理解为:内核把它的内部数据结构,以文件和目录的形式,暴露给了用户态。你想想看,如果没有 sysfs,你想查看一个设备的电源状态、修改一个驱动的参数,都得写 ioctl 或者 procfs 的复杂逻辑。有了 sysfs,一个 echo 命令就搞定了。

核心一句话:sysfs 是内核对象(kobject)的用户态视图。每个 kobject 对应一个目录,每个属性(attribute)对应一个文件。

4.2 kobject 与 kset:内核对象模型的基础

要理解 sysfs,必须先搞懂 kobject 和 kset。这两个结构体,是 Linux 设备模型大厦的「砖块」和「脚手架」。

4.2.1 kobject:最小的内核对象单元

kobject 本身并不大,但它嵌入在各种更大的结构体里(比如 struct devicestruct bus_type)。它的主要职责是:

  • 引用计数:管理对象的生命周期,防止被提前释放
  • 父子关系:通过 parent 指针形成层次结构
  • sysfs 表示:每个 kobject 在 sysfs 中对应一个目录
  • 热插拔事件:当 kobject 创建或销毁时,可以触发 uevent

我曾经在调试一个 USB 摄像头驱动时,遇到过 kobject 引用计数泄漏的问题。每次插拔摄像头,/sys 下就多一个僵尸目录,怎么都删不掉。后来发现是 kobject 的 putget 没配对。嗯,这里要注意:kobject 的引用计数必须严格对称,否则就是内存泄漏。

4.2.2 kset:对象的集合与归属

kset 可以理解为「一群 kobject 的集合」。它本身也是一个 kobject,所以它也能在 sysfs 中创建目录。

kset 的作用有两个:

  1. 归属管理:同一个 kset 下的 kobject,共享同一个父目录
  2. 热插拔过滤:kset 可以注册 uevent 处理函数,统一处理子对象的插拔事件

举个例子,/sys/class 下的每个子目录(比如 ttyinput)其实就是一个 kset。所有注册到 tty 类的设备,都会自动出现在 /sys/class/tty/ 下面。

我的经验:写驱动时,如果你想让设备在 /sys/class/xxx 下出现,记得用 class_create() 创建 kset,然后用 device_create() 把设备挂进去。别自己手动创建 kobject,容易踩坑。

4.3 属性文件创建:让用户态能读写内核数据

光有目录还不够,用户态需要能读写数据。这就是属性文件(attribute)的用武之地。

4.3.1 属性的两种类型

类型 结构体 说明
普通属性 struct attribute 只有名字和权限,没有读写方法
设备属性 struct device_attribute 包含 show()store() 回调

实际开发中,我们几乎只用 device_attribute。因为只有定义了 showstore,用户态才能 catecho

4.3.2 创建属性的标准流程

我习惯用宏 DEVICE_ATTR 来定义属性,这样最简洁:

// 定义 show/store 函数
static ssize_t my_attr_show(struct device *dev,
                            struct device_attribute *attr,
                            char *buf)
{
    return sprintf(buf, "%d\n", my_driver_data);
}

static ssize_t my_attr_store(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t count)
{
    sscanf(buf, "%d", &my_driver_data);
    return count;
}

// 用宏创建属性
static DEVICE_ATTR(my_control, 0644, my_attr_show, my_attr_store);

// 在 probe 函数中创建文件
static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int ret;

    ret = device_create_file(dev, &dev_attr_my_control);
    if (ret) {
        dev_err(dev, "Failed to create sysfs file\n");
        return ret;
    }

    return 0;
}

// 在 remove 函数中删除文件
static int my_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    device_remove_file(dev, &dev_attr_my_control);
    return 0;
}

注意:DEVICE_ATTR 宏生成的变量名会自动加上 dev_attr_ 前缀。比如 DEVICE_ATTR(my_control, ...) 生成的变量是 dev_attr_my_control。别写错了,我刚开始就犯过这个低级错误。

4.4 设备模型关联:sysfs 如何串联起整个内核

sysfs 不是孤立的。它和 Linux 设备模型的四大组件紧密关联:

  • bus(总线)/sys/bus/ 下每个子目录对应一种总线(如 pci、spi、i2c)
  • device(设备)/sys/devices/ 下是所有设备的真实拓扑结构
  • driver(驱动)/sys/bus/xxx/drivers/ 下是已注册的驱动
  • class(类)/sys/class/ 下是按功能分类的设备视图

举个例子,一个 USB 键盘在 sysfs 中的路径可能是这样的:

/sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/input/input3/event3

这个路径反映了从 PCI 总线 → USB 控制器 → USB 设备 → 输入子系统 → 事件设备的完整硬件拓扑。你想想看,如果没有 sysfs,你要怎么在用户态获取这个拓扑关系?

4.5 核心架构图:sysfs 与设备模型的关系

下面这张图,是我自己总结的 sysfs 核心架构。它展示了 kobject、kset、属性文件以及设备模型四大组件之间的关系:

sysfs 核心架构图 用户态 cat /sys/class/xxx/yyy | echo 1 > /sys/.../control sysfs 虚拟文件系统 目录 = kobject,文件 = attribute kobject / kset 内核对象模型 引用计数 | 父子关系 | 热插拔事件 | 集合管理 每个 kobject 对应一个 sysfs 目录 Linux 设备模型(四大组件) bus(总线) /sys/bus/ device(设备) /sys/devices/ driver(驱动) /sys/bus/xxx/drivers/ class(类) /sys/class/

4.6 避坑指南:我踩过的 sysfs 的坑

最后,分享几个我实际项目中遇到的教训:

  • 属性文件权限问题:我曾经把 DEVICE_ATTR 的权限写成 0666,结果被安全团队打回。记住:只给最小必要权限,只读用 0444,读写用 0644
  • show() 函数返回值show() 必须返回实际写入 buf 的字节数,不能多也不能少。我见过有人返回 strlen(buf) 但 buf 里是格式化后的字符串,结果 cat 出来多了一堆乱码。
  • 并发访问:多个用户态进程同时 echo 到同一个属性文件,你的 store() 函数能扛住吗?我建议在 store() 里加锁,或者用 atomic_t 保护简单变量。
  • 删除时机:在 remove() 函数中,一定要先删除 sysfs 文件,再释放设备数据。顺序反了,用户态可能读到野指针。

一个小技巧:调试 sysfs 属性时,可以用 udevadm info -a -n /dev/xxx 查看设备的 sysfs 路径和属性列表。这比手动翻 /sys 目录高效多了。

sysfs 看似简单,但它是内核与用户态通信的「最后一公里」。搞懂了 kobject、kset 和属性文件的创建,你就能随心所欲地把内核数据暴露给用户态。嗯,这部分内容就到这里,下一章我们聊聊 debugfs——一个比 sysfs 更灵活、更随意的调试利器。


公众号:蓝海资料掘金营,微信 deep3321