Linux设备模型之Kobject
1. Linux设备模型之Kobject
Kobject
是Linux设备模型的基础,也是设备模型中最难理解的一部分(可参考Documentation/kobject.txt
的表述)。
1.1 基本概念
Linux设备模型的核心是使用Bus
、Class
、Device
、Driver
四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。
而硬件设备的数量、种类是非常多的,这就决定了Kernel中将会有大量的有关设备模型的数据结构。这些数据结构一定有一些共同的功能,需要抽象出来统一实现,否则就会不可避免的产生冗余代码。这就是Kobject诞生的背景。
目前为止,Kobject主要提供如下功能:
- 通过
parent
指针,可以将所有Kobject
以层次结构的形式组合起来。 - 使用一个引用计数(
reference count
),来记录Kobject
被引用的次数,并在引用次数变为0
时把它释放(这是Kobject
诞生时的唯一功能)。 - 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。
- 在Linux中,
Kobject
几乎不会单独存在。它的主要功能,就是内嵌在一个大型的数据结构中,为这个数据结构提供一些底层的功能实现。- Linux driver开发者,很少会直接使用
Kobject
以及它提供的接口,而是使用构建在Kobject
之上的设备模型接口。
1.2 代码解析
在Kernel源代码中,Kobject由如下两个文件实现:
include/linux/kobject.h
lib/kobject.c
其中kobject.h
为Kobject
的头文件,包含所有的数据结构定义和接口声明。kobject.c`为核心功能的实现。
1.2.1 主要的数据结构
在描述数据结构之前,有必要说明一下Kobject
, Kset
和Ktype
这三个概念。
Kobject
是基本数据类型,每个Kobject
都会在/sys/
文件系统中以目录的形式出现。Ktype
代表Kobject
(严格地讲,是包含了Kobject
的数据结构)的属性操作集合(由于通用性,多个Kobject
可能共用同一个属性操作集,因此把Ktype
独立出来了)。Kset
是一个特殊的Kobject
(因此它也会在/sys/
文件系统中以目录的形式出现),它用来集合相似的Kobject
(这些Kobject
可以是相同属性的,也可以不同属性的)。
-
Kobject
struct kobject { const char *name;/*在sys目录下建立目录的名字*/ struct list_head entry;/*用于连接到所属kset链表中*/ struct kobject *parent;/*父对象*/ struct kset *kset;/*属于哪个kset*/ struct kobj_type *ktype;/*类型*/ struct kernfs_node *sd; /* sysfs directory entry *//*sys中与该对象对应的文件节点*/ struct kref kref;/*对象的应用计数*/ #ifdef CONFIG_DEBUG_KOBJECT_RELEASE struct delayed_work release; #endif unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1; };
name,该Kobject的名称,同时也是sysfs中的目录名称。由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。 entry,用于将Kobject加入到Kset中的list_head。 parent,指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。 kset,该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(别忘了Kset是一个特殊的Kobject)。 ktype,该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。 sd,该Kobject在sysfs中的表示。 kref,"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。 state_initialized,指示该Kobject是否已经初始化,以在Kobject的Init,Put,Add等操作时进行异常校验。 state_in_sysfs,指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。 state_add_uevent_sent/state_remove_uevent_sent,记录是否已经向用户空间发送ADD uevent,如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,以便让用户空间正确处理。 uevent_suppress,如果该字段为1,则表示忽略所有上报的uevent事件。 注4:Uevent提供了“用户空间通知”的功能实现,通过该功能,当内核中有Kobject的增加、删除、修改等动作时,会通知用户空间。有关该功能的具体内容,会在其它文章详细描述。
-
Kset
/** * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem. * * A kset defines a group of kobjects. They can be individually * different "types" but overall these kobjects all want to be grouped * together and operated on in the same manner. ksets are used to * define the attribute callbacks and other common events that happen to * a kobject. * * @list: the list of all kobjects for this kset * @list_lock: a lock for iterating over the kobjects * @kobj: the embedded kobject for this kset (recursion, isn't it fun...) * @uevent_ops: the set of uevent operations for this kset. These are * called whenever a kobject has something happen to it so that the kset * can add new environment variables, or filter out the uevents if so * desired. */ struct kset { struct list_head list; spinlock_t list_lock; struct kobject kobj; const struct kset_uevent_ops *uevent_ops; } __randomize_layout;
list/list_lock,用于保存该kset下所有的kobject的链表。 kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。 uevent_ops,该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
-
Ktype
struct kobj_type { void (*release)(struct kobject *kobj); const struct sysfs_ops *sysfs_ops; const struct attribute_group **default_groups; const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid); };
release,通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。 sysfs_ops,该种类型的Kobject的sysfs文件系统接口。 default_attrs,该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中。 child_ns_type/namespace,和文件系统(sysfs)的命名空间有关,这里不再详细说明。
1.3 Ktype以及整个Kobject机制的理解。
Kobject
的核心功能是:保持一个引用计数,当该计数减为0
时,自动释放(由本文所讲的kobject
模块负责) Kobject
所占用的meomry
空间。这就决定了Kobject
必须是动态分配的(只有这样才能动态释放)。
而Kobject
大多数的使用场景,是内嵌在大型的数据结构中(如Kset
、device_driver
等),因此这些大型的数据结构,也必须是动态分配、动态释放的。那么释放的时机是什么呢?是内嵌的Kobject
释放时。但是Kobject
的释放是由Kobject
模块自动完成的(在引用计数为0
时),那么怎么一并释放包含自己的大型数据结构呢?
这时Ktype
就派上用场了。我们知道,Ktype
中的release
回调函数负责释放Kobject
(甚至是包含Kobject
的数据结构)的内存空间,那么Ktype
及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject
嵌在哪个数据结构中,并通过Kobject
指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。
讲到这里,就清晰多了。所以,每一个内嵌Kobject
的数据结构,例如kset
、device
、device_driver
等等,都要实现一个Ktype
,并定义其中的回调函数。同理,sysfs
相关的操作也一样,必须经过ktype
的中转,因为sysfs
看到的是Kobject
,而真正的文件操作的主体,是内嵌Kobject
的上层数据结构!
Kobject
是面向对象的思想在Linux kernel中的极致体现,但C语言的优势却不在这里,所以Linux kernel需要用比较巧妙(也很啰嗦)的手段去实现,
1.4 功能分析
1.4.1 Kobject使用流程
Kobject大多数情况下(有一种例外)会嵌在其它数据结构中使用,其使用流程如下:
- 定义一个
struct kset
类型的指针,并在初始化时为它分配空间,添加到内核中 - 根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有
Kobject
- 定义一个适合自己的
ktype
,并实现其中回调函数 - 在需要使用到包含
Kobject
的数据结构时,动态分配该数据结构,并分配Kobject
空间,添加到内核中 - 每一次引用数据结构时,调用
kobject_ge
t接口增加引用计数;引用结束时,调用kobject_put
接口,减少引用计数 - 当引用计数减少为
0
时,Kobject
模块调用ktype
所提供的release
接口,释放上层数据结构以及Kobject
的内存空间
上面有提过,有一种例外,Kobject
不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs
中创建一个目录,而不需要其它的kset
、ktype
的操作。这时可以直接调用kobject_create_and_add
接口,分配一个kobject
结构并把它添加到kernel中。
1.4.2 Kobject的分配和释放
Kobject
必须动态分配,而不能静态定义或者位于堆栈之上,它的分配方法有两种。
-
通过
kmalloc
自行分配(一般是跟随上层数据结构分配),并在初始化后添加到kernel。这种方法涉及如下接口:extern void kobject_init(struct kobject *kobj, const struct kobj_type *ktype); extern __printf(3, 4) __must_check int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...); extern __printf(4, 5) __must_check int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);
kobject_init,初始化通过kmalloc等内存分配函数获得的struct kobject指针。主要执行逻辑为: 确认kobj和ktype不为空 如果该指针已经初始化过(判断kobj->state_initialized),打印错误提示及堆栈信息(但不是致命错误,所以还可以继续) 初始化kobj内部的参数,包括引用计数、list、各种标志等 根据输入参数,将ktype指针赋予kobj->ktype kobject_add,将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,可以为空)、用于提供kobject name的格式化字符串。主要执行逻辑为: 确认kobj不为空,确认kobj已经初始化,否则错误退出 调用内部接口kobject_add_varg,完成添加操作 kobject_init_and_add,是上面两个接口的组合,不再说明。 ==========================内部接口====================================== kobject_add_varg,解析格式化字符串,将结果赋予kobj->name,之后调用kobject_add_internal接口,完成真正的添加操作。 kobject_add_internal,将kobject添加到kernel。主要执行逻辑为: 校验kobj以及kobj->name的合法性,若不合法打印错误信息并退出 调用kobject_get增加该kobject的parent的引用计数(如果存在parent的话) 如果存在kset(即kobj->kset不为空),则调用kobj_kset_join接口加入kset。同时,如果该kobject没有parent,却存在kset,则将它的parent设为kset(kset是一个特殊的kobject),并增加kset的引用计数 通过create_dir接口,调用sysfs的相关接口,在sysfs下创建该kobject对应的目录 如果创建失败,执行后续的回滚操作,否则将kobj->state_in_sysfs置为1 kobj_kset_join,负责将kobj加入到对应kset的链表中。
这种方式分配的kobject,会在引用计数变为0时,由kobject_put调用其ktype的release接口,释放内存空间,具体可参考kobject_put。
-
使用kobject_create创建
Kobject
模块可以使用kobject_create
自行分配空间,并内置了一个ktype
(dynamic_kobj_ktype
),用于在计数为0
时释放空间。代码如下:extern struct kobject * __must_check kobject_create_and_add(const char *name, struct kobject *parent);
static void dynamic_kobj_release(struct kobject *kobj) { pr_debug("kobject: (%p): %s\n", kobj, __func__); kfree(kobj); } static struct kobj_type dynamic_kobj_ktype = { .release = dynamic_kobj_release, .sysfs_ops = &kobj_sysfs_ops, };
kobject_create,该接口为kobj分配内存空间,并以dynamic_kobj_ktype为参数,调用kobject_init接口,完成后续的初始化操作。
kobject_create_and_add,是kobject_create和kobject_add的组合,不再说明。
dynamic_kobj_release,直接调用kfree释放kobj的空间。
1.4.3 Kobject引用计数的修改
通过kobject_get
和kobject_put
可以修改kobject
的引用计数,并在计数为0
时,调用ktype
的release
接口,释放占用空间。
extern struct kobject *kobject_get(struct kobject *kobj);
extern struct kobject * __must_check kobject_get_unless_zero(
struct kobject *kobj);
extern void kobject_put(struct kobject *kobj);
kobject_get,调用kref_get,增加引用计数。
kobject_put,以内部接口kobject_release为参数,调用kref_put。kref模块会在引用计数为零时,调用kobject_release。
========================== 内部接口======================================
kobject_release,通过kref结构,获取kobject指针,并调用kobject_cleanup接口继续。
kobject_cleanup,负责释放kobject占用的空间,主要执行逻辑如下:
- 检查该kobject是否有ktype,如果没有,打印警告信息
- 如果该kobject向用户空间发送了ADD uevent但没有发送REMOVE uevent,补发REMOVE uevent
- 如果该kobject有在sysfs文件系统注册,调用kobject_del接口,删除它在sysfs中的注册
- 调用该kobject的ktype的release接口,释放内存空间
- 释放该kobject的name所占用的内存空间
1.4.4 Kset的初始化、注册
Kset
是一个特殊的kobject
,因此其初始化、注册等操作也会调用kobject
的相关接口,除此之外,会有它特有的部分。另外,和Kobject
一样,kset
的内存分配,可以由上层软件通过kmalloc
自行分配,也可以由Kobject
模块负责分配,具体如下。
extern void kset_init(struct kset *kset);
extern int __must_check kset_register(struct kset *kset);
extern void kset_unregister(struct kset *kset);
extern struct kset * __must_check kset_create_and_add(const char *name,
const struct kset_uevent_ops *u,
struct kobject *parent_kobj);
kset_init,该接口用于初始化已分配的kset,主要包括调用kobject_init_internal初始化其kobject,然后初始化kset的链表。需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype。
kset_register,先调用kset_init,然后调用kobject_add_internal将其kobject添加到kernel。
kset_unregister,直接调用kobject_put释放其kobject。当其kobject的引用计数为0时,即调用ktype的release接口释放kset占用的空间。
kset_create_and_add,会调用内部接口kset_create动态创建一个kset,并调用kset_register将其注册到kernel。
========================== 内部接口======================================
kset_create,该接口使用kzalloc分配一个kset空间,并定义一个kset_ktype类型的ktype,用于释放所有由它分配的kset空间。
2. 实例解析kobject创建bus
我们知道sys
下有一个bus
目录,这一节将分析如何通过kobject
创建bus
目录。
下面代码位于drivers/base/bus.c
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &bus_ktype)
return 1;
return 0;
}
static const struct kset_uevent_ops bus_uevent_ops = {
.filter = bus_uevent_filter,
};
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
if (!system_kset)
return -ENOMEM;
return 0;
}
这里直接调用kset_create_and_add
,第一个参数为要创建的目录的名字,而第三个参数表示没有父对象。
下面代码位于drivers/base/kobject.c
/**
* kset_create_and_add() - Create a struct kset dynamically and add it to sysfs.
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically and registers it
* with sysfs. When you are finished with this structure, call
* kset_unregister() and the structure will be dynamically freed when it
* is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error;
kset = kset_create(name, uevent_ops, parent_kobj);/*简历kset,设置某些字段*/
if (!kset)
return NULL;
error = kset_register(kset);/*添加kset到sysfs*/
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);
这里主要调用了两个函数,接下分别来看下。
2.1 kset_create函数
下面代码位于drivers/base/kobject.c
/**
* kset_create() - Create a struct kset dynamically.
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically. This structure can
* then be registered with the system and show up in sysfs with a call to
* kset_register(). When you are finished with this structure, if
* kset_register() has been called, call kset_unregister() and the
* structure will be dynamically freed when it is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
static struct kset *kset_create(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int retval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL);/*分配kset*/
if (!kset)
return NULL;
retval = kobject_set_name(&kset->kobj, "%s", name);/*设置kset-name*/
if (retval) {
kfree(kset);
return NULL;
}
kset->uevent_ops = uevent_ops;
kset->kobj.parent = parent_kobj;/*设置父对象*/
/*
* The kobject of this kset will have a type of kset_ktype and belong to
* no kset itself. That way we can properly free it when it is
* finished being used.
*/
kset->kobj.ktype = &kset_ktype;
kset->kobj.kset = NULL;/*本kset不属于任何kset*/
return kset;
}
这个函数中,动态分配了kset
结构,调用kobject_set_name
设置kset->kobj->name
为bus,也就是我们要创建的目录bus
。同时这里kset->kobj.parent
为NULL
,
也就是没有父对象。因为要创建的bus
目录是在sysfs
所在的根目录创建的,自然没有父对象。
随后简要看下由kobject_set_name
函数调用引发的一系列调用。
/**
* kobject_set_name_vargs() - Set the name of a kobject.
* @kobj: struct kobject to set the name of
* @fmt: format string used to build the name
* @vargs: vargs to format the string.
*/
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... some of these buggers have '/' in the name ... If
* that's the case, we need to make sure we have an actual
* allocated copy to modify, since kvasprintf_const may have
* returned something from .rodata.
*/
if (strchr(s, '/')) {
char *t;
t = kstrdup(s, GFP_KERNEL);
kfree_const(s);
if (!t)
return -ENOMEM;
strreplace(t, '/', '!');
s = t;
}
kfree_const(kobj->name);
kobj->name = s;
return 0;
}
/**
* kobject_set_name() - Set the name of a kobject.
* @kobj: struct kobject to set the name of
* @fmt: format string used to build the name
*
* This sets the name of the kobject. If you have already added the
* kobject to the system, you must call kobject_rename() in order to
* change the name of the kobject.
*/
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)
{
va_list vargs;
int retval;
va_start(vargs, fmt);
retval = kobject_set_name_vargs(kobj, fmt, vargs);
va_end(vargs);
return retval;
}
EXPORT_SYMBOL(kobject_set_name);
2.1.1 kset_register
/**
* kset_register() - Initialize and add a kset.
* @k: kset.
*/
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);/*初始化kset*/
err = kobject_add_internal(&k->kobj);/*在sysfs中建立目录*/
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
EXPORT_SYMBOL(kset_register);
这里面调用了3
个函数。
2.1.1.1 kset_init
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() - Initialize a kset for use.
* @k: kset
*/
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);/*初始化kobject的某些字段*/
INIT_LIST_HEAD(&k->list);/*初始化链表头*/
spin_lock_init(&k->list_lock);/*初始化自旋锁*/
}
2.1.1.2 kobject_add_internal
该函数将在sysfs
中建立目录。
下面代码位于drivers/base/kobject.c
。
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
/*检查name字段是否存在*/
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);/*有父对象则增加父对象引用计数*/
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)
/*kobj属于某个kset,但是该kobj没有父对象,则已kset的kobj作为父对象*/
parent = kobject_get(&kobj->kset->kobj);/**/
kobj_kset_join(kobj);/*将kobject添加到kset结构的链表当中*/
kobj->parent = parent;
}
pr_debug("kobject: '%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);/*根据kobj->name在sys中建立目录*/
if (error) {
kobj_kset_leave(kobj);/*删除链表项*/
kobject_put(parent);/*减少引用计数*/
kobj->parent = NULL;
/* be noisy on error issues */
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;
}
在上面的kset_create
中有kset->kobj.kset = NULL
,因此if (kobj->kset)
条件不满足。因此在这个函数中,对name
进行了必要的检查之后,调用了create_dir
在sysfs
中创建目录。
在create_dir
执行完成以后会在sysfs
的根目录(/sys/
)建立文件夹bus
。该函数的详细分析将在后面给出。
至此,对bus
目录的建立有了简单而直观的了解。我们可以看出kset
其实就是表示一个文件夹,而kset
本身也含有一个kobject
,而该kobject
的name
字段即为该目录的名字,本例中为bus
。