第九十二章:sysfs
斩灵期涉及内核源码:
一
林小源离开 procfs 的领地,沿着一条由金属光泽铺成的道路继续前行。道路两旁排列着整齐的建筑,每一栋都挂着铭牌——"PCI 总线"、"USB 控制器"、"网络设备"、"块设备"。
一个穿着工装的身影站在路口,胸前别着一枚徽章,上面刻着"kobject"。
"欢迎来到设备之城。"那人的声音干练利落,"我叫 sysfs。你刚才在 procfs 那里看到的,是进程和内核的信息。而我这里,全是设备。"
林小源环顾四周,发现每栋建筑都有相同的结构——门口挂着设备名,门内是一排排整齐的文件:size、、ro、、、。
"每个设备都对应一个目录?"林小源问。
"准确说,每个 kobject 对应一个目录。"sysfs 敲了敲胸前的徽章,"kobject 是 Linux 设备模型的核心。设备、驱动、总线——它们在内核中都是 kobject。我根据 kobject 的层级关系,自动生成目录树。"
"不过 sysfs 不是普通存储。"sysfs 指向城门上的匾额,"这里的文件多数是属性视图,背后是内核对象的 与 。它们应该简洁、可解析,常见规则是一文件一值或一组同类值。把复杂二进制协议塞进 sysfs,就像把整本功法刻在门牌上。"
"那目录里的文件呢?"
"那些是 kobject 的属性。"sysfs 指着一个文件说,"比如 /sys/block/sda/size,它对应一个 结构体,里面有 函数和 函数。读取时调用 ,写入时调用 。"
林小源伸手去读 size 文件,一串数字浮现在眼前:1953525168。
"这是 sda 硬盘的扇区数。"sysfs 说,"差不多 931GB。"
金城初试
sysfs 属性文件读取和写入时,背后通常对应哪两个回调?
二
林小源跟着 sysfs 走进设备之城的深处。他注意到,有些设备的目录下还有子目录——比如 /sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda。
"这个路径好长。"林小源说。
"那是设备的拓扑路径。"sysfs 解释道,"从 PCI 总线到 ATA 控制器,再到 SCSI 目标,最后到块设备——每一层都是一个 kobject,每一层都有自己的目录。这就是设备树的真实结构。"
"那 /sys/class/net/eth0/ 呢?那里的路径短多了。"
"那是另一种组织方式。"sysfs 说,"/sys/devices/ 按物理拓扑组织,/sys/class/ 按设备类型组织,/sys/bus/ 按总线类型组织。同一设备在不同目录下有不同的入口,但它们都指向同一个 kobject。"
林小源恍然大悟:"就像同一个人,在公司按部门分类,在通讯录按姓名分类——但都是同一个人。"
"很好的比喻。"sysfs 点头。
"这些入口很多是 symlink。"sysfs 继续说,"/sys/bus/pci/devices 里的设备会指向 /sys/devices/... 的真实物理位置,driver 目录也会反向列出绑定的设备。VFS 负责路径和 inode/dentry,sysfs 负责把 kobject 层级投影成目录项。它不像 ext4 那样把 dentry 存到磁盘;这些目录来自内核对象当下的生命状态。"
拓扑之试
sysfs 中按物理拓扑组织所有设备的主目录是什么?
三
林小源正要离开,忽然看到一道光从远处飞来——一个身穿蓝衣的身影,手持一块发光的芯片。
"那是 udev。"sysfs 说,"用户空间的设备管理器。"
udev 落地后,对 sysfs 抱拳:"又有新设备插入了。我在你的目录里看到了新的 kobject,需要创建对应的设备节点。"
"没问题。"sysfs 打开一个目录,"这是新设备的信息——设备类型、主设备号、次设备号,都在这里。"
udev 扫描了一遍信息,手指在空中划出一个符号——/dev/sdb1,一个设备节点凭空出现。
林小源看得目瞪口呆:"设备节点是这样创建的?"
"是的。"udev 说,"当设备插入时,内核在 sysfs 中创建对应的 kobject 目录,然后发送 uevent 通知。我收到通知后,读取 sysfs 中的信息,根据规则创建设备节点。整个过程都是自动的。"
"所以 sysfs 是你和内核之间的桥梁?"
"对。"udev 说,"sysfs 提供信息,我负责行动。没有 sysfs,我就不知道设备长什么样。"
sysfs 微微点头:"我是设备模型的镜子——内核里有什么设备,我就映射出什么目录。"
"镜子也有边界。"sysfs 说,"内核 ABI 一旦暴露给用户空间,就很难随意改变。sysfs 的路径、属性名、取值格式都可能被脚本和系统服务依赖。驱动作者添加属性时,必须把它当成用户空间 ABI,而不是调试 printf。临时调试该去 debugfs,配置型对象可以考虑 configfs,不要把所有东西都塞给我。"
ABI 之试
sysfs 暴露给用户空间的路径和属性格式,一旦发布通常应被视为什么接口?
/*
* sysfs 的结构:
*
* /sys/
* ├── block/ — 块设备
* ├── bus/ — 总线类型
* ├── class/ — 设备类
* ├── devices/ — 所有设备
* ├── firmware/ — 固件接口
* ├── fs/ — 文件系统
* ├── kernel/ — 内核信息
* ├── module/ — 内核模块
* └── power/ — 电源管理
*
* sysfs vs procfs:
* procfs — 进程和内核信息
* sysfs — 设备和驱动信息
*
* sysfs 的特点:
* - 一个文件一个值
* - 每个目录对应一个 kobject
* - 自动生成
*/
printf("=== sysfs — 设备模型的文件系统 ===\n\n");
printf("sysfs 目录结构:\n");
printf(" /sys/\n");
printf(" ├── block/ 块设备\n");
printf(" │ └── sda/\n");
printf(" │ ├── size\n");
printf(" │ └── queue/\n");
printf(" ├── bus/ 总线类型\n");
printf(" │ ├── pci/\n");
printf(" │ └── usb/\n");
printf(" ├── class/ 设备类\n");
printf(" │ ├── net/\n");
printf(" │ └── block/\n");
printf(" ├── devices/ 所有设备\n");
printf(" ├── firmware/ 固件接口\n");
printf(" ├── kernel/ 内核信息\n");
printf(" ├── module/ 内核模块\n");
printf(" └── power/ 电源管理\n\n");
printf("--- 设备信息示例 ---\n");
printf("/sys/block/sda/:\n");
printf(" size: 1953525168 (扇区数)\n");
printf(" removable: 0\n");
printf(" ro: 0 (只读标志)\n\n");
printf("/sys/class/net/eth0/:\n");
printf(" address: 00:11:22:33:44:55\n");
printf(" mtu: 1500\n");
printf(" speed: 1000 (Mbps)\n\n");
printf("--- kobject 与 sysfs ---\n");
printf("每个 kobject 对应一个目录:\n");
printf(" struct kobject {\n");
printf(" char *name; // 目录名\n");
printf(" ... // 其他字段\n");
printf(" };\n\n");
printf("kobject 的属性对应文件:\n");
printf(" struct kobj_attribute {\n");
printf(" char *name; // 文件名\n");
printf(" ssize_t (*show)(); // 读取函数\n");
printf(" ssize_t (*store)(); // 写入函数\n");
printf(" };\n\n");
printf("--- sysfs 的用途 ---\n");
printf("1. 查看设备信息\n");
printf(" cat /sys/block/sda/size\n\n");
printf("2. 修改设备参数\n");
printf(" echo 1 > /sys/class/leds/led0/brightness\n\n");
printf("3. udev 规则\n");
printf(" 根据 sysfs 信息创建设备节点\n");#include <stdio.h>
/*
* sysfs 的结构:
*
* /sys/
* ├── block/ — 块设备
* ├── bus/ — 总线类型
* ├── class/ — 设备类
* ├── devices/ — 所有设备
* ├── firmware/ — 固件接口
* ├── fs/ — 文件系统
* ├── kernel/ — 内核信息
* ├── module/ — 内核模块
* └── power/ — 电源管理
*
* sysfs vs procfs:
* procfs — 进程和内核信息
* sysfs — 设备和驱动信息
*
* sysfs 的特点:
* - 一个文件一个值
* - 每个目录对应一个 kobject
* - 自动生成
*/
int main() {
printf("=== sysfs — 设备模型的文件系统 ===\n\n");
printf("sysfs 目录结构:\n");
printf(" /sys/\n");
printf(" ├── block/ 块设备\n");
printf(" │ └── sda/\n");
printf(" │ ├── size\n");
printf(" │ └── queue/\n");
printf(" ├── bus/ 总线类型\n");
printf(" │ ├── pci/\n");
printf(" │ └── usb/\n");
printf(" ├── class/ 设备类\n");
printf(" │ ├── net/\n");
printf(" │ └── block/\n");
printf(" ├── devices/ 所有设备\n");
printf(" ├── firmware/ 固件接口\n");
printf(" ├── kernel/ 内核信息\n");
printf(" ├── module/ 内核模块\n");
printf(" └── power/ 电源管理\n\n");
printf("--- 设备信息示例 ---\n");
printf("/sys/block/sda/:\n");
printf(" size: 1953525168 (扇区数)\n");
printf(" removable: 0\n");
printf(" ro: 0 (只读标志)\n\n");
printf("/sys/class/net/eth0/:\n");
printf(" address: 00:11:22:33:44:55\n");
printf(" mtu: 1500\n");
printf(" speed: 1000 (Mbps)\n\n");
printf("--- kobject 与 sysfs ---\n");
printf("每个 kobject 对应一个目录:\n");
printf(" struct kobject {\n");
printf(" char *name; // 目录名\n");
printf(" ... // 其他字段\n");
printf(" };\n\n");
printf("kobject 的属性对应文件:\n");
printf(" struct kobj_attribute {\n");
printf(" char *name; // 文件名\n");
printf(" ssize_t (*show)(); // 读取函数\n");
printf(" ssize_t (*store)(); // 写入函数\n");
printf(" };\n\n");
printf("--- sysfs 的用途 ---\n");
printf("1. 查看设备信息\n");
printf(" cat /sys/block/sda/size\n\n");
printf("2. 修改设备参数\n");
printf(" echo 1 > /sys/class/leds/led0/brightness\n\n");
printf("3. udev 规则\n");
printf(" 根据 sysfs 信息创建设备节点\n");
return 0;
}道藏笔记
内核启示
sysfs 是设备模型的文件系统表示,专门暴露设备、驱动、总线的信息。跟 procfs 不同,procfs 看进程和内核,sysfs 看设备和驱动——两扇不同的窗户。
sysfs 的核心是 kobject。Linux 设备模型中,设备、驱动、总线在内核中都是 kobject,每个 kobject 对应 sysfs 中的一个目录,kobject 的属性对应目录中的文件。一个文件一个值,读取时调用 show 函数,写入时调用 store 函数——简洁明了。
目录的组织方式有好几种:/sys/devices/ 按物理拓扑,/sys/class/ 按设备类型,/sys/bus/ 按总线类型。同一设备在不同目录下有不同的入口,但都指向同一个 kobject。就像同一个人,在公司按部门分,在通讯录按姓名分。
udev 也靠 sysfs 活着——设备插入时,内核在 sysfs 中创建 kobject 目录,发送 uevent,udev 收到通知后读取 sysfs 信息,自动创建设备节点。没有 sysfs,udev 就是个瞎子。
sysfs 还是用户空间 ABI。属性名、路径和格式被发布后就要谨慎维护;临时调试不该塞进 sysfs,debugfs/configfs 各有自己的用途。
sysfs 之试
设备模型把内核对象层级暴露给用户空间时,本章对应的伪文件系统叫什么?