3. 内核对象与sysfs
Linux 设备模型的核心并不在“设备”本身,而在设备背后那套统一的对象抽象机制。kobject、kset、ktype 三者共同构成了内核对象模型的地基:kobject 提供引用计数与父子层级,kset 把同类 kobject 组织成集合,ktype 则定义这些对象在 sysfs 中如何呈现、如何释放。理解它们,才能真正看懂 /sys 下那些目录、属性和符号链接从何而来。
一、核心原理:kobject 是对象模型的原子
kobject 是内核中一个极简但关键的结构,它本身通常不单独存在,而是被嵌入到更大的结构体中,例如 struct device、struct driver、struct class。它承担三项职责:引用计数、父子关系维护、以及在 sysfs 中生成对应目录。
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd;
struct kref kref;
unsigned int flags;
};
其中 kref 决定对象生命周期,parent 决定 sysfs 中的目录层级,ktype 决定属性文件与释放回调。kobject 的 name 一旦注册,就会在 sysfs 中生成一个同名目录;若 parent 指向某个设备对象,该目录便挂在其父目录之下,形成树状结构。
二、kset 与 ktype:集合与行为分离
kset 是一组 kobject 的集合,它自身也内嵌一个 kobject,因此 kset 在 sysfs 中同样表现为一个目录。kset 的主要作用是批量管理同类对象,并提供 uevent 事件通知。例如 /sys/bus 对应一个 kset,其下每个总线目录又各自是一个 kset 或 kobject。
ktype 则描述“这一类对象长什么样”。它包含三个关键成员:release 回调、sysfs_ops 操作集、default_attrs 默认属性。release 负责对象引用归零后的释放;sysfs_ops 定义 show/store 方法,决定属性文件如何读写;default_attrs 列出该类型对象默认暴露的属性。
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
三者的关系可以概括为:kobject 是实例,kset 是同类实例的容器,ktype 是实例的行为模板。一个 kobject 可以属于某个 kset,同时拥有自己的 ktype;kset 也可以指定 ktype,供其成员继承。
三、sysfs 层次结构与用户空间接口
sysfs 是 kobject 层级在用户空间的投影。它通常挂载在 /sys,顶层包含 bus、devices、class、firmware、module 等目录。其中 /sys/devices 是真实设备树的根,/sys/bus 和 /sys/class 中的条目大多是指向 /sys/devices 的符号链接,用于按总线或功能分类快速检索。
| sysfs 路径 | 对应内核对象 | 典型用途 |
|---|---|---|
| /sys/devices | device 内嵌 kobject | 真实设备拓扑,父子关系 |
| /sys/bus/pci/devices | bus 与 device 的 kset | 按总线查看设备与驱动绑定 |
| /sys/class/net | class 与 device 的 kobject | 按功能类别访问网络接口 |
| /sys/module | module 的 kset | 查看模块参数与引用计数 |
| /sys/kernel | kernel 相关 kset | 内核运行时配置与状态 |
用户空间通过普通文件读写与 sysfs 交互。属性文件通常是一行文本,读操作触发 ktype 中 sysfs_ops 的 show 方法,写操作触发 store 方法。例如查看 PCI 设备厂商 ID:
cat /sys/bus/pci/devices/0000:00:1f.2/vendor
# 输出:0x8086
echo 1 > /sys/bus/pci/devices/0000:00:1f.2/remove
# 触发设备移除流程
需要注意的是,sysfs 属性并非随意可写。只有 ktype 中显式声明且 store 方法允许的属性才可写,否则会返回权限错误。这也是内核对象模型对用户空间接口的约束:接口由 ktype 定义,而非由目录名决定。
四、案例与场景:从注册到 /sys 出现目录
以注册一个 PCI 设备为例,内核调用 device_register(),内部完成 kobject 初始化、parent 设置、ktype 绑定,随后在 sysfs 中创建目录与默认属性。若该设备属于某个总线,还会在 /sys/bus/pci/devices 下生成符号链接。整个过程可简化为:
- 分配并初始化 struct device,内嵌 kobject 被设置 name、parent、ktype。
- 调用 kobject_add(),在 sysfs 中创建对应目录。
- 根据 ktype 的 default_attrs 创建属性文件。
- 若属于 kset,加入集合链表,并触发 uevent 通知用户空间。
- 用户空间 udev 收到事件,创建 /dev 节点或执行规则。
ls -l /sys/bus/pci/devices/ 观察符号链接指向,用 readlink -f 确认真实设备路径,从而验证 kobject 父子关系是否符合预期。五、误区与复盘
常见误区之一是把 kobject 当作设备本身。实际上 kobject 只是嵌入在设备结构中的“对象身份”,设备可以没有 kobject 而存在,但一旦注册到设备模型,就必须通过 kobject 暴露到 sysfs。误区之二是认为 kset 就是目录,实际上 kset 是集合管理结构,其目录表现只是内嵌 kobject 的结果。误区之三是忽略 ktype 的 release 回调,导致引用计数归零后对象无法正确释放,形成内存泄漏或 use-after-free。
复盘来看,kobject、kset、ktype 分别对应“实例—集合—行为”三个维度,sysfs 则是这三者在用户空间的统一投影。掌握这条主线,再去看 /sys 下任何目录和属性,都能追溯到其内核对象来源与生命周期规则。