Linux驱动 2026年6月9日 51 分钟

Linux内核kobject与kset详解

Linux内核kobject与kset详解 1. 为什么 Linux 内核需要 kobject 和 kset 如果没有这…

Linux内核kobject与kset详解

[!abstract] kobjectkset 是 Linux 内核对象模型的核心基础设施。

它们不是某个单独子系统私有的“小工具”,而是驱动模型、sysfs、热插拔事件、设备层级管理背后的公共底座。

这篇笔记不走那种只解释 API 名字的路子,而是按下面这条主线来讲:

  1. 先回答 kobject / kset 为什么会存在。
  2. 再拆它们的结构体和包含关系。
  3. 再追踪它们如何进入 sysfs、如何发 uevent、如何管理生命周期。
  4. 最后结合这棵 Linux 4.1.15 内核树里的真实源码,解释它们在 deviceclassbus 里的落地方式。

1. 为什么 Linux 内核需要 kobjectkset

如果没有这套对象模型,内核各个子系统都会自己处理下面这些共性问题:

  • 这个对象叫什么名字
  • 这个对象在层级树中的父节点是谁
  • 这个对象什么时候创建、什么时候销毁
  • 这个对象怎样导出到用户空间
  • 用户空间如何通过 sysfs 读写它
  • 这个对象变化时,要不要通知用户空间

这些问题在设备、总线、类、模块、固件信息、IOMMU group、cpufreq policy、cpuidle state 等对象上都会反复出现。

Linux 的做法不是让每个子系统各写一套,而是抽一层公共对象模型出来:

  • kobject 负责“一个对象”的统一管理
  • kset 负责“一组对象”的统一组织

所以你看到的:

  • /sys/kernel
  • /sys/devices
  • /sys/class
  • /sys/bus
  • /sys/firmware

表面上看是很多目录,底层其实很多都落在 kobject / kset 这套机制上。

2. 一句话理解这两个对象

先建立最短心智模型:

  • kobject:内核里的一个“对象节点”
  • kset:一组 kobject 的“集合和组织者”

再进一步:

  • kobject 解决的是“单个对象如何被命名、计数、挂到 sysfs、加入层级、发事件”
  • kset 解决的是“多个对象如何作为一个子系统被组织、查找、统一处理 uevent”

还要再加一个经常一起出现的角色:

  • kobj_type:定义“这一类对象怎么释放、默认有哪些属性、sysfs 的 show/store 入口是什么”

所以可以把三者关系记成:

  • kobject 是对象本体
  • kset 是对象所属的组织
  • kobj_type 是对象所属的类型规则

3. 它们之间的关系图

flowchart TD
    A[业务对象 struct xxx] --> B[内嵌 struct kobject]
    B --> C[parent]
    B --> D[kset]
    B --> E[ktype]
    B --> F[kref]
    B --> G[sysfs节点 sd]
    D --> H[struct kset]
    H --> I[成员链表 list]
    H --> J[内嵌 kobject]
    H --> K[uevent_ops]
    E --> L[release]
    E --> M[sysfs_ops]
    E --> N[default_attrs]
    B --> O[sysfs目录/文件]
    B --> P[uevent]

这个图里最关键的 3 个点:

  1. 真实业务对象通常是“内嵌一个 kobject”,不是“等于 kobject
  2. kset 自己内部也嵌了一个 kobject
  3. kobjectksetkobj_type 三者是配合使用,不是互相替代

4. struct kobject 详解

struct kobject 定义在 include/linux/kobject.h,可以直接看 include/linux/kobject.h:63

它的核心字段如下:

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;
    ...
};

下面逐个解释。

4.1 name

对象名字。

它最直观的作用就是对应 sysfs 目录名。

例如 /sys/kernel 里的 kernel,对应的就是 kernel_kobj 的名字。

如果一个对象名字为空,kobject_add_internal() 会直接报错,这段检查在 lib/kobject.c:208

