[TOC]

lib/kobject.c 内核对象(Kernel Object) 设备模型的核心基石

历史与背景

这项技术是为了解决什么特定问题而诞生的?

lib/kobject.c 及其核心数据结构 struct kobject 的诞生,是为了解决在Linux 2.5/2.6内核开发周期中遇到的一个根本性问题:缺乏一个统一的、内在一致的内核对象模型

在kobject出现之前,内核充满了各种不相关的子系统,它们:

  • 缺乏统一的生命周期管理:内核中创建了大量的动态对象,但没有一个标准的方法来跟踪它们的使用情况并安全地释放它们。这导致了复杂的、容易出错的手动引用计数或锁机制,是use-after-free和内存泄漏等bug的主要来源。
  • 无法表示对象间的层次关系:物理世界中的硬件设备天然地具有层次结构(例如,一个USB鼠标连接到一个USB集线器,该集线器又连接到一个PCI总线上的USB控制器)。旧的内核模型无法以一种通用的方式来表达这种父子关系。
  • 没有统一的内核-用户空间接口procfs被滥用于向用户空间暴露各种各样的内核信息,但它缺乏结构和访问控制,变得杂乱无章。内核需要一个更结构化、更规范的方式来将内核对象及其属性展现给用户空间。

kobject 被创造出来,就是为了提供一个嵌入式的、轻量级的对象,专门解决以上三个问题,它为Linux统一设备模型的建立奠定了基础。

它的发展经历了哪些重要的里程碑或版本迭代?

kobject 的发展与Linux统一设备模型和sysfs文件系统的发展是同步的,由Patrick Mochel主导。

  • 2.6内核的引入kobject 是在Linux 2.6内核开发周期中引入的最重要的概念之一。它的出现标志着Linux开始拥有一个正式的、统一的设备模型。
  • sysfs的诞生:与kobject紧密耦合,sysfs伪文件系统被创造出来。sysfs的设计目标就是成为内核中kobject层次结构在用户空间的一个直接、实时的反映。每一个kobject都可以在sysfs中表现为一个目录。
  • 功能的提炼与分离:最初kobject的功能可能更庞杂。后来,其核心功能被清晰地提炼为三个:
    1. 引用计数(kref:用于对象生命周期管理。
    2. 层次关系(parent指针和kset:用于构建对象树。
    3. 与sysfs的关联:用于在用户空间展现对象及其属性。

目前该技术的社区活跃度和主流应用情况如何?

kobject 是Linux内核中最基础、最稳定、最核心的组件之一。它不是一个可选功能,而是整个驱动模型、设备管理、模块管理和cgroup管理的基石。

  • 无处不在:内核中几乎所有需要结构化表示并暴露给用户空间的对象都内嵌了一个kobject。这包括struct device, struct device_driver, struct bus_type, struct module, struct cgroup等。
  • 稳定成熟:其核心API和行为已经非常稳定,社区活动主要集中在修复一些由其复杂性导致的微妙bug,而不是进行大的架构变动。

核心原理与设计

它的核心工作原理是什么?

kobject 本身并不是一个庞大的、独立的数据结构,而是一个小型的、被嵌入到其他更大的数据结构中的“组件”。它的核心工作原理围绕以下三点展开:

  1. 生命周期管理 (通过 kref)

    • kobject 内部包含一个struct kref成员,这是一个原子引用计数器。
    • 当一个kobject通过kobject_init()初始化时,其引用计数被设为1。
    • 其他代码可以通过kobject_get()来增加引用计数,表示它们正在“使用”这个对象。
    • 使用完毕后,必须调用kobject_put()来减少引用计数。
    • 当引用计数减到0时,kobject_put()会触发一个预先设定的release回调函数(定义在kobjectktype中)。这个release函数是真正负责释放包含kobject的宿主数据结构的内存的地方。这个机制从根本上解决了“何时可以安全释放对象”的问题。
  2. 层次结构构建

    • kobject包含一个parent指针,可以直接指向另一个kobject,从而形成一个树状的层次结构。
    • 它还引入了kset的概念。一个kset是一个kobject的集合,它本身也是一个kobjectkset可以作为其内部所有kobject的父节点,用于将一组相关的对象组织在一起。
  3. Sysfs视图

    • kobject是内核对象与sysfs之间的桥梁。当一个kobject通过kobject_add()被注册到内核中时,sysfs会在对应的父目录下为它创建一个新的目录
    • 每个kobject都可以关联一组属性(attributes)。每个属性在sysfs中表现为该目录下的一个文件
    • 对这个文件的读写操作,会触发内核中预先定义好的showstore回调函数,从而允许用户空间读取或修改内核对象的状态。

它的主要优势体现在哪些方面?

  • 统一性:为内核中所有需要管理的对象提供了一个统一的模型。
  • 健壮的生命周期管理:引用计数机制极大地减少了use-after-free和内存泄漏等问题。
  • 结构清晰:能够清晰地反映内核中对象的层次关系。
  • 自动化的用户空间接口:一旦一个对象正确地使用了kobject,它就能“免费”获得一个结构清晰、访问受控的sysfs接口。

它存在哪些已知的劣势、局-限性或在特定场景下的不适用性?

  • 复杂性kobject的规则,特别是引用计数的正确配对(get/put),对于初学者来说是复杂且容易出错的。一个未配对的kobject_put()可能导致过早释放,而一个遗漏的kobject_put()则会导致内存泄漏。
  • “重量级”:对于一些非常简单的、内部使用的、不需要暴露给用户空间的数据结构,引入一个kobject会带来不必要的开销和复杂性。
  • 它只是一个机制kobject本身不“做”任何具体的事情。它只是一个框架,所有具体的功能(如文件读写)都依赖于开发者提供的回调函数。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

在内核中,只要你需要创建一个需要长期存在、被多处引用、需要结构化地展现给用户空间的对象,kobject就是唯一且标准的解决方案。

  • 例一:设备 (struct device)
    内核中代表任何物理或虚拟设备的struct devicekobject最典型的用户。struct device内嵌了一个kobject。当一个USB鼠标被插入时,内核会为其创建一个device对象。其kobjectparent会指向代表它所连接的USB端口的device对象的kobject,从而在sysfs中创建出层级分明的路径,如/sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/
  • 例二:内核模块 (struct module)
    每个加载的内核模块都由一个struct module表示,其中也内嵌了一个kobject。这会在/sys/module/下创建对应的目录,并通过属性文件暴露模块的引用计数、状态等信息。
  • 例三:Cgroups
    Cgroup v1和v2的层次结构也是通过kobject(及其cgroup特定的封装css_set)来构建的。你在/sys/fs/cgroup/下看到的每一个目录都对应一个内核中的kobject

是否有不推荐使用该技术的场景?为什么?

  • 临时的、内部使用的数据结构:如果一个数据结构只在单个函数或单个执行线程内部使用,生命周期非常明确,那么完全不需要kobject。简单的kmalloc/kfree就足够了。
  • 只需要简单的引用计数:如果你只需要一个安全的生命周期管理机制,但完全不需要层次结构或sysfs接口,那么直接使用更轻量级的struct kref就足够了,不必引入整个kobject
  • 当数据结构只是一个列表节点时:如果你只是想把对象组织成一个简单的链表或哈希表,应该使用list_headhlist_nodekobject是用来构建的。

对比分析

请将其 与 其他相似技术 进行详细对比。

特性 kobject struct device kref
核心功能 通用的内核对象。提供三大核心机制:引用计数、层次结构、sysfs接口。 具体的设备对象。描述一个真实的硬件或软件设备。 纯粹的引用计数器。只提供安全的生命周期管理。
实现方式 一个可被嵌入的结构体。 一个**内嵌了kobject**的、更大的结构体。 一个可被嵌入的、更小的结构体。
包含内容 kref, parent指针, kset指针, ktype, name等。 包含一个kobject,并额外增加了总线指针、驱动数据、电源管理回调、DMA配置等设备相关的字段。 只包含一个atomic_t计数器。
与Sysfs关系 紧密耦合kobjectsysfs中目录的直接来源。 通过其内嵌的kobjectsysfs交互。 无任何关系kref本身不会在sysfs中创建任何东西。
抽象层次 低层、通用。是构成设备模型的基础“砖块”。 高层、特化。是设备模型的具体“产品”。 极底层、单一功能。是kobject生命周期管理的核心“零件”。
典型用途 作为struct device, struct module等的基础。 表示系统中的每一个设备。 为任何不需要sysfs或层次结构的内核对象提供安全的释放机制。

include/linux/kref.h

kref_init 初始化对象

1
2
3
4
5
6
7
8
9
10
11
struct kref {
refcount_t refcount;
};
/**
* kref_init - 初始化对象。
* @kref:有问题的对象。
*/
static inline void kref_init(struct kref *kref)
{
refcount_set(&kref->refcount, 1);
}

kref_put 对象的递减 refcount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* kref_put - 对象的递减 refcount
* @kref:对象
* @release:指向函数的指针,该函数将在释放对对象的最后一个引用时清理对象。
*
* 递减 refcount,如果为 0,则调用 @release。 调用方不能将 NULL 或 kfree() 作为 release 函数传递。
*
* 返回:如果此调用删除了对象,则为 1,否则返回 0。 请注意,如果此函数返回 0,则此函数返回时,其他调用方可能已删除该对象。 仅当您想要查看对象是否明确释放时,返回值才是确定的。
*/
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
if (refcount_dec_and_test(&kref->refcount)) {
release(kref);
return 1;
}
return 0;
}

kobject_get 对象的递增 refcount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* kref_get - increment refcount for object.
* @kref: object.
*/
static inline void kref_get(struct kref *kref)
{
refcount_inc(&kref->refcount);
}


/**
* kobject_get() - Increment refcount for object.
* @kobj: object.
*/
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
kobject_name(kobj), kobj);
kref_get(&kobj->kref);
}
return kobj;
}
EXPORT_SYMBOL(kobject_get);

