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 指向某个设备对象,该目录便挂在其父目录之下,形成树状结构。

kobject 不是“设备”,而是“可被 sysfs 表达的内核对象”。设备、驱动、总线、类,都是通过内嵌 kobject 获得 sysfs 能力的。

二、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,供其成员继承。

kobject / kset / ktype 与 sysfs 层次关系 ktype(行为模板) release() 释放回调 sysfs_ops show/store default_attrs 默认属性 决定属性文件读写方式 kset(同类集合) 内嵌 kobject list 管理成员 uevent 事件通知 对应 /sys 下一级目录 kobject(实例) name 目录名 parent 父对象 kref 引用计数 sd 指向 sysfs 节点 sysfs 层次结构(/sys 视角) /sys /sys/bus /sys/devices /sys/class pci/ pci0000:00/ net/ kobject 注册后生成目录,kset 组织同类目录,ktype 决定目录内属性文件

三、sysfs 层次结构与用户空间接口

sysfs 是 kobject 层级在用户空间的投影。它通常挂载在 /sys,顶层包含 bus、devices、class、firmware、module 等目录。其中 /sys/devices 是真实设备树的根,/sys/bus 和 /sys/class 中的条目大多是指向 /sys/devices 的符号链接,用于按总线或功能分类快速检索。

sysfs 路径对应内核对象典型用途
/sys/devicesdevice 内嵌 kobject真实设备拓扑,父子关系
/sys/bus/pci/devicesbus 与 device 的 kset按总线查看设备与驱动绑定
/sys/class/netclass 与 device 的 kobject按功能类别访问网络接口
/sys/modulemodule 的 kset查看模块参数与引用计数
/sys/kernelkernel 相关 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 下生成符号链接。整个过程可简化为:

  1. 分配并初始化 struct device,内嵌 kobject 被设置 name、parent、ktype。
  2. 调用 kobject_add(),在 sysfs 中创建对应目录。
  3. 根据 ktype 的 default_attrs 创建属性文件。
  4. 若属于 kset,加入集合链表,并触发 uevent 通知用户空间。
  5. 用户空间 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 的引用计数必须与 ktype 的 release 配对使用。若只增加引用而不释放,或释放后仍访问,都会造成内核不稳定。排查时优先检查 kobject_put() 与 release 回调是否成对出现。

复盘来看,kobject、kset、ktype 分别对应“实例—集合—行为”三个维度,sysfs 则是这三者在用户空间的统一投影。掌握这条主线,再去看 /sys 下任何目录和属性,都能追溯到其内核对象来源与生命周期规则。