3. sysfs与设备模型
在Linux设备驱动开发中,sysfs是用户空间观察和操作内核设备模型的主要窗口。它并不是一个普通的文件系统,而是内核对象(kobject)在用户空间的一种映射机制。理解sysfs的结构,必须从kobject、kset、attribute、uevent以及Class这几个核心概念入手,它们共同构成了Linux设备模型的骨架。
一、sysfs文件系统结构:内核对象的用户空间视图
sysfs通常挂载在/sys目录下,其目录结构并非随意组织,而是严格对应内核中的设备模型层次。顶层目录包括/sys/devices、/sys/bus、/sys/class、/sys/block、/sys/module等。其中/sys/devices是设备模型的真实树状结构,其他目录大多是指向该树中节点的符号链接或视图。
sysfs中的每一个目录都对应一个kobject,每一个文件都对应一个attribute。用户通过读写这些文件,实际上是在调用内核中注册的show/store回调函数。这种设计使得sysfs成为驱动调试、参数配置和状态查询的重要接口。
二、kobject与kset:设备模型的基石
kobject是Linux设备模型中最基本的对象单元,它本身并不代表具体设备,而是为设备对象提供引用计数、命名、父子关系和sysfs目录映射等通用能力。每个kobject在sysfs中对应一个目录,目录名由kobject的name字段决定。kobject通过kobject_init()初始化,通过kobject_add()加入设备模型层次,通过kobject_put()减少引用计数并在归零时释放。
kset是kobject的集合,用于将同一类kobject组织在一起,并作为它们的父对象。kset本身也内嵌一个kobject,因此kset在sysfs中同样表现为一个目录。例如,/sys/bus/pci对应一个kset,其下挂载了所有PCI总线相关的kobject。kset的主要作用是提供统一的父目录和子系统级别的属性管理。
/* 典型 kobject 与 kset 关系示意 */
struct kset {
struct list_head list; /* 链接所有属于该 kset 的 kobject */
spinlock_t list_lock;
struct kobject kobj; /* kset 自身的 kobject */
const struct kset_uevent_ops *uevent_ops;
};
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs 目录项 */
struct kref kref; /* 引用计数 */
};
三、attribute:sysfs中的文件接口
attribute是kobject在sysfs中暴露的文件,用户通过读写这些文件与内核交互。每个attribute包含名称、权限和show/store回调函数。show函数在用户读取文件时被调用,store函数在用户写入文件时被调用。attribute通常通过sysfs_create_file()或sysfs_create_group()注册到kobject的sysfs目录下。
在驱动开发中,attribute常用于暴露设备状态、配置参数和调试信息。例如,一个LED驱动可能通过brightness属性文件让用户设置亮度,通过max_brightness属性文件报告最大亮度。attribute的设计使得sysfs成为驱动与用户空间之间轻量级的交互通道。
四、uevent机制:设备热插拔的事件通知
uevent是内核向用户空间发送设备状态变化事件的机制。当设备被添加、移除或状态改变时,内核会通过uevent通知用户空间的udev或mdev等设备管理器。uevent的内容包括设备路径、子系统名称、动作类型(add/remove/change)以及设备特定的环境变量。
uevent的触发流程通常为:内核调用kobject_uevent(),该函数根据kobject的kset和ktype收集uevent_ops,生成环境变量,然后通过netlink套接字广播到用户空间。用户空间的udev接收到事件后,根据规则文件在/dev下创建设备节点或执行其他动作。
# 查看内核发送的 uevent 示例
$ udevadm monitor --kernel --property
KERNEL[1234.567] add /devices/virtual/block/loop0 (block)
ACTION=add
DEVNAME=loop0
DEVPATH=/devices/virtual/block/loop0
SUBSYSTEM=block
...
五、Class与sysfs的关系
Class是Linux设备模型中的设备类概念,用于将不同总线、不同物理设备的同类功能统一到/sys/class/下。例如,所有网络接口都出现在/sys/class/net/下,所有输入设备都出现在/sys/class/input/下。Class并不改变设备在/sys/devices中的真实位置,而是提供一种按功能分类的视图。
Class与kobject、kset的关系是:Class内部使用kset来组织,Class下的每个设备对应一个kobject,并通过符号链接指向/sys/devices中的真实设备目录。这样,用户空间既可以通过/sys/devices按物理拓扑查找设备,也可以通过/sys/class按功能类别查找设备。
| 概念 | 作用 | sysfs表现 | 典型API |
|---|---|---|---|
| kobject | 设备模型基本单元,提供引用计数和层次关系 | 一个目录 | kobject_init/add/put |
| kset | 同类kobject的集合,作为父对象 | 一个目录,下挂多个kobject | kset_create_and_add |
| attribute | 暴露内核数据为文件接口 | 目录下的文件 | sysfs_create_file/group |
| uevent | 设备状态变化事件通知 | 不直接表现为文件,通过netlink发送 | kobject_uevent |
| Class | 按功能分类的设备视图 | /sys/class/xxx/ 下的符号链接 | class_create/device_create |
六、案例与误区复盘
假设我们编写一个简单的字符设备驱动,使用class_create()创建一个名为myclass的设备类,然后通过device_create()创建两个设备实例。此时在/sys/class/myclass/下会出现两个符号链接,分别指向/sys/devices/virtual/myclass/mydev0和mydev1。同时,内核会发送uevent,udev根据规则在/dev下创建对应的设备节点。
常见误区包括:一是认为sysfs中的目录都是真实设备,实际上很多是符号链接;二是混淆/sys/class和/sys/devices的关系,前者是功能视图,后者是物理拓扑;三是在attribute的store回调中直接操作硬件寄存器而不做并发保护,导致竞态;四是忘记在驱动卸载时调用class_destroy()和device_destroy(),造成sysfs残留和引用泄漏。
总结来说,sysfs是Linux设备模型在用户空间的映射,kobject是基本单元,kset是集合,attribute是文件接口,uevent是事件通道,Class是功能视图。掌握这五者的关系,就掌握了Linux设备模型的核心脉络,也为后续编写规范的设备驱动打下坚实基础。