lib/kobject.c

kobject_init_internal 初始化一个 kobject 结构

1
2
3
4
5
6
7
8
9
10
11
12
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;
}

kobject_init 初始化一个 kobject 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* kobject_init() - 初始化一个 kobject 结构。
* @kobj:指向要初始化的 kobject 的指针
* @ktype:指向此 kobject 的 ktype 的指针。
*
* 这个函数会正确地初始化一个kobject,这样它就可以被传递给kobject_add()调用了。
*
* 调用此函数后,必须通过调用 kobject_put() 来清理 kobject,而不是直接调用 kfree,以确保所有内存都被正确清理。
*/
void kobject_init(struct kobject *kobj, const struct kobj_type *ktype)
{
char *err_str;

if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {
/*不要出错,因为有时我们可以恢复*/
pr_err("kobject (%p): tried to init an initialized object, something is seriously wrong.\n",
kobj);
dump_stack_lvl(KERN_ERR);
}

kobject_init_internal(kobj);
kobj->ktype = ktype;
return;

error:
pr_err("kobject (%p): %s\n", kobj, err_str);
dump_stack_lvl(KERN_ERR);
}
EXPORT_SYMBOL(kobject_init);

kobject_put

kobject_put 对象的递减 refcount。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* kobject_put() - 对象的递减 refcount。
* @kobj: 对象。
*
* 递减 refcount,如果为 0,则调用 kobject_cleanup()。
*/
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n",
kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release);
}
}
EXPORT_SYMBOL(kobject_put);

kobject_get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* kobject_get() - 增加 object 的 refcount。
* @kobj: object.
*/
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
kobject_name(kobj), kobj);
kref_get(&kobj->kref);
}
return kobj;
}
EXPORT_SYMBOL(kobject_get);

kobject_create 动态创建一个 kobject 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static const struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};

/**
* kobject_create() - 动态创建一个 kobject 结构体。
*
* 这个函数动态地创建一个 kobject 结构,并将其设置为一个 “动态” kobject,并设置了一个默认的释放函数。
*
* 如果无法创建 kobject,将返回 NULL。从这里返回的 kobject 结构必须通过调用 kobject_put() 而不是 kfree() 来清理,因为 kobject_init() 已经在这个结构上被调用了。
*/
static struct kobject *kobject_create(void)
{
struct kobject *kobj;

kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;

kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}

kobject_set_name_vargs 设置 kobject 的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* kobject_set_name_vargs() - 设置 kobject 的名称。
* @kobj: 要设置名称的 struct kobject。
* @fmt: 用于构建名称的格式字符串。
* @vargs: 用于格式化字符串的 va_list。
*/
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *s;

if (kobj->name && !fmt)
return 0;

