Skip to content

第九十二章:sysfs

斩灵期

涉及内核源码:

林小源离开 procfs 的领地,沿着一条由金属光泽铺成的道路继续前行。道路两旁排列着整齐的建筑,每一栋都挂着铭牌——"PCI 总线"、"USB 控制器"、"网络设备"、"块设备"。

一个穿着工装的身影站在路口,胸前别着一枚徽章,上面刻着"kobject"。

"欢迎来到设备之城。"那人的声音干练利落,"我叫 sysfs。你刚才在 procfs 那里看到的,是进程和内核的信息。而我这里,全是设备。"

林小源环顾四周,发现每栋建筑都有相同的结构——门口挂着设备名,门内是一排排整齐的文件:sizero

"每个设备都对应一个目录?"林小源问。

"准确说,每个 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 暴露给用户空间的路径和属性格式,一旦发布通常应被视为什么接口?

答对后才能继续滑动和进入下一章。
c
/*
 * 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");

道藏笔记

内核启示

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 之试

设备模型把内核对象层级暴露给用户空间时,本章对应的伪文件系统叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道