Linux(27)-sysfs虚拟文件系统
核心背景
Linux 2.6 之前,/proc(procfs)原为进程状态接口,却因缺乏规范演变为设备参数、总线拓扑、硬件资源与调试接口的“全局杂物箱”,严重阻碍可维护性与自动化能力。
问题本质
| | |
|---|
| 结构混乱 | /proc/scsi/、/proc/sys/、/proc/bus/ 职责交叉、命名随意 | |
| 格式失范 | | |
| 语义模糊 | 同一文件既读状态又写控制(如 /proc/scsi/scsi) | |
| 维护困难 | 驱动直接向 /proc “硬塞”私有目录(如 /proc/scsi/aic7xxx/) | |
sysfs 的重构逻辑
- 结构统一:所有块设备归于
/sys/block/,并通过符号链接映射至 /sys/bus/scsi/devices/,形成清晰的设备-总线-驱动树状模型。 - 格式规范:严格“单文件单属性”,如
cat /sys/block/sda/device/vendor 直出 ATA,无需解析。 - 语义分离:只读属性(
vendor)与控制触发(/sys/class/scsi_host/host0/scan)物理隔离,权限与用途一目了然。 - 内核托管:驱动仅注册
kobject,目录与属性由内核框架自动生成,彻底终结“各写各的”乱象。
由此,procfs 回归进程与系统统计本职,sysfs 专司硬件拓扑暴露,debugfs 独立承载调试接口——三域分治,奠定现代 Linux 设备管理基石。
案例:无法形成统一硬件拓扑
旧方式(procfs)
- 路径分散且无关联。SCSI 硬盘位于 /proc/scsi/scsi,IDE 硬盘位于 /proc/ide/,PCI 设备位于 /proc/bus/pci/。若要获取系统所有磁盘,必须跨多个路径分别查找。
新方式(sysfs)
- 按统一树状模型归类。所有块设备统一列于 /sys/block/(如 /sys/block/sda),同时硬链接指向底层的物理总线拓扑 /sys/bus/scsi/devices/。
案例:非结构化文本致脚本极易崩溃
旧方式(procfs)
- 执行 cat /proc/scsi/scsi 返回格式复杂的复合文本
Attached devices:
Host: scsi0 Channel: 00 Id: 00 Lun: 00
Vendor: ATA Model: Samsung SSD 860 Rev: 1B2Q
Type: Direct-Access ANSI SCSI revision: 05
- 缺点:自动化脚本必须写复杂的正则表达式切分列。内核一旦多加一个空格,解析就会失效崩溃。
新方式(procfs)
- 严格遵循 “单文件单属性” 规范,直接读取纯文本:
- 获取厂商:cat /sys/block/sda/device/vendor ➔ ATA
- 获取型号:cat /sys/block/sda/device/model ➔ Samsung SSD 860
案例:只读查询与控制写操作混用
旧方式(procfs)
- 读取 /proc/scsi/scsi ➔ 获取设备列表;
- 写入 /proc/scsi/scsi ➔ 动态扫描/添加设备:
echo"scsi add-single-device 0 0 1 0" > /proc/scsi/scsi
- 缺点:单一路径既是输出终端又是控制开关,缺乏清晰的访问权限和功能划分。
新方式(procfs)
- 查看属性读取 vendor 或 model 节点;
echo"0 0 1 0" > /sys/class/scsi_host/host0/scan
案例:驱动开发者各自为政
旧方式(procfs)
- 不同控制器厂商的驱动开发者会在 /proc/scsi/ 下自建子目录(如 /proc/scsi/aic7xxx/0),输出格式随意定,缺乏内核层统一约束。
新方式(procfs)
- 内核统一提供 kobject 底层模型。驱动只需向内核注册设备,目录结构与属性节点均由内核框架自动规范化生成。
sysfs 结构
sysfs(挂载于 /sys)是 Linux 内核在 2.6 版本引入的内存文件系统。它将内核内部的统一设备模型(Unified Device Model)以虚拟目录和文件树的形式呈现在用户态。
目录结构
sysfs 并非随意存放文件,其根目录下的各个子目录分别代表了内核设备模型中的不同抽象维度:
| |
|---|
/sys/devices/ | 物理设备拓扑树(核心)。严格按硬件真实连接关系构建目录层级,体现设备间的父子、兄弟等物理隶属关系;其余子目录中的设备节点多为此处的符号链接。 |
/sys/bus/ | 按总线类型组织(如 pci、usb、i2c)。每个总线子目录下包含 devices/(指向该总线上所有已识别设备的链接)和 drivers/(已注册驱动的目录,含绑定状态与参数接口)。 |
/sys/class/ | 按设备功能抽象分类(如 net/、sound/、block/)。屏蔽底层总线与驱动细节,为用户态提供统一、语义清晰的功能视图,便于跨平台设备管理。 |
/sys/dev/ | 按设备号(主:次)索引。含 block/ 与 char/ 两个子目录,每个条目为指向 /sys/devices/ 对应节点的软链接,支持快速定位设备节点。 |
/sys/module/ | 已加载内核模块信息。每个子目录对应一个模块,包含 parameters/(可调参数)、sections/(内存段)、refcnt(引用计数)等,支持运行时模块状态查询与调试。 |
实现逻辑
sysfs 目录树的组织与动态生成,完全依赖于内核 C 语言中的统一设备模型数据结构:
kobject(目录的实体)
是内核中最基础的对象抽象,对应 struct kobject。它在 sysfs 中表现为一个目录节点,承担对象生命周期管理(通过引用计数)、父子层级组织,并将其名称注册到 sysfs 树中,构成设备模型的骨架。
kset(目录容器与事件集合)
是 kobject 的集合容器(struct kset),通常映射为 sysfs 中的分类目录(如 /sys/bus、/sys/class)。它统一管理同类型 kobject,并作为热插拔事件(uevent)的广播源,驱动 udev 等用户态服务响应设备增删。
attribute(文件的实体)
对应 sysfs 目录下的单个文件(struct attribute),严格遵循“单文件单属性”原则——每个文件仅暴露一个可读写值。其读写行为由 struct sysfs_ops 绑定:
show():用户执行 cat 时调用,将内核字段序列化为字符串输出;store():用户执行 echo 时调用,解析输入字符串并更新内核状态或触发硬件操作。
属性文件是内核与用户空间交互的核心接口,兼具简洁性与可控性。
用户态读写交互原理
以读取网卡 MAC 地址 cat /sys/class/net/eth0/address 为例:
[用户态 cat 命令]
│ 触发 read() 系统调用
▼
[VFS 虚拟文件系统] ➔ 识别挂载点为 sysfs
│
▼
[sysfs 文件系统] ➔ 找到 eth0 目录对应的 kobject 及 address 属性结构体
│
▼
[执行回调 show()] ➔ 调用驱动定义的 address_show() 函数
│
▼
[内核结构体] ➔ 读取网卡结构体 struct net_device 中的 dev_addr 并返回字符串
sysfs 与设备树的关系
设备树(Device Tree)是 Bootloader 启动阶段传递给内核的静态硬件描述文件(输入数据),而 sysfs 是内核运行起来后向用户态呈现的动态内存文件系统(输出/交互视图)。
简单来说:设备树告诉内核“板子上有哪些硬件”,sysfs 则是内核把这些硬件组织好后“展示给用户看”。
| | |
|---|
| 本质 | 静态硬件描述文件,编译为二进制 .dtb 镜像,与内核镜像分离 | 运行时虚拟文件系统,完全驻留内存(RAM),无磁盘存储开销 |
| 生效阶段 | 系统启动极早期:由 Bootloader(如 U-Boot)加载并传递给内核,在内核初始化前解析 | 内核完成基础初始化、驱动模块加载并注册设备后动态构建 |
| 流向与作用 | 下→上:向内核“声明”硬件拓扑与资源(如寄存器基地址、中断号、GPIO 引脚编号、时钟频率等),实现平台无关的硬件抽象 | 上→下:为用户空间提供标准化、层次化的接口(如 cat /sys/class/leds/red/brightness 查状态,echo 1 > /sys/class/leds/red/brightness 控制行为),支持动态读写与热插拔感知 |
设备树解耦了内核代码与硬件细节,提升可移植性;sysfs 则是内核与用户空间交互的关键桥梁,强调实时性与可操作性。二者协同:设备树让内核“知道硬件长什么样”,sysfs 让用户“能用硬件做什么”。
两者的三大核心关联
设备树在 sysfs 中的镜像导出
- 内核加载
.dtb 文件后,将完整设备树结构映射至 /sys/firmware/devicetree/base/:每个设备树节点对应一个子目录,每个属性(property)以只读文件形式暴露,便于用户态直接读取硬件拓扑与配置。
设备树驱动 sysfs 设备节点的自动创建
- OF 子系统解析
compatible 等属性(如 "vendor,my-sensor"),动态实例化 struct platform_device 或 struct i2c_client;设备模型据此注册设备,触发 sysfs 在 /sys/bus/platform/devices/ 或 /sys/devices/ 下自动生成对应目录及标准属性(如 name、uevent)。
静态描述与动态控制的协同分工
设备树仅声明硬件静态信息(如 gpio = <&gpio1 15 GPIO_ACTIVE_HIGH>);驱动读取该信息完成资源申请与初始化;成功后,驱动在 /sys/class/leds/red/ 等路径创建可写接口(如 brightness),实现用户态对硬件状态的实时控制。
该链路完整串联了 设备树描述 → 内核设备实例化 → sysfs 节点生成 → 用户态接口暴露,形成从固件配置到应用交互的闭环,兼顾声明式配置的简洁性与运行时控制的灵活性。
[DTS 硬件描述] ──(U-Boot 传入 DTB)──► [内核 OF 框架解析]
│
┌────────────────────────┴────────────────────────┐
▼ ▼
生成镜像(仅供查阅) 动态创建内核 Device 对象
│ │
▼ ▼
`/sys/firmware/devicetree/base/` `/sys/bus/platform/devices/`
│
匹配 Driver 驱动程序
│
▼
暴露用户态控制/状态属性
│
▼
`/sys/class/xxx/attribute`