s = kvasprintf_const(GFP_KERNEL, fmt, vargs);
if (!s)
return -ENOMEM;

/*
* ewww... 有些名称里带有 '/' ... 如果是这种情况,我们需要确保有一个实际分配的副本可以修改,
* 因为 kvasprintf_const 可能返回的是 .rodata 里的内容。
*/
if (strchr(s, '/')) {
char *t;

t = kstrdup(s, GFP_KERNEL);
kfree_const(s);
if (!t)
return -ENOMEM;
s = strreplace(t, '/', '!');
}
kfree_const(kobj->name);
kobj->name = s;

return 0;
}

kobject_namespace 返回 kobject 的命名空间标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* kobject_namespace() - 返回 @kobj 的命名空间标签。
* @kobj: 相关的 kobject
*
* 如果 @kobj 的父对象启用了命名空间操作,并且因此 @kobj 应该有一个关联的命名空间标签,则返回 @kobj 的命名空间标签。否则返回 %NULL。
*/
const void *kobject_namespace(const struct kobject *kobj)
{
const struct kobj_ns_type_operations *ns_ops = kobj_ns_ops(kobj);

if (!ns_ops || ns_ops->type == KOBJ_NS_TYPE_NONE)
return NULL;

return kobj->ktype->namespace(kobj);
}

kobject_get_ownership 获取 @kobj 的 sysfs 所有权数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* kobject_get_ownership() - 获取 @kobj 的 sysfs 所有权数据
* @kobj: 相关的 kobject
* @uid: sysfs 对象的内核用户 ID
* @gid: sysfs 对象的内核组 ID
*
* 返回创建给定 kobject 的 sysfs 表示时应使用的初始 uid/gid 对。通常用于调整容器中对象的所有权。
*/
void kobject_get_ownership(const struct kobject *kobj, kuid_t *uid, kgid_t *gid)
{
*uid = GLOBAL_ROOT_UID;
*gid = GLOBAL_ROOT_GID;

if (kobj->ktype->get_ownership)
kobj->ktype->get_ownership(kobj, uid, gid);
}

create_dir: 创建sysfs目录及其默认属性文件

此函数是 kobject 添加过程中的一个内部辅助函数, 其核心作用是在 sysfs 虚拟文件系统中为一个给定的 kobject 创建一个目录, 并用该 kobject 类型所定义的默认属性文件来填充这个目录。

函数的执行原理如下:

  1. 创建目录: 它首先调用 sysfs_create_dir_ns()sysfs 中为 kobject 创建一个空的目录, 同时处理可能的命名空间(namespace)属性。
  2. 创建属性文件: 目录创建成功后, 它会获取 kobject 的类型(kobj_type), 并从该类型定义中找到一个默认的属性组列表 (default_groups)。然后, 它调用 sysfs_create_groups() 将这个列表中的所有属性, 在新创建的目录下实例化为对应的文件 (例如, uevent 文件)。
  3. 错误回滚: 如果在创建属性文件时发生错误, 函数会执行回滚操作, 调用 sysfs_remove_dir() 删除之前已经创建的空目录, 以保证 sysfs 状态的一致性。
  4. 命名空间支持: 最后, 它会检查该 kobject 是否需要为其子对象提供特殊的命名空间过滤, 如果需要, 它会为新创建的目录启用此功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
* 静态函数声明: create_dir
* 这是一个内部函数, 负责为一个 kobject 在 sysfs 中创建目录和相关文件.
* @kobj: 指向需要为其创建目录的 kobject 的指针.
* @return: 成功时返回 0, 失败时返回负的错误码.
*/
static int create_dir(struct kobject *kobj)
{
/*
* 定义一个指向 const struct kobj_type 的指针 ktype.
* ktype 包含了 kobject 的通用属性, 如默认的 sysfs 文件组.
* get_ktype(kobj) 函数用于获取 kobj 关联的 kobj_type.
*/
const struct kobj_type *ktype = get_ktype(kobj);
/*
* 定义一个指向 kobj_ns_type_operations 的指针 ops.
* 这个结构体包含了一组函数指针, 用于处理子对象的命名空间过滤.
*/
const struct kobj_ns_type_operations *ops;
/*
* 定义一个整型变量 error, 用于存储函数调用的返回值.
*/
int error;

/*
* 调用 sysfs_create_dir_ns 在 sysfs 中创建一个目录.
* kobject_namespace(kobj) 函数获取此 kobj 关联的命名空间信息.
* 这个函数是实际在 sysfs 中创建目录的底层调用.
*/
error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
/*
* 检查目录创建是否失败.
*/
if (error)
/*
* 如果失败, 直接返回错误码.
*/
return error;

/*
* 检查此 kobject 是否有关联的 ktype.
*/
if (ktype) {
/*
* 调用 sysfs_create_groups 在新创建的目录下创建一组属性文件.
* ktype->default_groups 是一个属性组的数组, 定义了这类 kobject 默认应该有哪些 sysfs 文件.
*/
error = sysfs_create_groups(kobj, ktype->default_groups);
/*
* 检查属性文件组的创建是否失败.
*/
if (error) {
/*
* 如果失败, 执行回滚操作.
* 调用 sysfs_remove_dir 删除刚才成功创建的空目录, 保持 sysfs 状态的一致性.
*/
sysfs_remove_dir(kobj);
/*
* 返回错误码.
*/
return error;
}
}

/*
* kobj->sd 指向 sysfs 的目录项结构(sysfs_dirent). 这个目录项可能会因为其父目录的移除而被删除.
* 为了保证在当前 kobj 的生命周期内, 其对应的 sysfs 目录项一直有效,
* 这里通过 sysfs_get() 额外增加一次对该目录项的引用计数.
*/
sysfs_get(kobj->sd);

/*
* 检查此 kobj 是否为它的子对象定义了命名空间操作.
* kobj_child_ns_ops() 会返回这些操作的函数指针结构体.
*/
ops = kobj_child_ns_ops(kobj);
/*
* 如果定义了命名空间操作.
*/
if (ops) {
/*
* 使用 BUG_ON 进行健全性检查. BUG_ON 会在条件为真时触发内核 panic.
* 检查操作的类型是否有效, 以及该类型是否已经向内核注册.
* 这是一个硬性要求, 如果不满足, 说明存在严重的编程错误.
*/
BUG_ON(!kobj_ns_type_is_valid(ops->type));
BUG_ON(!kobj_ns_type_registered(ops->type));

/*
* 在 kobj 对应的 sysfs 目录项上启用命名空间支持.
* 这会告诉 sysfs 在列出此目录下的内容时, 需要根据查看者进程的命名空间进行过滤.
*/
sysfs_enable_ns(kobj->sd);
}

/*
* 所有操作成功, 返回 0.
*/
return 0;
}