4.2 parent

父对象指针。

它决定这个对象挂在层级树的哪一层。

比如:

  • kernel_kobj 的 parent 是 NULL
  • /sys/kernel/kobject_example 中的 kobject_example,其 parent 是 kernel_kobj
  • 设备对象的 parent 可能是另一个设备的 kobj

4.3 kset

这个对象属于哪个集合。

一旦设置了 ksetkobject_add_internal() 会做两件重要的事:

  1. 把对象挂进 kset 的成员链表
  2. 如果你没显式指定 parent,就默认把 kset->kobj 当 parent

对应源码在:

这说明 kset 不只是“记账”,还会影响对象在层级树里的位置。

4.4 ktype

对象类型描述。

它不是 C 语言意义上的“类型系统”,而是 kobject 世界中的“行为描述表”。

通过它,内核知道:

  • 这个对象最后怎么释放
  • 它的 sysfs show/store 回调从哪进
  • 注册时自动创建哪些默认属性文件

4.5 sd

sysfs/kernfs 节点。

你可以把它理解成“这个 kobject 在 sysfs 里的实体句柄”。

创建目录时会被建立,删除目录时会被移除。

4.6 kref

引用计数。

这是生命周期管理的核心。

谁拿到了对象引用,就应当:

  • 增引用:kobject_get()
  • 释放引用:kobject_put()

一旦引用计数归零,就进入清理与释放流程。

4.7 state_*

这些标志位是内部状态机,用来记录:

  • 是否已经初始化
  • 是否已经加入 sysfs
  • 是否已经发过 KOBJ_ADD
  • 是否已经发过 KOBJ_REMOVE
  • 是否要抑制 uevent

它们主要服务于内部安全检查和自动清理。

5. struct kobj_type 详解

kobj_typeinclude/linux/kobject.h:115

struct kobj_type {
    void (*release)(struct kobject *kobj);
    const struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
    ...
};

这是理解 kobject 的关键。

5.1 release

最重要的回调,没有之一。

kobject 的引用计数归零后,最终会走到:

这就是对象最后真正释放自己的地方。

[!warning] release 必须有。

如果没有,说明对象生命周期设计是不完整的。

kobject_cleanup() 里,内核甚至会专门打印“does not have a release() function”这样的调试信息,见 lib/kobject.c:608

5.2 sysfs_ops

这个对象类型在 sysfs 层面的通用读写入口。

对普通 kobj_attribute 来说,常见默认实现是 kobj_sysfs_ops,定义在 lib/kobject.c:783

也就是说:

  • 用户读 sysfs 文件时,先进入 sysfs_ops.show
  • 再由它把 attribute 转回更具体的属性结构
  • 最后再进入真正的 foo_show() / foo_store()

5.3 default_attrs

默认属性文件数组。

当对象目录被创建后,populate_dir() 会遍历它,自动帮你建文件,源码在 lib/kobject.c:49

这非常适合“一类对象都带同一组属性”的场景。

6. struct kset 详解

struct kset 定义在 include/linux/kobject.h:167

struct kset {
    struct list_head list;
    spinlock_t list_lock;
    struct kobject kobj;
    const struct kset_uevent_ops *uevent_ops;
};

它本质上包含 4 个部分:

  • 一个成员链表 list
  • 一个保护链表的锁 list_lock
  • 一个“代表整个集合自身”的 kobject
  • 一组 uevent 策略 uevent_ops

这里最容易忽略的是:

kset 自己也是一个 kobject

这意味着:

  • kset 自己可以出现在 sysfs 里
  • 其他 kobject 可以挂在它下面
  • 它既是“集合”,又是“目录节点”

7. kset_uevent_ops 的意义

定义在 include/linux/kobject.h:131

它不是给单个对象用的,而是给一整个集合用的。

作用有 3 个:

7.1 filter

决定某个对象变化时,这个 uevent 要不要发。

