Linux内核kobject与kset详解
[!abstract]
kobject和kset是 Linux 内核对象模型的核心基础设施。它们不是某个单独子系统私有的“小工具”,而是驱动模型、
sysfs、热插拔事件、设备层级管理背后的公共底座。这篇笔记不走那种只解释 API 名字的路子,而是按下面这条主线来讲:
- 先回答
kobject/kset为什么会存在。- 再拆它们的结构体和包含关系。
- 再追踪它们如何进入
sysfs、如何发uevent、如何管理生命周期。- 最后结合这棵
Linux 4.1.15内核树里的真实源码,解释它们在device、class、bus里的落地方式。
1. 为什么 Linux 内核需要 kobject 和 kset
如果没有这套对象模型,内核各个子系统都会自己处理下面这些共性问题:
- 这个对象叫什么名字
- 这个对象在层级树中的父节点是谁
- 这个对象什么时候创建、什么时候销毁
- 这个对象怎样导出到用户空间
- 用户空间如何通过
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 个点:
- 真实业务对象通常是“内嵌一个
kobject”,不是“等于kobject” kset自己内部也嵌了一个kobjectkobject、kset、kobj_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
这个对象属于哪个集合。
一旦设置了 kset,kobject_add_internal() 会做两件重要的事:
- 把对象挂进
kset的成员链表 - 如果你没显式指定
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_type 在 include/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 的引用计数归零后,最终会走到:
kobject_put()→ lib/kobject.c:669kobject_release()→ lib/kobject.c:648kobject_cleanup()→ lib/kobject.c:600ktype->release()→ lib/kobject.c:627
这就是对象最后真正释放自己的地方。
[!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。
这一步做的事情包括:
- 给对象设置名字
- 设置 parent
- 如果有
kset,加入kset成员链表 - 创建 sysfs 目录
- 创建默认属性文件
实际底层核心是 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()
这里会:
- 调
sysfs_create_dir_ns() - 再调
populate_dir()
也就是说,一个 kobject 一旦成功加入系统,通常就已经在 sysfs 里有目录了。
8.5 自动创建默认属性:populate_dir()
如果 ktype->default_attrs 不为空,内核会自动给你在目录下建文件。
8.6 发 add 事件:kobject_uevent(KOBJ_ADD)
这里有个特别容易踩坑的点:
kobject_add()不会自动发KOBJ_ADD。
这一点在 lib/kobject.c:377 的注释里写得很清楚。
所以对动态对象,正确套路通常是:
kobject_init_and_add()- 建好属性文件
kobject_uevent(..., KOBJ_ADD)
这也是官方 samples/kobject/kset-example.c 的写法。
8.7 获取与释放引用:kobject_get() / kobject_put()
API 在:
这是对象生命周期的标准入口。
8.8 最终清理:kobject_cleanup()
它会做几件很关键的善后:
- 如果已经发过
ADD但没发REMOVE,自动补REMOVE - 如果还在 sysfs 里,自动
kobject_del() - 调用
ktype->release() - 释放名字内存
所以它不是简单 free,而是完整的对象退场流程。
9. kset 的完整生命周期
9.1 初始化:kset_init()
它做的事情是:
- 初始化内嵌
kobject - 初始化成员链表
- 初始化自旋锁
9.2 注册:kset_register()
这里和普通 kobject 有一个差别:
kset_register()成功后会自动发KOBJ_ADD。
9.3 动态创建:kset_create_and_add()
这是最方便的用法:
- 分配
kset - 设名字
- 设
uevent_ops - 设 parent
- 注册进系统
9.4 注销:kset_unregister()
它会:
- 从 sysfs 删除
kobject_put(&k->kobj)
9.5 查成员:kset_find_obj()
它会遍历这个 kset 的成员链表,按名字找对象。
这再次说明:
kset不是抽象概念- 它内部真的维护了一组对象成员
10. sysfs 视角下如何看 kobject
很多人学 kobject 时会把注意力都放在结构体上,但其实它最大的可见效果在 sysfs。
10.1 目录来自哪里
来自 kobject 自己。
一个 kobject 成功 add 后,通常对应一个 sysfs 目录。
10.2 文件来自哪里
常见有两条路:
ktype->default_attrs- 手动
sysfs_create_file()/sysfs_create_group()
10.3 读写回调如何落地
sysfs 层最终需要统一函数签名,而具体子系统又想拿到自己的业务对象。
所以常见套路是:
show/store先接收通用的struct kobject *- 再用
container_of()转回自定义对象 - 再调用真正业务层的读写函数
官方 samples/kobject/kset-example.c 就是标准范例。
11. uevent 视角下如何看 kset
kobject_uevent_env() 在 lib/kobject_uevent.c:164。
它的一个核心逻辑是:
- 从当前对象往上找祖先
- 找到第一个带
kset的对象 - 使用这个
kset的uevent_ops
对应源码在 lib/kobject_uevent.c:183 到 lib/kobject_uevent.c:196。
然后它会准备:
ACTION=DEVPATH=SUBSYSTEM=
见 lib/kobject_uevent.c:238 到 lib/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
这两个也很直接:
firmware_kobj在 drivers/base/firmware.c:18hypervisor_kobj在 drivers/base/hypervisor.c:16
它们都采用:
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_kset 在 drivers/base/core.c:1381 创建:
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
它对应 /sys/devices。
14.3 设备初始化
在 drivers/base/core.c:654 的 device_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:976 的 device_add() 里,会执行:
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
之后再:
kobject_uevent(&dev->kobj, KOBJ_ADD);
这条链路说明:
- 设备能出现在
/sys/devices - 设备能发热插拔事件
底层都与 kobject 直接相关。
15. 真实实例四:class_kset 与 /sys/class
class_kset 在 drivers/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:193 到 drivers/base/class.c:201。
16. 真实实例五:bus_kset 与 /sys/bus
bus_kset 在 drivers/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:892 到 drivers/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:904 到 drivers/base/bus.c:912。
这就是为什么你在 sysfs 里会看到:
/sys/bus/platform/devices/sys/bus/platform/drivers
这样的组织结构。
[!tip] 这里是理解
kset最直观的地方:
kset不是一个抽象名词,它就是 sysfs 里一层层“集合目录”的直接实现基础。
17. 官方样例一:最小 kobject 用法
样例文件是 samples/kobject/kobject-example.c。
关键代码在:
主线非常简单:
kobject_create_and_add("kobject_example", kernel_kobj)sysfs_create_group(example_kobj, &attr_group)
效果是:
- 在
/sys/kernel/下创建一个kobject_example - 里面挂 3 个属性文件:
foo、baz、bar
这个例子适合理解:
- 如何快速创建一个 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_ktype 在 samples/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 devicestruct classstruct bus_type
因为这些对象已经把:
kobjectkset- 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 太早
正确顺序应该是:
kobject_init_and_add()- 建好属性
- 再
kobject_uevent(KOBJ_ADD)
21.5 没搞清 parent 和 kset 的差别
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事件]
最终你应该建立下面这几个直觉:
kobject是“对象最小公共壳”kset是“对象集合 + 子系统组织者”kobj_type是“对象行为模板”sysfs是它们对用户空间的文件化呈现uevent是它们对用户空间的事件化通知
23. 一段最实用的结论
如果只保留一句最有用的话:
kobject解决“单个对象如何被内核统一管理并映射到 sysfs”,kset解决“多个对象如何作为一个子系统被组织、查找并统一发 uevent”。struct device、class、bus这些你平时最常见的驱动模型对象,本质上都建立在这套机制之上。
24. 推荐继续阅读路径
如果你想继续顺着这条线往下打通,建议按这个顺序看:
samples/kobject/kobject-example.csamples/kobject/kset-example.cdrivers/base/core.cdrivers/base/class.cdrivers/base/bus.clib/kobject.clib/kobject_uevent.c
再配合下面这些已有笔记一起看,会更顺:
- [[iMX6ULL设备树从DTS到设备创建全过程详解]]
- [[device_node转换为platform_device的完整调用链路]]
- [[Linux GPIO子系统从零基础到驱动开发与使用开发完全指南]]