kobj_kset_join

1
2
3
4
5
6
7
8
9
10
11
12
/* add the kobject to its kset's list */
static void kobj_kset_join(struct kobject *kobj)
{
if (!kobj->kset)
return;

kset_get(kobj->kset);
spin_lock(&kobj->kset->list_lock);
list_add_tail(&kobj->entry, &kobj->kset->list);
spin_unlock(&kobj->kset->list_lock);
}

kobject_add_internal: 将kobject添加到内核并创建sysfs目录

此函数是一个内部核心函数, 负责将一个已经初始化过的 kobject 添加到内核的对象层次结构中, 并在 sysfs 虚拟文件系统中为其创建一个对应的目录。这是在 sysfs 中展现任何内核对象 (如设备、驱动、总线、模块等) 的关键步骤。

其工作原理可以概括为以下几步:

  1. 验证和父对象确定: 函数首先验证 kobject 本身及其名称的有效性。然后, 它确定此 kobject 的父对象。如果 kobject->parent 已被明确指定, 则直接使用它; 如果没有, 但该 kobject 属于一个 kset, 那么该 kset 自身的 kobject 就会被作为父对象。它通过 kobject_get() 增加父对象的引用计数, 确保在子对象添加过程中父对象不会被释放。
  2. 加入Kset: 如果该 kobject 属于一个 kset, 函数会调用 kobj_kset_join() 将其加入到 kset 的内部链表中。这个操作由 kset 的自旋锁保护, 以确保链表操作的原子性。
  3. 创建Sysfs目录: 核心操作是调用 create_dir()。这个函数与 sysfs (底层为 kernfs) 交互, 在其父目录下创建一个以 kobject 的名字命名的新目录。
  4. 错误回滚: 如果 create_dir() 失败 (最常见的原因是同名目录已存在, 返回 -EEXIST), 函数会执行严格的回滚操作: 将 kobjectkset 链表中移除, 并减少父对象的引用计数, 将所有状态恢复到调用之前的样子, 然后返回错误码。
  5. 设置成功状态: 如果目录创建成功, 函数会将 kobjectstate_in_sysfs 标志设置为1, 表明它现在是 sysfs 中的一个活动对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
* 静态函数声明: kobject_add_internal
* 这是一个内部函数, 用于处理将 kobject 添加到内核对象模型中的具体逻辑.
* @kobj: 指向需要被添加的 kobject 的指针.
* @return: 成功时返回 0, 失败时返回负的错误码.
*/
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;

/*
* 检查传入的 kobj 指针是否有效. 如果为 NULL, 无法进行任何操作.
*/
if (!kobj)
/*
* 返回 -ENOENT (无此条目) 错误.
*/
return -ENOENT;

/*
* 检查 kobj 的名字是否有效. 一个没有名字的 kobject 是无意义的, 无法在 sysfs 中表示.
* kobj->name 必须指向一个非空的字符串.
*/
if (!kobj->name || !kobj->name[0]) {
/*
* 使用 WARN 宏打印一个严重的警告信息, 包括 kobject 的地址和堆栈回溯.
* 这表明有代码试图注册一个没有名字的 kobject, 是一个编程错误.
*/
WARN(1,
"kobject: (%p): attempted to be registered with empty name!\n",
kobj);
/*
* 返回 -EINVAL (无效参数) 错误.
*/
return -EINVAL;
}

/*
* 获取 kobj 指定的父 kobject 的指针, 并通过 kobject_get() 增加其引用计数.
* 这样做可以防止在子 kobject 的添加过程中, 父 kobject 被意外释放.
*/
parent = kobject_get(kobj->parent);

/*
* 如果此 kobj 属于一个 kset (kobj->kset 被设置).
*/
if (kobj->kset) {
/*
* 如果此 kobj 没有一个明确指定的父对象 (parent 为 NULL).
*/
if (!parent)
/*
* 则将其父对象设置为其所属 kset 的 kobject, 并增加 kset kobject 的引用计数.
* 这是一种常用模式, 对象的父目录就是它所属的集合的目录.
*/
parent = kobject_get(&kobj->kset->kobj);
/*
* 调用 kobj_kset_join 将此 kobj 加入其 kset 的链表中.
* 这个操作由 kset 的自旋锁保护, 保证了并发安全.
*/
kobj_kset_join(kobj);
/*
* 将 kobj 的父指针明确指向我们最终确定的 parent.
*/
kobj->parent = parent;
}