7.2 name

决定这个事件对用户空间表现成什么 SUBSYSTEM

7.3 uevent

往 uevent 环境变量里再补充额外键值对。

这意味着 kset 不只是存对象,它还定义了这类对象和用户空间交互时的“公共策略”。

8. kobject 的完整生命周期

这一节是最重要的主线。

8.1 初始化:kobject_init()

入口在 lib/kobject.c:314

它做的事情很集中:

  • 检查参数合法性
  • 初始化内部引用计数和链表节点
  • 标记对象已经初始化
  • ktype 记下来

真正的内部初始化在 lib/kobject.c:187

8.2 加入层级树:kobject_add()

入口在 lib/kobject.c:382

这一步做的事情包括:

  1. 给对象设置名字
  2. 设置 parent
  3. 如果有 kset,加入 kset 成员链表
  4. 创建 sysfs 目录
  5. 创建默认属性文件

实际底层核心是 kobject_add_internal(),在 lib/kobject.c:200

8.3 初始化并加入:kobject_init_and_add()

入口在 lib/kobject.c:417

它只是把前两步打包:

  • kobject_init()
  • kobject_add()

这是最常用的 API 之一。

8.4 创建 sysfs 目录:create_dir()

lib/kobject.c:66

这里会:

  • sysfs_create_dir_ns()
  • 再调 populate_dir()

也就是说,一个 kobject 一旦成功加入系统,通常就已经在 sysfs 里有目录了。

8.5 自动创建默认属性:populate_dir()

lib/kobject.c:49

如果 ktype->default_attrs 不为空,内核会自动给你在目录下建文件。

8.6 发 add 事件:kobject_uevent(KOBJ_ADD)

这里有个特别容易踩坑的点:

kobject_add() 不会自动发 KOBJ_ADD

这一点在 lib/kobject.c:377 的注释里写得很清楚。

所以对动态对象,正确套路通常是:

  1. kobject_init_and_add()
  2. 建好属性文件
  3. kobject_uevent(..., KOBJ_ADD)

这也是官方 samples/kobject/kset-example.c 的写法。

8.7 获取与释放引用:kobject_get() / kobject_put()

API 在:

这是对象生命周期的标准入口。

8.8 最终清理:kobject_cleanup()

lib/kobject.c:600

它会做几件很关键的善后:

  1. 如果已经发过 ADD 但没发 REMOVE,自动补 REMOVE
  2. 如果还在 sysfs 里,自动 kobject_del()
  3. 调用 ktype->release()
  4. 释放名字内存

所以它不是简单 free,而是完整的对象退场流程。

9. kset 的完整生命周期

9.1 初始化:kset_init()

lib/kobject.c:751

它做的事情是:

  • 初始化内嵌 kobject
  • 初始化成员链表
  • 初始化自旋锁

9.2 注册:kset_register()

lib/kobject.c:793

这里和普通 kobject 有一个差别:

kset_register() 成功后会自动发 KOBJ_ADD

lib/kobject.c:804

9.3 动态创建:kset_create_and_add()

lib/kobject.c:918

这是最方便的用法:

  • 分配 kset
  • 设名字
  • uevent_ops
  • 设 parent
  • 注册进系统

9.4 注销:kset_unregister()

lib/kobject.c:812

它会:

  • 从 sysfs 删除
  • kobject_put(&k->kobj)

9.5 查成员:kset_find_obj()

lib/kobject.c:829

它会遍历这个 kset 的成员链表,按名字找对象。

这再次说明:

  • kset 不是抽象概念
  • 它内部真的维护了一组对象成员

10. sysfs 视角下如何看 kobject

很多人学 kobject 时会把注意力都放在结构体上,但其实它最大的可见效果在 sysfs

10.1 目录来自哪里

来自 kobject 自己。

一个 kobject 成功 add 后,通常对应一个 sysfs 目录。

10.2 文件来自哪里

