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成为驱动调试、参数配置和状态查询的重要接口。

sysfs 与 Linux 设备模型核心关系图 kobject 引用计数 / 名称 / 父对象 / kset kset kobject 集合 / 子系统 attribute show / store 回调 uevent 热插拔事件通知 Class(设备类) /sys/class/xxx/ 统一视图 /sys/devices 真实设备树 /sys/bus 总线视图 kobject 是基本单元,kset 是同类 kobject 的集合,attribute 暴露为文件,uevent 负责事件通知 Class 将不同总线/设备的同类功能统一到 /sys/class/ 下,方便用户空间按功能查找 核心对象 集合/子系统 属性文件 事件机制 设备类

二、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成为驱动与用户空间之间轻量级的交互通道。

提示:attribute的show/store回调运行在进程上下文中,可以睡眠,但应尽量保持简短。不要在show/store中执行耗时操作,否则会阻塞用户进程。

四、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文件和设备节点都被正确移除。如果kobject的引用计数未归零,sysfs目录会残留,后续重新加载驱动时可能因目录已存在而失败。

总结来说,sysfs是Linux设备模型在用户空间的映射,kobject是基本单元,kset是集合,attribute是文件接口,uevent是事件通道,Class是功能视图。掌握这五者的关系,就掌握了Linux设备模型的核心脉络,也为后续编写规范的设备驱动打下坚实基础。