/*
* 打印一条调试信息, 显示正在添加的 kobj, 它的父对象以及它所属的 kset.
* 这在调试设备模型问题时非常有用.
*/
pr_debug("'%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

/*
* 调用 create_dir() 在 sysfs 中为这个 kobj 创建一个目录.
* 这是实际与文件系统层交互的一步.
*/
error = create_dir(kobj);
/*
* 检查目录创建是否失败.
*/
if (error) {
/*
* 如果创建失败, 执行回滚操作.
* 调用 kobj_kset_leave 将 kobj 从其 kset 的链表中移除 (如果之前加入了的话).
*/
kobj_kset_leave(kobj);
/*
* 调用 kobject_put 来减少父对象的引用计数, 与前面的 kobject_get() 相对应.
*/
kobject_put(parent);
/*
* 将 kobj 的父指针设为 NULL, 恢复到初始状态.
*/
kobj->parent = NULL;

/*
* 对错误情况进行明确的日志记录.
* 如果错误是 -EEXIST (文件已存在).
*/
if (error == -EEXIST)
/*
* 打印一条详细的错误信息, 明确指出不要在同一个目录下注册同名的东西.
*/
pr_err("%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
/*
* 对于其他错误, 打印一条通用的错误信息, 包含错误码和父对象名.
*/
pr_err("%s failed for %s (error: %d parent: %s)\n",
__func__, kobject_name(kobj), error,
parent ? kobject_name(parent) : "'none'");
} else
/*
* 如果 create_dir() 成功, 将 kobj 的 state_in_sysfs 标志设置为 1.
* 这表明此 kobject 现在已经在 sysfs 中可见并且处于活动状态.
*/
kobj->state_in_sysfs = 1;

/*
* 返回最终的错误码 (成功时为 0).
*/
return error;
}

kobject_add_varg 添加 kobject 到内核对象层次结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval;

retval = kobject_set_name_vargs(kobj, fmt, vargs);
if (retval) {
pr_err("can not set name properly!\n");
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}

kobject_add 用于将内核对象(kobject)添加到内核对象层次结构中的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* kobject_add() - 主要的 kobject 添加函数。
* @kobj: 要添加的 kobject。
* @parent: kobject 的父对象指针。
* @fmt: 用于命名 kobject 的格式字符串。
*
* 本函数会设置 kobject 的名称,并将其添加到 kobject 层次结构中。
*
* 如果设置了 @parent,则 @kobj 的父对象会被设置为它。
* 如果 @parent 为 NULL,则 @kobj 的父对象会被设置为分配给该 kobject 的 kset 关联的 kobject。
* 如果没有为 kobject 分配 kset,则该 kobject 会被放置在 sysfs 树的根目录下。
*
* 注意,此调用不会创建 "add" uevent,调用者应在为对象设置好所有必要的 sysfs 文件后,
* 调用 kobject_uevent() 并传递 UEVENT_ADD 参数,以确保用户空间能够正确收到该 kobject 创建的通知。
*
* 返回值:如果本函数返回错误,必须调用 kobject_put() 来正确清理与该对象相关的内存。
* 无论如何,都不应直接通过 kfree() 释放传递给本函数的 kobject,否则可能导致内存泄漏。
*
* 如果本函数返回成功,也必须调用 kobject_put(),以便正确释放与该对象相关的内存。
*
* 简而言之,一旦调用了本函数,使用结束后必须调用 kobject_put(),以正确释放所有资源。
*/
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args;
int retval;

if (!kobj)
return -EINVAL;

if (!kobj->state_initialized) {
pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",
kobject_name(kobj), kobj);
dump_stack_lvl(KERN_ERR);
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);

return retval;
}
EXPORT_SYMBOL(kobject_add);

kobject_create_and_add 动态创建和注册内核对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* kobject_create_and_add() - 动态创建一个 struct kobject 并注册到 sysfs。
* @name: kobject 的名称
* @parent: 此 kobject 的父 kobject(如果有)。
*
* 此函数动态创建一个 kobject 结构并将其注册到 sysfs。当你不再需要该结构时,
* 调用 kobject_put(),当引用计数为 0 时结构会被自动释放。
*
* 如果无法创建 kobject,则返回 NULL。
*/
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;

kobj = kobject_create();
if (!kobj)
return NULL;

retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
pr_warn("%s: kobject_add error: %d\n", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}
EXPORT_SYMBOL_GPL(kobject_create_and_add);

kobject_init_internal: 初始化一个 kobject

这是一个内部静态函数, 是Linux设备模型中所有对象初始化的最底层、最核心的步骤。它的作用是将一个 struct kobject 的所有成员设置为一个已知的、干净的初始状态, 包括它的引用计数器、链表节点以及各种状态标志。任何 kobject (无论是独立的, 还是内嵌在 devicedriverkset 中的) 在被添加到内核之前, 都必须经过这个初始化过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
* 静态函数声明: kobject_init_internal
* 'static' 关键字意味着此函数仅在定义它的文件中可见, 是一种内部辅助函数.
* @kobj: 指向需要被初始化的 kobject 结构体的指针.
*/
static void kobject_init_internal(struct kobject *kobj)
{
/*
* 检查传入的指针是否为 NULL, 这是一个基本的安全检查.
*/
if (!kobj)
return;
/*
* 调用 kref_init() 来初始化 kobject 内嵌的引用计数器 (kobj->kref).
* kref 是内核中用于管理对象生命周期的标准引用计数机制.
* kref_init() 会将引用计数器的值设置为 1.
* 当内核的其他部分获得对这个 kobject 的引用时, 计数会增加; 当释放引用时, 计数会减少.
* 当计数减到 0 时, kobject 及其包含的结构体就会被销毁和释放.
*/
kref_init(&kobj->kref);
/*
* 调用 INIT_LIST_HEAD 宏来初始化 kobject 中的 entry 成员.
* 这个 'entry' 是一个链表节点, 当这个 kobject 加入到一个 kset 时,
* 就是通过这个 'entry' 节点被链接到 kset 的 'list' 链表中的.
* 初始化操作将此节点的 next 和 prev 指针都指向它自身, 表示它当前未链接到任何链表中.
*/
INIT_LIST_HEAD(&kobj->entry);
/*
* 将 state_in_sysfs 标志位设置为 0 (假).
* 这个标志用于追踪该 kobject 当前是否已经被添加到 sysfs 中并可见.
* 初始状态下, 它显然不在 sysfs 中.
*/
kobj->state_in_sysfs = 0;
/*
* 将 state_add_uevent_sent 标志位设置为 0 (假).
* 这个标志用于记录是否已经为该 kobject 的添加操作发送了 uevent 事件.
* 这有助于内核核心在对象被移除时决定是否需要自动生成一个 "remove" 事件.
*/
kobj->state_add_uevent_sent = 0;
/*
* 将 state_remove_uevent_sent 标志位设置为 0 (假).
* 这个标志用于记录是否已经为该 kobject 的移除操作发送了 uevent 事件.
*/
kobj->state_remove_uevent_sent = 0;
/*
* 将 state_initialized 标志位设置为 1 (真).
* 这个标志表明该 kobject 已经成功执行了初始化操作.
* 内核的其他部分可以检查这个标志, 以防止意外地使用了未经初始化的 kobject.
*/
kobj->state_initialized = 1;
}

kset_init: 初始化一个 kset 以备使用

