[TOC]
fs/sysfs/fs.c & dir.c 内核对象文件系统(Kernel Object Filesystem) 将内核对象层次结构导出到用户空间
历史与背景
这项技术是为了解决什么特定问题而诞生的?
Sysfs(System Filesystem)的诞生是为了解决一个在早期Linux内核中日益严重的问题:/proc
文件系统的混乱。
/proc
的无序扩张:/proc
文件系统最初设计用于提供关于系统中正在运行的进程的信息(即/proc/[pid]
目录)。然而,由于其便利性,内核开发者开始将各种与进程无关的系统信息、硬件状态和可调参数也塞入/proc
,导致其结构混乱、内容混杂,缺乏统一的逻辑。- 缺乏结构化视图:
/proc
中的信息是平面的、零散的,无法清晰地反映出系统中设备、驱动和总线之间复杂的层次关系和连接关系。例如,你很难从/proc
中直观地看出某个USB设备连接在哪条总线上,以及它正在使用哪个驱动程序。
Sysfs是作为统一设备模型(Unified Device Model),或称为kobject模型,的一部分而被创造出来的。它的核心目标是提供一个严格结构化的、基于对象关系的文件系统视图来替代/proc
中混乱的硬件信息部分,让用户空间能够清晰地看到内核是如何组织和看待系统中的每一个组件的。
它的发展经历了哪些重要的里程碑或版本迭代?
Sysfs的发展是Linux 2.6内核系列引入的最重大的变革之一。
- 诞生于2.5开发内核:Sysfs和其背后的kobject模型在Linux 2.5的开发周期中被引入,并随着Linux 2.6.0的正式发布而成为内核的标准部分。这是一个革命性的变化,而不是一个渐进的迭代。
- 清理
/proc
:在sysfs被引入后,社区花费了大量的精力将原本位于/proc
中的硬件和系统设备信息迁移到sysfs中,让/proc
回归其主要关注于进程信息的本职。 - 催生
udev
:Sysfs提供的结构化信息和事件通知机制(通过uevent
)是现代设备管理器(如udev
和systemd-udevd
)能够实现的基础。udev
通过监视sysfs产生的事件,来动态地创建设备节点(/dev
下的文件)、加载驱动模块和执行配置脚本,实现了真正的动态热插拔设备管理。
目前该技术的社区活跃度和主流应用情况如何?
Sysfs是现代Linux系统中一个极其核心、稳定且不可或缺的组成部分。它不是一个经常添加新功能的“活跃”子系统,而是作为内核与用户空间交互的基石被严格维护。
- 主流应用:
- 所有现代Linux发行版都依赖sysfs进行设备管理、电源管理和系统状态监控。
systemd
和udev
:完全基于sysfs来管理设备。- 容器技术(Docker, LXC):通过控制cgroups(它也通过一个类似sysfs的虚拟文件系统暴露)来隔离资源,而cgroups的管理接口与sysfs的设计思想一致。
- 命令行工具:如
lspci
,lsusb
,lsscsi
等工具使用sysfs来获取设备信息并展示其拓扑结构。 - 系统监控和调优:管理员和脚本通过读写sysfs中的文件来监控设备状态(如网络链接速度)或调整参数(如CPU调速器策略、屏幕亮度)。
核心原理与设计
它的核心工作原理是什么?
Sysfs的核心原理是将内核中的kobject(Kernel Object)层次结构直接映射为一个目录和文件的层次结构。它本身是一个虚拟文件系统,不存储任何实际数据。
- kobject核心:在内核中,有一个通用的数据结构
struct kobject
。任何需要被导出到sysfs的内核组件(如struct device
,struct bus_type
,struct driver
)都会内嵌一个kobject
。kobject
提供了引用计数、父子关系指针和名称,从而在内核内存中形成一个巨大的对象树。 - 映射规则:
- 一个kobject映射为一个目录。目录的名称就是kobject的名称。
- kobject之间的父子关系映射为目录的嵌套关系。
- kobject的属性(Attribute)映射为目录中的文件。
- 文件操作的重定向:当用户空间的程序对sysfs中的一个文件进行读(
read
)或写(write
)操作时,sysfs文件系统驱动并不会去磁盘上查找数据。相反,它会:- 找到该文件对应的kobject和
attribute
结构。 attribute
结构中定义了两个函数指针:show()
用于读操作,store()
用于写操作。- 读操作:sysfs调用
show()
函数。这个show
函数是由拥有该kobject的驱动程序实现的。驱动代码会读取硬件寄存器或内核变量,将其格式化为文本字符串,然后通过sysfs返回给用户。 - 写操作:sysfs调用
store()
函数。驱动实现的store
函数会接收来自用户的字符串,解析它,然后根据其内容去修改硬件寄存ators或内核变量。
- 找到该文件对应的kobject和
简单来说,读写sysfs文件,实际上是在远程调用(RPC)内核中特定驱动程序提供的函数。
它的主要优势体现在哪些方面?
- 结构清晰:提供了内核对象模型的严格、一致的视图。
- “一文件一值”原则:每个文件通常只包含一个值,这使得脚本解析和程序处理变得极其简单。
- 统一的交互接口:为用户空间提供了一个统一的、基于文件I/O的API来与各种不同的设备驱动进行交互,替代了大量混乱的
ioctl
调用。 - 事件机制:提供了
uevent
机制,允许内核在对象状态改变(如设备添加/移除)时向用户空间发送通知。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 固化的ABI:一旦一个sysfs接口被加入到内核并发布,它就被视为一个稳定的应用程序二进制接口(ABI)。这意味着它几乎不能被修改或移除,否则会破坏依赖它的用户空间程序。这给内核开发带来了很大的维护负担。
- 基于文本的低效性:所有数据都必须在内核和用户空间之间以文本字符串的形式来回转换,这对于大量数据的传输来说效率很低。
- 缺乏原子性:如果要修改多个相互关联的属性,需要对多个文件进行多次写操作,这期间无法保证原子性。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
- 查看设备拓扑:管理员想知道一个USB硬盘(如
sdb
)物理上插在哪里,可以查看/sys/block/sdb
目录下的符号链接,它会指向devices
目录下该设备的完整路径,清晰地展示其位于哪个USB端口、哪个控制器之下。 - 修改设备参数:用户想要调节笔记本屏幕亮度,可以通过向
/sys/class/backlight/acpi_video0/brightness
文件写入一个数值来实现。 - 控制设备状态:管理员需要临时解绑一个设备与其驱动的绑定关系(例如为了绑定到一个测试驱动),可以通过向
/sys/bus/pci/drivers/my_driver/unbind
文件写入设备的PCI地址来完成。 - 电源管理:向
/sys/power/state
写入mem
可以触发系统休眠。
是否有不推荐使用该技术的场景?为什么?
- 大量数据传输:例如,从一个设备读取连续的数据流(如摄像头视频流)。这种场景应该使用字符设备(character device)接口,因为它更高效。
- 复杂的、事务性的操作:例如,配置一个需要一次性提交多个参数才能生效的硬件设备。这种场景更适合使用
ioctl
或netlink
套接字,因为它们可以传递复杂的二进制数据结构并保证操作的原子性。 - 普通文件存储:sysfs是一个虚拟文件系统,不用于存储用户数据。
对比分析
请将其 与 其他相似技术 进行详细对比。
特性 | sysfs | procfs (/proc ) |
debugfs | configfs |
---|---|---|---|---|
主要目的 | 展示kobject模型,提供稳定的设备和系统结构视图。 | 提供进程信息和一些遗留的、杂项的系统信息。 | 内核调试。专为内核开发者提供一个临时的、不稳定的接口来查看或修改内核数据。 | 让用户空间创建和配置内核对象。 |
ABI稳定性 | 稳定。被视为内核的正式ABI,不能轻易破坏。 | 部分稳定。/proc/[pid] 部分是稳定的,其他文件则稳定性不一。 |
不稳定。没有任何稳定性保证,接口可以随时改变。 | 稳定。作为配置接口,其结构也需要保持稳定。 |
结构 | 非常严格。基于kobject的层次结构,“一文件一值”。 | 混合结构。部分有结构(进程目录),部分是单一文件包含多项非结构化信息。 | 无固定结构。开发者可以随意创建目录和文件。 | 严格。基于目录和属性来管理对象,但操作方向与sysfs相反。 |
数据流向 | 内核 -> 用户空间。主要用于从内核导出(export)信息。 | 内核 -> 用户空间。 | 双向,但无任何规则。 | 用户空间 -> 内核。主要用于从用户空间创建(create)和配置内核对象。 |
生命周期 | 内核对象存在,sysfs中的条目就存在(内核驱动)。 | 进程存在,目录就存在;或内核模块加载后创建。 | 开发者在代码中手动创建和移除。 | 用户空间程序通过mkdir 和rmdir 来创建和销毁内核对象。 |
典型例子 | /sys/devices , /sys/class |
/proc/1234/status , /proc/meminfo |
/sys/kernel/debug/pinctrl |
/sys/kernel/config/usb_gadget |
include/linux/sysfs.h
sysfs_get 函数用于获取 sysfs 节点的引用计数
1 | static inline struct kernfs_node *sysfs_get(struct kernfs_node *kn) |
fs/sysfs/mount.c
sysfs_init 设置和注册 sysfs 文件系统
1 | static struct file_system_type sysfs_fs_type = { |
fs/sysfs/dir.c
sysfs_create_dir_ns 为对象创建带有命名空间标签的目录
1 | /** |
kernfs_get 获取 kernfs_node 的引用计数
1 | /** |
fs/sysfs/file.c
sysfs_create_bin_file: 为kobject创建一个二进制属性文件
此函数是一个通用的内核API,作用是在sysfs
中为一个给定的内核对象(kobject
,表现为目录)创建一个代表二进制数据(binary data)的属性文件。与普通的文本属性文件不同,二进制属性文件允许用户空间程序直接读写大块的、非文本格式的原始数据,例如固件、校准数据或硬件寄存器快照。
这个函数本质上是一个封装器(wrapper),它首先对输入参数进行有效性检查,然后获取该kobject
应该具有的文件所有权信息(用户ID和组ID),最后调用一个更底层的内部函数sysfs_add_bin_file_mode_ns
来完成实际的文件创建工作。
1 | /** |
sysfs_add_bin_file_mode_ns: 创建一个具有指定模式、所有权和命名空间的二进制sysfs文件
该函数是sysfs
文件系统中创建二进制属性文件的底层核心实现。它由上层函数sysfs_create_bin_file
调用,负责处理文件创建的全部逻辑:根据驱动程序提供的回调函数(read
, write
, mmap
)来选择合适的内核文件操作集(kernfs_ops
),准备所有必要的元数据(权限、所有者、大小等),并最终调用更底层的kernfs
(内核文件系统)接口来在sysfs
中真正地创建文件节点。
1 | /* |