[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
的功能可能更庞杂。后来,其核心功能被清晰地提炼为三个:
引用计数(kref
) :用于对象生命周期管理。
层次关系(parent
指针和kset
) :用于构建对象树。
与sysfs的关联 :用于在用户空间展现对象及其属性。
目前该技术的社区活跃度和主流应用情况如何? kobject
是Linux内核中最基础、最稳定、最核心的组件之一。它不是一个可选功能,而是整个驱动模型、设备管理、模块管理和cgroup管理的基石。
无处不在 :内核中几乎所有需要结构化表示并暴露给用户空间的对象都内嵌了一个kobject
。这包括struct device
, struct device_driver
, struct bus_type
, struct module
, struct cgroup
等。
稳定成熟 :其核心API和行为已经非常稳定,社区活动主要集中在修复一些由其复杂性导致的微妙bug,而不是进行大的架构变动。
核心原理与设计 它的核心工作原理是什么? kobject
本身并不是一个庞大的、独立的数据结构,而是一个小型的、被嵌入 到其他更大的数据结构中的“组件”。它的核心工作原理围绕以下三点展开:
生命周期管理 (通过 kref
) :
kobject
内部包含一个struct kref
成员,这是一个原子引用计数器。
当一个kobject
通过kobject_init()
初始化时,其引用计数被设为1。
其他代码可以通过kobject_get()
来增加引用计数,表示它们正在“使用”这个对象。
使用完毕后,必须调用kobject_put()
来减少引用计数。
当引用计数减到0时,kobject_put()
会触发一个预先设定的release
回调函数(定义在kobject
的ktype
中)。这个release
函数是真正负责释放包含kobject
的宿主数据结构的内存的地方。这个机制从根本上解决了“何时可以安全释放对象”的问题。
层次结构构建 :
kobject
包含一个parent
指针,可以直接指向另一个kobject
,从而形成一个树状的层次结构。
它还引入了kset
的概念。一个kset
是一个kobject
的集合,它本身也是一个kobject
。kset
可以作为其内部所有kobject
的父节点,用于将一组相关的对象组织在一起。
Sysfs视图 :
kobject
是内核对象与sysfs
之间的桥梁。当一个kobject
通过kobject_add()
被注册到内核中时,sysfs
会在对应的父目录下为它创建一个新的目录 。
每个kobject
都可以关联一组属性(attributes) 。每个属性在sysfs
中表现为该目录下的一个文件 。
对这个文件的读写操作,会触发内核中预先定义好的show
和store
回调函数,从而允许用户空间读取或修改内核对象的状态。
它的主要优势体现在哪些方面?
统一性 :为内核中所有需要管理的对象提供了一个统一的模型。
健壮的生命周期管理 :引用计数机制极大地减少了use-after-free和内存泄漏等问题。
结构清晰 :能够清晰地反映内核中对象的层次关系。
自动化的用户空间接口 :一旦一个对象正确地使用了kobject
,它就能“免费”获得一个结构清晰、访问受控的sysfs
接口。
它存在哪些已知的劣势、局-限性或在特定场景下的不适用性?
复杂性 :kobject
的规则,特别是引用计数的正确配对(get
/put
),对于初学者来说是复杂且容易出错的。一个未配对的kobject_put()
可能导致过早释放,而一个遗漏的kobject_put()
则会导致内存泄漏。
“重量级” :对于一些非常简单的、内部使用的、不需要暴露给用户空间的数据结构,引入一个kobject
会带来不必要的开销和复杂性。
它只是一个机制 :kobject
本身不“做”任何具体的事情。它只是一个框架,所有具体的功能(如文件读写)都依赖于开发者提供的回调函数。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? 在内核中,只要你需要创建一个需要长期存在、被多处引用、需要结构化地展现给用户空间 的对象,kobject
就是唯一且标准 的解决方案。
例一:设备 (struct device
) 内核中代表任何物理或虚拟设备的struct device
是kobject
最典型的用户。struct device
内嵌了一个kobject
。当一个USB鼠标被插入时,内核会为其创建一个device
对象。其kobject
的parent
会指向代表它所连接的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_head
或hlist_node
。kobject
是用来构建树 的。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
kobject
struct device
kref
核心功能
通用的内核对象 。提供三大核心机制:引用计数、层次结构、sysfs接口。
具体的设备对象 。描述一个真实的硬件或软件设备。
纯粹的引用计数器 。只提供安全的生命周期管理。
实现方式
一个可被嵌入 的结构体。
一个**内嵌了kobject
**的、更大的结构体。
一个可被嵌入的、更小的结构体。
包含内容
kref
, parent
指针, kset
指针, ktype
, name
等。
包含一个kobject
,并额外增加了总线指针、驱动数据、电源管理回调、DMA配置等设备相关的字段。
只包含一个atomic_t
计数器。
与Sysfs关系
紧密耦合 。kobject
是sysfs
中目录的直接来源。
通过其内嵌的kobject
与sysfs
交互。
无任何关系 。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; }; 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 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 static inline void kref_get (struct kref *kref) { refcount_inc(&kref->refcount); } 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 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 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 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, }; 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 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; 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 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 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
类型所定义的默认属性文件来填充这个目录。
函数的执行原理如下:
创建目录 : 它首先调用 sysfs_create_dir_ns()
在 sysfs
中为 kobject
创建一个空的目录, 同时处理可能的命名空间(namespace)属性。
创建属性文件 : 目录创建成功后, 它会获取 kobject
的类型(kobj_type
), 并从该类型定义中找到一个默认的属性组列表 (default_groups
)。然后, 它调用 sysfs_create_groups()
将这个列表中的所有属性, 在新创建的目录下实例化为对应的文件 (例如, uevent
文件)。
错误回滚 : 如果在创建属性文件时发生错误, 函数会执行回滚操作, 调用 sysfs_remove_dir()
删除之前已经创建的空目录, 以保证 sysfs
状态的一致性。
命名空间支持 : 最后, 它会检查该 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 static int create_dir (struct kobject *kobj) { const struct kobj_type *ktype = get_ktype(kobj); const struct kobj_ns_type_operations *ops ; int error; error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); if (error) return error; if (ktype) { error = sysfs_create_groups(kobj, ktype->default_groups); if (error) { sysfs_remove_dir(kobj); return error; } } sysfs_get(kobj->sd); ops = kobj_child_ns_ops(kobj); if (ops) { BUG_ON(!kobj_ns_type_is_valid(ops->type)); BUG_ON(!kobj_ns_type_registered(ops->type)); sysfs_enable_ns(kobj->sd); } return 0 ; }
kobj_kset_join 1 2 3 4 5 6 7 8 9 10 11 12 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
中展现任何内核对象 (如设备、驱动、总线、模块等) 的关键步骤。
其工作原理可以概括为以下几步:
验证和父对象确定 : 函数首先验证 kobject
本身及其名称的有效性。然后, 它确定此 kobject
的父对象。如果 kobject->parent
已被明确指定, 则直接使用它; 如果没有, 但该 kobject
属于一个 kset
, 那么该 kset
自身的 kobject
就会被作为父对象。它通过 kobject_get()
增加父对象的引用计数, 确保在子对象添加过程中父对象不会被释放。
加入Kset : 如果该 kobject
属于一个 kset
, 函数会调用 kobj_kset_join()
将其加入到 kset
的内部链表中。这个操作由 kset
的自旋锁保护, 以确保链表操作的原子性。
创建Sysfs目录 : 核心操作是调用 create_dir()
。这个函数与 sysfs
(底层为 kernfs
) 交互, 在其父目录下创建一个以 kobject
的名字命名的新目录。
错误回滚 : 如果 create_dir()
失败 (最常见的原因是同名目录已存在, 返回 -EEXIST
), 函数会执行严格的回滚操作: 将 kobject
从 kset
链表中移除, 并减少父对象的引用计数, 将所有状态恢复到调用之前的样子, 然后返回错误码。
设置成功状态 : 如果目录创建成功, 函数会将 kobject
的 state_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 static int kobject_add_internal (struct kobject *kobj) { int error = 0 ; struct kobject *parent ; if (!kobj) return -ENOENT; if (!kobj->name || !kobj->name[0 ]) { WARN(1 , "kobject: (%p): attempted to be registered with empty name!\n" , kobj); return -EINVAL; } parent = kobject_get(kobj->parent); if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj); kobj_kset_join(kobj); kobj->parent = parent; } 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>" ); error = create_dir(kobj); if (error) { kobj_kset_leave(kobj); kobject_put(parent); kobj->parent = NULL ; 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 kobj->state_in_sysfs = 1 ; 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 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 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
(无论是独立的, 还是内嵌在 device
、driver
或 kset
中的) 在被添加到内核之前, 都必须经过这个初始化过程。
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 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 ; }
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 void kset_init (struct kset *k) { kobject_init_internal(&k->kobj); INIT_LIST_HEAD(&k->list ); spin_lock_init(&k->list_lock); }
kset_register: 初始化并注册一个kset 此函数用于将一个 kset
结构体注册到Linux内核的 kobject
层次结构中。kset
本质上是一个 kobject
的集合或容器, 同时它自身也是一个 kobject
, 因此它在 sysfs
文件系统中表现为一个目录, 这个目录可以包含其他的 kobject
(表现为文件或其他子目录)。该函数是创建 sysfs
目录层次结构的关键步骤。
函数的核心原理如下:
验证 : 首先, 它会验证传入的 kset
指针是否有效, 以及该 kset
的 kobject
是否关联了一个有效的 kobj_type
。kobj_type
描述了 kobject
的通用行为, 是 kobject
能被正确处理的前提。
初始化 : 调用 kset_init()
来初始化 kset
内部用于链接其成员 kobject
的链表。
添加Kobject : 调用 kobject_add_internal()
将 kset
自身的 kobject
添加到 sysfs
层次结构中。这一步是真正在 sysfs
中创建目录的操作。
发送Uevent : 在目录创建成功后, 调用 kobject_uevent()
发送一个 KOBJ_ADD
类型的 uevent
事件, 通知用户空间(例如udev
或mdev
)有一个新的目录被创建了。
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 int kset_register (struct kset *k) { int err; if (!k) return -EINVAL; if (!k->kobj.ktype) { pr_err("must have a ktype to be initialized properly!\n" ); return -EINVAL; } kset_init(k); err = kobject_add_internal(&k->kobj); if (err) { kfree_const(k->kobj.name); k->kobj.name = NULL ; return err; } kobject_uevent(&k->kobj, KOBJ_ADD); return 0 ; } EXPORT_SYMBOL(kset_register);
lib/kobject_uevent.c kobject_uevent_env: 发送带有环境变量的uevent 此函数负责构建一个 uevent
事件, 并将其从内核空间发送到用户空间。uevent
是内核用来通知用户空间守护进程(例如 udev
或 mdev
)有关内核对象状态变化(如设备添加、移除、绑定等)的核心机制。
该函数的工作原理如下:
初始化与过滤 : 首先, 函数会沿着 kobject
的父子关系链向上查找, 直到找到其所属的 kset
。kset
提供了一套 uevent_ops
(uevent操作函数集)。内核可以利用这些操作函数来过滤事件 (决定是否发送) 或为事件添加额外信息。如果 kobject
的 uevent_suppress
标志被设置, 或者 kset
的 filter
函数返回 false
, 事件将被丢弃。
收集环境变量 : 函数会分配一个 kobj_uevent_env
结构体来缓存环境变量。然后, 它会收集一系列标准的环境变量, 包括:
ACTION
: 事件的类型, 例如 “add”、”remove”。
DEVPATH
: 该对象在 sysfs
中的路径, 唯一标识了该对象。
SUBSYSTEM
: 对象所属的子系统名称。
调用者传入的额外变量 (envp_ext
)。
kset
的 uevent
操作函数可能添加的特定变量。
生成序列号 : 为了保证事件的唯一性和顺序性, 函数会原子地增加一个全局64位序列号 (uevent_seqnum
), 并将其作为 SEQNUM
环境变量添加。
发送事件 : 事件主要通过两种方式发送到用户空间:
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 int kobject_uevent_env (struct kobject *kobj, enum kobject_action action, char *envp_ext[]) { struct kobj_uevent_env *env ; const char *action_string = kobject_actions[action]; const char *devpath = NULL ; const char *subsystem; struct kobject *top_kobj ; struct kset *kset ; const struct kset_uevent_ops *uevent_ops ; int i = 0 ; int retval = 0 ; if (action == KOBJ_REMOVE) kobj->state_remove_uevent_sent = 1 ; pr_debug("kobject: '%s' (%p): %s\n" , kobject_name(kobj), kobj, __func__); top_kobj = kobj; while (!top_kobj->kset && top_kobj->parent) top_kobj = top_kobj->parent; if (!top_kobj->kset) { pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " "without kset!\n" , kobject_name(kobj), kobj, __func__); return -EINVAL; } kset = top_kobj->kset; uevent_ops = kset->uevent_ops; if (kobj->uevent_suppress) { pr_debug("kobject: '%s' (%p): %s: uevent_suppress " "caused the event to drop!\n" , kobject_name(kobj), kobj, __func__); return 0 ; } if (uevent_ops && uevent_ops->filter) if (!uevent_ops->filter(kobj)) { pr_debug("kobject: '%s' (%p): %s: filter function " "caused the event to drop!\n" , kobject_name(kobj), kobj, __func__); return 0 ; } if (uevent_ops && uevent_ops->name) subsystem = uevent_ops->name(kobj); else 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 ; } env = kzalloc(sizeof (struct kobj_uevent_env), GFP_KERNEL); if (!env) return -ENOMEM; devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit ; } retval = add_uevent_var(env, "ACTION=%s" , action_string); if (retval) goto exit ; retval = add_uevent_var(env, "DEVPATH=%s" , devpath); if (retval) goto exit ; retval = add_uevent_var(env, "SUBSYSTEM=%s" , subsystem); if (retval) goto exit ; if (envp_ext) { for (i = 0 ; envp_ext[i]; i++) { retval = add_uevent_var(env, "%s" , envp_ext[i]); if (retval) goto exit ; } } if (uevent_ops && uevent_ops->uevent) { retval = uevent_ops->uevent(kobj, env); if (retval) { pr_debug("kobject: '%s' (%p): %s: uevent() returned " "%d\n" , kobject_name(kobj), kobj, __func__, retval); goto exit ; } } switch (action) { case KOBJ_ADD: kobj->state_add_uevent_sent = 1 ; break ; case KOBJ_UNBIND: zap_modalias_env(env); break ; default : break ; } retval = add_uevent_var(env, "SEQNUM=%llu" , atomic64_inc_return(&uevent_seqnum)); if (retval) goto exit ; #ifdef CONFIG_UEVENT_HELPER if (uevent_helper[0 ] && !kobj_usermode_filter(kobj)) { 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 ; retval = init_uevent_argv(env, subsystem); if (retval) goto exit ; retval = -ENOMEM; info = call_usermodehelper_setup(env->argv[0 ], env->argv, env->envp, GFP_KERNEL, NULL , cleanup_uevent_env, env); if (info) { retval = call_usermodehelper_exec(info, UMH_NO_WAIT); env = NULL ; } } #endif exit : kfree(devpath); kfree(env); return retval; } EXPORT_SYMBOL_GPL(kobject_uevent_env);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int kobject_uevent (struct kobject *kobj, enum kobject_action action) { return kobject_uevent_env(kobj, action, NULL ); } EXPORT_SYMBOL_GPL(kobject_uevent);