此函数的作用是对一个 struct kset 变量进行基础的初始化。一个 kset 在被注册到系统(kset_register)之前, 必须先通过此函数进行准备。它会设置 kset 内嵌的 kobject, 并初始化用于管理其成员的链表和保护该链表的自旋锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* kset_init() - 初始化一个 kset 以备使用.
* @k: 需要被初始化的 kset
*/
void kset_init(struct kset *k)
{
/*
* 调用 kobject_init_internal() 函数来初始化 kset 结构体中内嵌的 kobject 成员 (k->kobj).
* 因为 kset 本身也是一个 kobject (在 sysfs 中表现为一个目录), 所以它也需要进行 kobject 的标准初始化.
*/
kobject_init_internal(&k->kobj);
/*
* 调用 INIT_LIST_HEAD 宏来初始化 kset 中的 list 成员.
* 这个 'list' 是一个双向链表的头部, 未来所有加入到这个 kset 的 kobject 都会通过它们的 'entry' 成员链接到这个链表上.
*/
INIT_LIST_HEAD(&k->list);
/*
* 调用 spin_lock_init 宏来初始化 kset 中的 list_lock 自旋锁.
* 这个锁的作用是保护 'list' 链表. 在任何代码要添加、删除或遍历这个链表时, 都必须先获取此锁.
* 在 STM32H750 这样的单核抢占式系统上, 获取自旋锁的操作会禁用内核抢占,
* 这可以防止在操作链表的极短时间内, 当前任务被中断或被更高优先级的任务抢占, 从而保证了链表操作的原子性.
*/
spin_lock_init(&k->list_lock);
}

kset_register: 初始化并注册一个kset

此函数用于将一个 kset 结构体注册到Linux内核的 kobject 层次结构中。kset 本质上是一个 kobject 的集合或容器, 同时它自身也是一个 kobject, 因此它在 sysfs 文件系统中表现为一个目录, 这个目录可以包含其他的 kobject (表现为文件或其他子目录)。该函数是创建 sysfs 目录层次结构的关键步骤。

函数的核心原理如下:

  1. 验证: 首先, 它会验证传入的 kset 指针是否有效, 以及该 ksetkobject 是否关联了一个有效的 kobj_typekobj_type 描述了 kobject 的通用行为, 是 kobject 能被正确处理的前提。
  2. 初始化: 调用 kset_init() 来初始化 kset 内部用于链接其成员 kobject 的链表。
  3. 添加Kobject: 调用 kobject_add_internal()kset 自身的 kobject 添加到 sysfs 层次结构中。这一步是真正在 sysfs 中创建目录的操作。
  4. 发送Uevent: 在目录创建成功后, 调用 kobject_uevent() 发送一个 KOBJ_ADD 类型的 uevent 事件, 通知用户空间(例如udevmdev)有一个新的目录被创建了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* kset_register() - 初始化并添加一个 kset.
* @k: 要注册的 kset.
*
* 注意: 出错时, 由 kobj_set_name() 分配的 kset.kobj.name 内存会被释放,
* 它将不能再被使用.
*/
int kset_register(struct kset *k)
{
/*
* 定义一个整型变量 err, 用于存储错误码.
*/
int err;

/*
* 检查传入的 kset 指针 k 是否为 NULL.
* 这是一个基本的有效性检查.
*/
if (!k)
/*
* 如果指针无效, 返回 -EINVAL (无效参数) 错误码.
*/
return -EINVAL;

/*
* 检查 kset 内嵌的 kobject 是否关联了一个 ktype.
* ktype 包含了对 kobject 进行操作所必需的函数指针 (如 release 函数),
* 如果没有 ktype, kobject 将无法被正确管理, 尤其是内存释放.
*/
if (!k->kobj.ktype) {
/*
* 如果没有 ktype, 打印一条错误信息到内核日志.
*/
pr_err("must have a ktype to be initialized properly!\n");
/*
* 返回 -EINVAL (无效参数) 错误码.
*/
return -EINVAL;
}

/*
* 调用 kset_init() 函数来初始化 kset 的内部状态.
* 这个函数主要做的是初始化 kset->list 链表头, 这个链表将用于链接所有属于此 kset 的 kobject.
*/
kset_init(k);
/*
* 调用 kobject_add_internal() 将 kset 内嵌的 kobject 添加到内核的 kobject 层次结构中.
* 这个函数会处理 kobject 的父子关系链接, 并使其在 sysfs 中可见.
* 实际上就是创建 sysfs 目录的核心步骤.
*/
err = kobject_add_internal(&k->kobj);
/*
* 检查 kobject_add_internal() 是否返回了错误.
*/
if (err) {
/*
* 如果添加失败, 释放之前为 kobject 的名字分配的内存.
* k->kobj.name 通常是在调用此函数之前由 kobject_set_name() 分配的.
*/
kfree_const(k->kobj.name);
/*
* 将 k->kobj.name 设置为 NULL.
* 这是一个良好的实践, 可以防止调用者在出错后意外地访问一个已被释放的无效指针.
*/
k->kobj.name = NULL;
/*
* 返回从 kobject_add_internal() 收到的错误码.
*/
return err;
}
/*
* 调用 kobject_uevent() 来生成并发送一个 uevent.
* &k->kobj 是事件的源头, KOBJ_ADD 是事件的动作类型("add").
* 这会通知用户空间守护进程 (如 udev/mdev) 有一个新的 kobject (目录) 被添加到了系统中.
*/
kobject_uevent(&k->kobj, KOBJ_ADD);
/*
* 所有操作成功, 返回 0.
*/
return 0;
}
/*
* 将 kset_register 函数导出, 使其对其他内核模块可用.
* EXPORT_SYMBOL 适用于所有许可证兼容的模块 (GPL, BSD, MIT等).
*/
EXPORT_SYMBOL(kset_register);

lib/kobject_uevent.c

kobject_uevent_env: 发送带有环境变量的uevent

此函数负责构建一个 uevent 事件, 并将其从内核空间发送到用户空间。uevent 是内核用来通知用户空间守护进程(例如 udevmdev)有关内核对象状态变化(如设备添加、移除、绑定等)的核心机制。