常见有两条路:

  1. ktype->default_attrs
  2. 手动 sysfs_create_file() / sysfs_create_group()

10.3 读写回调如何落地

sysfs 层最终需要统一函数签名,而具体子系统又想拿到自己的业务对象。

所以常见套路是:

  1. show/store 先接收通用的 struct kobject *
  2. 再用 container_of() 转回自定义对象
  3. 再调用真正业务层的读写函数

官方 samples/kobject/kset-example.c 就是标准范例。

11. uevent 视角下如何看 kset

kobject_uevent_env()lib/kobject_uevent.c:164

它的一个核心逻辑是:

  1. 从当前对象往上找祖先
  2. 找到第一个带 kset 的对象
  3. 使用这个 ksetuevent_ops

对应源码在 lib/kobject_uevent.c:183lib/kobject_uevent.c:196

然后它会准备:

  • ACTION=
  • DEVPATH=
  • SUBSYSTEM=

lib/kobject_uevent.c:238lib/kobject_uevent.c:246

所以从用户空间看见的“这个对象属于哪个 subsystem”,很多时候本质上就是由 kset 决定的。

12. 真实实例一:/sys/kernel

这是最简单、最适合入门的真实例子。

kernel_kobj 定义在 kernel/ksysfs.c:187,初始化在 kernel/ksysfs.c:213

关键代码:

kernel_kobj = kobject_create_and_add("kernel", NULL);
sysfs_create_group(kernel_kobj, &kernel_attr_group);

这说明:

  • /sys/kernel 本身就是一个 kobject
  • 它下面的很多通用属性文件,是通过 sysfs_create_group() 建出来的

这个例子说明什么时候只用 kobject 就够:

  • 只是想创建一个固定目录
  • 没有必要维护一组动态对象
  • 也不需要复杂的集合级 uevent 策略

13. 真实实例二:/sys/firmware/sys/hypervisor

这两个也很直接:

它们都采用:

kobject_create_and_add("firmware", NULL);
kobject_create_and_add("hypervisor", NULL);

这再次说明:

  • 只需要一个顶层 sysfs 节点时,直接用 kobject 就非常自然

14. 真实实例三:struct device

这是最重要的真实实例。

struct device 本身就内嵌了一个 kobject,定义在 include/linux/device.h:723

struct device {
    ...
    struct kobject kobj;
    ...
};

这意味着:

  • 设备模型不是“另外搞一套对象系统”
  • 它直接构建在 kobject 之上

14.1 device_ktype

设备对象的类型规则定义在 drivers/base/core.c:266

static struct kobj_type device_ktype = {
    .release    = device_release,
    .sysfs_ops  = &dev_sysfs_ops,
    .namespace  = device_namespace,
};

这里清楚地体现了前面讲的分工:

  • release:设备最后怎么释放
  • sysfs_ops:设备 sysfs 文件怎么读写

14.2 devices_kset

设备集合 devices_ksetdrivers/base/core.c:1381 创建:

devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);

它对应 /sys/devices

14.3 设备初始化

drivers/base/core.c:654device_initialize() 里:

dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);

也就是:

  • 每个 struct device 都属于 devices_kset
  • 每个 struct device 都遵循 device_ktype

14.4 设备真正加入系统

drivers/base/core.c:976device_add() 里,会执行:

error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);

drivers/base/core.c:1025

之后再:

kobject_uevent(&dev->kobj, KOBJ_ADD);

drivers/base/core.c:1070

这条链路说明:

  • 设备能出现在 /sys/devices
  • 设备能发热插拔事件

底层都与 kobject 直接相关。

15. 真实实例四:class_kset/sys/class

class_ksetdrivers/base/class.c:588 创建:

class_kset = kset_create_and_add("class", NULL, NULL);

这就是 /sys/class 的根。

类的内部不是简单用 struct class 自己充当 kobject,而是通过 subsys_private 来承载对象模型,定义在 drivers/base/base.h:28

