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 device、struct bus_type)。它的主要职责是:
- 引用计数:管理对象的生命周期,防止被提前释放
- 父子关系:通过 parent 指针形成层次结构
- sysfs 表示:每个 kobject 在 sysfs 中对应一个目录
- 热插拔事件:当 kobject 创建或销毁时,可以触发 uevent
我曾经在调试一个 USB 摄像头驱动时,遇到过 kobject 引用计数泄漏的问题。每次插拔摄像头,/sys 下就多一个僵尸目录,怎么都删不掉。后来发现是 kobject 的 put 和 get 没配对。嗯,这里要注意:kobject 的引用计数必须严格对称,否则就是内存泄漏。
4.2.2 kset:对象的集合与归属
kset 可以理解为「一群 kobject 的集合」。它本身也是一个 kobject,所以它也能在 sysfs 中创建目录。
kset 的作用有两个:
- 归属管理:同一个 kset 下的 kobject,共享同一个父目录
- 热插拔过滤:kset 可以注册 uevent 处理函数,统一处理子对象的插拔事件
举个例子,/sys/class 下的每个子目录(比如 tty、input)其实就是一个 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。因为只有定义了 show 和 store,用户态才能 cat 和 echo。
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、属性文件以及设备模型四大组件之间的关系:
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