该函数的工作原理如下:

  1. 初始化与过滤: 首先, 函数会沿着 kobject 的父子关系链向上查找, 直到找到其所属的 ksetkset 提供了一套 uevent_ops (uevent操作函数集)。内核可以利用这些操作函数来过滤事件 (决定是否发送) 或为事件添加额外信息。如果 kobjectuevent_suppress 标志被设置, 或者 ksetfilter 函数返回 false, 事件将被丢弃。
  2. 收集环境变量: 函数会分配一个 kobj_uevent_env 结构体来缓存环境变量。然后, 它会收集一系列标准的环境变量, 包括:
    • ACTION: 事件的类型, 例如 “add”、”remove”。
    • DEVPATH: 该对象在 sysfs 中的路径, 唯一标识了该对象。
    • SUBSYSTEM: 对象所属的子系统名称。
    • 调用者传入的额外变量 (envp_ext)。
    • ksetuevent 操作函数可能添加的特定变量。
  3. 生成序列号: 为了保证事件的唯一性和顺序性, 函数会原子地增加一个全局64位序列号 (uevent_seqnum), 并将其作为 SEQNUM 环境变量添加。
  4. 发送事件: 事件主要通过两种方式发送到用户空间:
    • Netlink套接字: 这是现代Linux系统的标准方法。函数调用 kobject_uevent_net_broadcast 将打包好的环境变量通过一个专门的Netlink套接字广播出去。用户空间的守护进程会监听这个套接字以接收事件。
    • Usermode Helper: 这是一个备用/兼容机制。如果配置了 CONFIG_UEVENT_HELPER, 内核会执行一个在 /proc/sys/kernel/hotplug 中指定的用户空间程序 (即 uevent_helper), 并将环境变量传递给它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/**
* kobject_uevent_env - 发送一个带有环境变量数据的 uevent
*
* @kobj: 发生事件的 struct kobject 对象
* @action: 正在发生的事件类型 (枚举 kobject_action)
* @envp_ext: 指向外部传入的环境变量字符串数组的指针, 以NULL结尾
*
* 如果 kobject_uevent_env() 成功完成, 返回 0; 如果失败, 则返回相应的错误码.
*/
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
/*
* 定义一个指向 kobj_uevent_env 结构体的指针 env.
* 这个结构体用于存储即将发送给用户空间的所有环境变量.
*/
struct kobj_uevent_env *env;
/*
* 定义一个指向常量字符的指针 action_string.
* kobject_actions 是一个全局数组, 将枚举 kobject_action 的值映射为字符串 (例如 KOBJ_ADD -> "add").
* 此行代码的作用是获取当前 action 对应的字符串表示.
*/
const char *action_string = kobject_actions[action];
/*
* 定义一个指向常量字符的指针 devpath, 并初始化为 NULL.
* 它将用于存储 kobj 在 sysfs 中的完整路径.
*/
const char *devpath = NULL;
/*
* 定义一个指向常量字符的指针 subsystem.
* 它将用于存储该 kobj 所属的子系统名称 (例如 "block", "net").
*/
const char *subsystem;
/*
* 定义一个指向 kobject 的指针 top_kobj.
* 它将用于向上追溯, 找到包含 kset 的那个 kobject.
*/
struct kobject *top_kobj;
/*
* 定义一个指向 kset 的指针 kset.
* kset 是一组 kobject 的集合, 它提供了 uevent 的通用处理函数.
*/
struct kset *kset;
/*
* 定义一个指向 kset_uevent_ops 结构体的指针 uevent_ops.
* 这个结构体包含了 kset 定义的 uevent 回调函数 (filter, name, uevent).
*/
const struct kset_uevent_ops *uevent_ops;
/*
* 定义一个整型变量 i, 并初始化为 0.
* 它将用作遍历 envp_ext 数组的索引.
*/
int i = 0;
/*
* 定义一个整型变量 retval, 并初始化为 0.
* 它将用于存储整个函数的返回值, 默认为成功.
*/
int retval = 0;

/*
* 如果事件是 KOBJ_REMOVE (移除), 无论后续操作成功与否, 都先设置 kobj->state_remove_uevent_sent 标志.
* 这是为了防止某些子系统在后续的自动清理流程中, 重复触发 "remove" 事件.
*/
if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;

/*
* pr_debug 是一个调试宏, 只有在内核配置了动态调试或编译时开启了DEBUG时才会打印信息.
* 打印当前 kobject 的名字, 地址和函数名, 用于调试.
*/
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);

/*
* 向上查找 kobj 所属的 kset.
* top_kobj 初始化为当前的 kobj.
*/
top_kobj = kobj;
/*
* 循环向上遍历父 kobject, 直到找到一个设置了 kset 成员的 kobject, 或者到达根 (parent 为 NULL).
* uevent 的属性通常由其所属的 kset 决定.
*/
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;

/*
* 如果向上追溯到顶层都没有找到 kset, 这是一个错误状态.
* 没有 kset 就无法确定如何处理 uevent.
*/
if (!top_kobj->kset) {
/*
* 打印调试信息, 表明尝试在一个没有 kset 的 kobject 上发送 uevent.
*/
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
"without kset!\n", kobject_name(kobj), kobj,
__func__);
/*
* 返回 -EINVAL (无效的参数) 错误.
*/
return -EINVAL;
}

/*
* 获取找到的 kset.
*/
kset = top_kobj->kset;
/*
* 获取 kset 关联的 uevent 操作函数集.
*/
uevent_ops = kset->uevent_ops;

/*
* 如果 kobj 的 uevent_suppress 标志被设置, 则直接抑制此事件.
*/
if (kobj->uevent_suppress) {
/*
* 打印调试信息, 表明事件被 uevent_suppress 标志抑制了.
*/
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
/*
* 成功返回 0, 因为抑制事件是预期的行为, 而不是一个错误.
*/
return 0;
}
/*
* 如果 kset 提供了 filter (过滤) 函数, 则调用它.
*/
if (uevent_ops && uevent_ops->filter)
/*
* 如果 filter 函数返回 0 (或 false), 表示此事件应该被过滤掉.
*/
if (!uevent_ops->filter(kobj)) {
/*
* 打印调试信息, 表明事件被 filter 函数丢弃.
*/
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
/*
* 成功返回 0.
*/
return 0;
}

/*
* 确定事件的 "SUBSYSTEM" 环境变量的值.
* 如果 kset 提供了 name 函数, 则调用它来获取子系统名称.
*/
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kobj);
else
/*
* 否则, 使用 kset 的 kobject 的名字作为子系统名称.
*/
subsystem = kobject_name(&kset->kobj);
/*
* 如果最终没有获得子系统名称, 这是一个无效状态, 事件将被丢弃.
*/
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}

/*
* 为环境变量缓冲区分配内存.
* kzalloc 会分配内存并将其初始化为零.
* GFP_KERNEL 表示在单核系统上, 如果内存不足, 进程可以睡眠等待.
*/
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
/*
* 如果内存分配失败, env 将为 NULL.
*/
if (!env)
/*
* 返回 -ENOMEM (内存不足) 错误.
*/
return -ENOMEM;