这个结构里内嵌了:

struct kset subsys;
struct kset *devices_kset;
struct kset *drivers_kset;

所以:

  • class 的 sysfs 呈现本质上是一组 kset 结构
  • 它不是“只有一个目录”,而是一个完整的子系统组织结构

在注册类时,代码会做:

cp->subsys.kobj.kset = class_kset;
cp->subsys.kobj.ktype = &class_ktype;
kset_register(&cp->subsys);

对应 drivers/base/class.c:193drivers/base/class.c:201

16. 真实实例五:bus_kset/sys/bus

bus_ksetdrivers/base/bus.c:1265 创建:

bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);

这就是 /sys/bus

每个总线注册时会做:

priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
retval = kset_register(&priv->subsys);

对应 drivers/base/bus.c:892drivers/base/bus.c:897

然后还会再创建两个子集合:

priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);

对应 drivers/base/bus.c:904drivers/base/bus.c:912

这就是为什么你在 sysfs 里会看到:

  • /sys/bus/platform/devices
  • /sys/bus/platform/drivers

这样的组织结构。

[!tip] 这里是理解 kset 最直观的地方:

kset 不是一个抽象名词,它就是 sysfs 里一层层“集合目录”的直接实现基础。

17. 官方样例一:最小 kobject 用法

样例文件是 samples/kobject/kobject-example.c

关键代码在:

主线非常简单:

  1. kobject_create_and_add("kobject_example", kernel_kobj)
  2. sysfs_create_group(example_kobj, &attr_group)

效果是:

  • /sys/kernel/ 下创建一个 kobject_example
  • 里面挂 3 个属性文件:foobazbar

这个例子适合理解:

  • 如何快速创建一个 sysfs 目录
  • 如何用属性组挂一批文件

18. 官方样例二:kset 管一组对象

样例文件是 samples/kobject/kset-example.c

这是学习 kobject / kset 的黄金样例。

18.1 自定义对象

它定义了一个真实业务对象:

struct foo_obj {
    struct kobject kobj;
    int foo;
    int baz;
    int bar;
};

这正是最典型的模式:

  • 业务对象内嵌 kobject

18.2 自定义属性类型

它还定义了 struct foo_attribute,再用 container_of() 把通用 attribute 还原为业务属性对象。

这展示了 sysfs 框架和业务层之间的桥接方式。

18.3 自定义 kobj_type

foo_ktypesamples/kobject/kset-example.c:189

static struct kobj_type foo_ktype = {
    .sysfs_ops = &foo_sysfs_ops,
    .release = foo_release,
    .default_attrs = foo_default_attrs,
};

这很好地体现了 kobj_type 的三个核心作用:

  • 释放对象
  • 决定 sysfs 入口
  • 指定默认属性

18.4 创建 kset

samples/kobject/kset-example.c:248

example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);

18.5 创建成员对象

samples/kobject/kset-example.c:222

retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);

注意,在这之前它先做了:

foo->kobj.kset = example_kset;

也就是:

  • 先告诉对象它属于哪个集合
  • 再让它进对象层级

18.6 显式发 add 事件

samples/kobject/kset-example.c:232

kobject_uevent(&foo->kobj, KOBJ_ADD);

这再次说明:

  • 动态对象通常要自己发 KOBJ_ADD

19. 如何使用:三种常见场景

19.1 场景一:只是想在 /sys/kernel 下建一个固定目录

这时通常只需要:

  • kobject_create_and_add()
  • sysfs_create_group()

适用场景:

  • 小型调试接口
  • 模块级状态导出
  • 系统级静态节点

19.2 场景二:要管理一类自定义对象

这时更适合:

  • 自定义业务结构体,内嵌 kobject
  • 自定义 kobj_type
  • 必要时用 kset 组织多个对象

适用场景:

  • 一组逻辑实例
  • 每个实例都要有独立 sysfs 目录
  • 每个实例都带同样一批属性

19.3 场景三:你其实在做设备/总线/类

这时通常不应该从裸 kobject 开始。

更推荐优先使用已经封装好的高层对象:

  • struct device
  • struct class
  • struct bus_type

因为这些对象已经把:

  • kobject
  • kset
  • sysfs
  • uevent
  • 引用计数

这些通用问题打包好了。

20. 什么时候不该直接上 kobject

这是一个很实用的问题。

如果你只是需要:

  • 普通链表节点
  • 单纯引用计数
  • 一个内部对象,但根本不打算进 sysfs

那就没必要强行用 kobject

因为 kobject 不只是“多一个字段”,它代表的是一整套对象模型义务:

  • 要考虑名字
  • 要考虑生命周期
  • 要考虑 release
  • 要考虑用户空间可见性

所以它适合的是“需要进入内核对象模型”的对象,不是所有对象。

21. 常见易错点

21.1 忘记写 release

这是最常见、也最严重的设计缺口之一。

21.2 直接 kfree() 而不是 kobject_put()

对象一旦纳入 kobject 生命周期,就应该通过引用计数退场。

21.3 以为 kobject_add() 会自动发 KOBJ_ADD

它不会。

21.4 动态对象发 uevent 太早

正确顺序应该是:

  1. kobject_init_and_add()
  2. 建好属性
  3. kobject_uevent(KOBJ_ADD)

21.5 没搞清 parentkset 的差别

parent 决定层级。

kset 决定集合归属,而且在未显式设 parent 时,还会影响默认父对象。

21.6 把 kset 误以为只是一个链表

它还是一个带 kobject 的 sysfs 目录节点,还携带 uevent_ops

21.7 业务代码里滥用裸 kobject

如果本来就是设备模型场景,很多时候应该直接用 struct device

22. 最后的整体心智模型

把前面所有内容收束成一张图:

flowchart TD
    A[业务对象] --> B[内嵌 kobject]
    B --> C[kobj_type]
    B --> D[parent]
    B --> E[kset]
    C --> F[release]
    C --> G[sysfs_ops]
    C --> H[default_attrs]
    E --> I[成员列表]
    E --> J[uevent策略]
    B --> K[sysfs目录]
    B --> L[属性文件]
    B --> M[uevent事件]

最终你应该建立下面这几个直觉:

  1. kobject 是“对象最小公共壳”
  2. kset 是“对象集合 + 子系统组织者”
  3. kobj_type 是“对象行为模板”
  4. sysfs 是它们对用户空间的文件化呈现
  5. uevent 是它们对用户空间的事件化通知

23. 一段最实用的结论

如果只保留一句最有用的话:

kobject 解决“单个对象如何被内核统一管理并映射到 sysfs”,kset 解决“多个对象如何作为一个子系统被组织、查找并统一发 uevent”。
struct deviceclassbus 这些你平时最常见的驱动模型对象,本质上都建立在这套机制之上。

24. 推荐继续阅读路径

如果你想继续顺着这条线往下打通,建议按这个顺序看:

  1. samples/kobject/kobject-example.c
  2. samples/kobject/kset-example.c
  3. drivers/base/core.c
  4. drivers/base/class.c
  5. drivers/base/bus.c
  6. lib/kobject.c
  7. lib/kobject_uevent.c

再配合下面这些已有笔记一起看,会更顺:

  • [[iMX6ULL设备树从DTS到设备创建全过程详解]]
  • [[device_node转换为platform_device的完整调用链路]]
  • [[Linux GPIO子系统从零基础到驱动开发与使用开发完全指南]]
上一篇 GPIO子系统-简单驱动编写

下一篇 Linux内核对象模型kobject-kset-sysfs-uevent详解

Linux内核对象模型kobject-kset-sysfs-uevent详解 1. 为什么 Linux 内核需要对象模型...