/*
* 调用 kobject_get_path 获取 kobj 在 sysfs 中的完整路径.
* 这个路径将作为 "DEVPATH" 环境变量的值.
*/
devpath = kobject_get_path(kobj, GFP_KERNEL);
/*
* 如果获取路径失败 (例如, 内存不足).
*/
if (!devpath) {
/*
* 将返回值设置为 -ENOENT (没有此文件或目录).
*/
retval = -ENOENT;
/*
* 跳转到 exit 清理代码.
*/
goto exit;
}

/*
* 添加默认的环境变量.
* 调用 add_uevent_var 将 "ACTION=<action_string>" 添加到 env 缓冲区.
*/
retval = add_uevent_var(env, "ACTION=%s", action_string);
/*
* 如果添加失败 (例如, 缓冲区满了).
*/
if (retval)
/*
* 跳转到 exit 清理代码.
*/
goto exit;
/*
* 添加 "DEVPATH=<devpath>" 到 env 缓冲区.
*/
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
/*
* 添加 "SUBSYSTEM=<subsystem>" 到 env 缓冲区.
*/
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;

/*
* 如果调用者通过 envp_ext 传入了额外的环境变量.
*/
if (envp_ext) {
/*
* 循环遍历 envp_ext 数组, 直到遇到 NULL 元素.
*/
for (i = 0; envp_ext[i]; i++) {
/*
* 将每个字符串原样添加到 env 缓冲区.
*/
retval = add_uevent_var(env, "%s", envp_ext[i]);
/*
* 如果添加失败, 则退出循环并跳转到 exit.
*/
if (retval)
goto exit;
}
}

/*
* 如果 kset 提供了 uevent 函数, 调用它, 让 kset 有机会添加自己特定的环境变量.
*/
if (uevent_ops && uevent_ops->uevent) {
/*
* 调用 kset 的 uevent 函数, 传入 kobj 和 env 缓冲区.
*/
retval = uevent_ops->uevent(kobj, env);
/*
* 如果该函数返回一个错误码.
*/
if (retval) {
/*
* 打印调试信息, 表明 kset 的 uevent 函数返回了错误.
*/
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
/*
* 跳转到 exit 清理代码.
*/
goto exit;
}
}

/*
* 根据事件类型做一些额外的处理.
*/
switch (action) {
case KOBJ_ADD:
/*
* 对于 "add" 事件, 设置 kobj->state_add_uevent_sent 标志.
* 这个标志用来确保如果发送了 "add" 事件, 内核核心将在对象被销毁时,
* 自动生成一个对应的 "remove" 事件 (如果调用者自己没有发送的话).
*/
kobj->state_add_uevent_sent = 1;
break;

case KOBJ_UNBIND:
/*
* 对于 "unbind" 事件, 调用 zap_modalias_env 函数.
* 这个函数会从环境变量中移除 MODALIAS 变量, 因为解绑后, 设备的 modalias 可能不再有效.
*/
zap_modalias_env(env);
break;

default:
/*
* 其他事件类型不做特殊处理.
*/
break;
}

/*
* 我们即将发送一个事件, 因此需要请求一个新的序列号.
* 调用 add_uevent_var 将 "SEQNUM=<新序列号>" 添加到 env 缓冲区.
* atomic64_inc_return 会原子地增加全局 uevent_seqnum 计数器并返回新值.
* 在单核系统上, 原子操作确保了在抢占或中断情况下, 计数器不会被破坏.
*/
retval = add_uevent_var(env, "SEQNUM=%llu",
atomic64_inc_return(&uevent_seqnum));
if (retval)
goto exit;

/*
* 通过 netlink 套接字将 uevent 广播给用户空间. 这是主要的 uevent 发送机制.
*/
// retval = kobject_uevent_net_broadcast(kobj, env, action_string,
// devpath);

/*
* 如果内核配置了 CONFIG_UEVENT_HELPER, 则启用 usermode-helper 机制.
* 这通常只在系统启动早期, netlink 机制还未完全就绪时使用.
*/
#ifdef CONFIG_UEVENT_HELPER
/*
* uevent_helper[0] 检查 /proc/sys/kernel/hotplug 是否设置了帮助程序路径.
* kobj_usermode_filter(kobj) 检查此 kobject 是否被过滤, 不应调用帮助程序.
*/
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
/*
* 定义一个指向 subprocess_info 的指针 info, 用于启动用户空间程序.
*/
struct subprocess_info *info;

/*
* 为帮助程序添加一些基本的环境变量.
*/
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
/*
* 初始化帮助程序的命令行参数 (argv). 通常是 [helper_path, subsystem].
*/
retval = init_uevent_argv(env, subsystem);
if (retval)
goto exit;

/*
* 设置默认错误码, 以防 call_usermodehelper_setup 失败.
*/
retval = -ENOMEM;
/*
* 准备调用用户模式帮助程序所需的信息.
* 包括程序路径, 命令行参数, 环境变量, 以及一个清理函数.
*/
info = call_usermodehelper_setup(env->argv[0], env->argv,
env->envp, GFP_KERNEL,
NULL, cleanup_uevent_env, env);
/*
* 如果准备成功.
*/
if (info) {
/*
* 以非阻塞方式 (UMH_NO_WAIT) 执行帮助程序.
* 内核不会等待该程序执行完成.
*/
retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
/*
* env 被传递给了帮助程序, 将由清理函数 cleanup_uevent_env 释放.
* 将 env 置为 NULL, 防止在下面的 exit 标签中被重复释放.
*/
env = NULL;
}
}
#endif

/*
* 清理和退出标签.
*/
exit:
/*
* 释放为 devpath 分配的内存.
*/
kfree(devpath);
/*
* 释放为 env 缓冲区分配的内存 (除非它已经被传递给了 usermode-helper).
*/
kfree(env);
/*
* 返回最终的结果码.
*/
return retval;
}
/*
* 将 kobject_uevent_env 函数导出, 使其对其他内核模块可用 (遵循GPL许可证).
*/
EXPORT_SYMBOL_GPL(kobject_uevent_env);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* kobject_uevent - notify userspace by sending an uevent
*
* @kobj: struct kobject that the action is happening to
* @action: action that is happening
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
EXPORT_SYMBOL_GPL(kobject_uevent);