1. 前言
Kobject是Linux设备模型的基础,也是设备模型中最难理解的一部分(可参考Documentation/kobject.txt的表述)。因此有必要先把它分析清楚。
2. 基本概念
由上一节可知,Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。
而硬件设备的数量、种类是非常多的,这就决定了Kernel中将会有大量的有关设备模型的数据结构。这些数据结构一定有一些共同的功能,需要抽象出来统一实现,否则就会不可避免的产生冗余代码。这就是Kobject诞生的背景。
注1:在Linux中,Kobject几乎不会单独存在。它的主要功能,就是内嵌在一个大型的数据结构中,为这个数据结构提供一些底层的功能实现。
注2:Linux driver开发者,很少会直接使用Kobject以及它提供的接口,而是使用构建在Kobject之上的设备模型接口。
3. 代码解析
3.1 在Linux Kernel source code中的位置
在Kernel源代码中,Kobject由如下两个文件实现:
其中kobject.h为Kobject的头文件,包含所有的数据结构定义和接口声明。kobject.c为核心功能的实现。
3.2 主要的数据结构
在描述数据结构之前,有必要说明一下Kobject, Kset和Ktype这三个概念。
Kobject是基本数据类型,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。
Ktype代表Kobject(严格地讲,是包含了Kobject的数据结构)的属性操作集合(由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了)。
注3:在设备模型中,ktype的命名和解释,都非常抽象,理解起来非常困难,后面会详细说明。
Kset是一个特殊的Kobject(因此它也会在"/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以不同属性的)。
下面就是它们三者在内核中的低位和关系结构图。
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
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的增加、删除、修改等动作时,会通知用户空间。有关该功能的具体内容,会在其它文章详细描述。
/**
* 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;
};
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的。
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
release,通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。
sysfs_ops,该种类型的Kobject的sysfs文件系统接口。
default_attrs,该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的文件)。将会在Kobject添加到内核时,一并注册到sysfs中。
child_ns_type/namespace,和文件系统(sysfs)的命名空间有关,这里不再详细说明。
总结,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需要用比较巧妙(也很啰嗦)的手段去实现。
3.3 功能分析
3.3.1 Kobject使用流程
Kobject大多数情况下(有一种例外,下面会讲)会嵌在其它数据结构中使用,其使用流程如下:
上面有提过,有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。
3.3.2 Kobject的分配和释放
前面讲过,Kobject必须动态分配,而不能静态定义或者位于堆栈之上,它的分配方法有两种。
1. 通过kmalloc自行分配(一般是跟随上层数据结构分配),并在初始化后添加到kernel。这种方法涉及如下接口:
extern void kobject_init(struct kobject *kobj, 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,
struct kobj_type *ktype, struct kobject *parent,
const char *fmt, ...);
extern void kobject_del(struct kobject *kobj);
kobject_init,初始化通过kmalloc等内存分配函数获得的struct kobject指针。
/**
* kobject_init - initialize a kobject structure
* @kobj: pointer to the kobject to initialize
* @ktype: pointer to the ktype for this kobject.
*
* This function will properly initialize a kobject such that it can then
* be passed to the kobject_add() call.
*
* After this function is called, the kobject MUST be cleaned up by a call
* to kobject_put(), not by a call to kfree directly to ensure that all of
* the memory is cleaned up properly.
*/
void kobject_init(struct kobject *kobj, 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) { /* 用这个函数初始化,所以正常都是没初始化过的 */
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj); /* 初始化kobj */
kobj->ktype = ktype; /* 绑定ktype到该kobj */
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
主要执行逻辑为:
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref); /* 原子操作,对kref引用计数加1 */
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0; /* 标记还没呈现到sysfs */
kobj->state_add_uevent_sent = 0; /* 还未户空间发送ADD uevent */
kobj->state_remove_uevent_sent = 0; /* 没有发送remove uevent */
kobj->state_initialized = 1; /* 标记被初始化过 */
}
kobject_add,将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,可以为空)、用于提供kobject name的格式化字符串。主要执行逻辑为:
/**
* kobject_add - the main kobject add function
* @kobj: the kobject to add
* @parent: pointer to the parent of the kobject.
* @fmt: format to name the kobject with.
*
* The kobject name is set and added to the kobject hierarchy in this
* function.
*
* If @parent is set, then the parent of the @kobj will be set to it.
* If @parent is NULL, then the parent of the @kobj will be set to the
* kobject associated with the kset assigned to this kobject. If no kset
* is assigned to the kobject, then the kobject will be located in the
* root of the sysfs tree.
*
* If this function returns an error, kobject_put() must be called to
* properly clean up the memory associated with the object.
* Under no instance should the kobject that is passed to this function
* be directly freed with a call to kfree(), that can leak memory.
*
* Note, no "add" uevent will be created with this call, the caller should set
* up all of the necessary sysfs files for the object and then call
* kobject_uevent() with the UEVENT_ADD parameter to ensure that
* userspace is properly notified of this kobject's creation.
*/
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) { /* 只有初始化过的才能add */
printk(KERN_ERR "kobject '%s' (%p): tried to add an "
"uninitialized object, something is seriously wrong.\n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
kobject_init_and_add,是上面两个接口的组合,不再说明。
==========================内部接口======================================
kobject_add_varg,解析格式化字符串,将结果赋予kobj->name,之后调用kobject_add_internal接口,完成真正的添加操作。
static 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) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}
kobject_add_internal,将kobject添加到kernel。
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); /* 如果有父节点的话,父节的点引用计数加1 */
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) { /* kset是kobj的集合 */
if (!parent) /* 如果该kobj的父节点不存在,就让kset里面的kobj做它的父节点 */
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj); /* 把kobj加入到kset list的链表中去 */
kobj->parent = parent; /* 给kobj添加父节点 */
}
pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "");
error = create_dir(kobj); /* 调用sysfs文件系统接口,创建一个名为kobj->name的文件夹 ,目录和parent有直接关系*/
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
printk(KERN_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
printk(KERN_ERR "%s failed for %s (%d)\n",
__func__, kobject_name(kobj), error);
dump_stack();
} else
kobj->state_in_sysfs = 1;
return error;
}
主要执行逻辑为:
kobj_kset_join,负责将kobj加入到对应kset的链表中。
/* 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); /* 将kobj的entey加入到kset链表中去 */
spin_unlock(&kobj->kset->list_lock);
}
这种方式分配的kobject,会在引用计数变为0时,由kobject_put调用其ktype的release接口,释放内存空间,具体可参考后面有关kobject_put的讲解。
2. 使用kobject_create创建
Kobject模块可以使用kobject_create自行分配空间,并内置了一个ktype(dynamic_kobj_ktype),用于在计数为0是释放空间。代码如下:
extern struct kobject * __must_check kobject_create(void);
extern struct kobject * __must_check kobject_create_and_add(const char *name,
struct kobject *parent);
每个kobj在其分配的时候就已经为其准备好了释放函数,绑定在它的kobj_type的回调函数release中
/**
* kobject_create - create a struct kobject dynamically
*
* This function creates a kobject structure dynamically and sets it up
* to be a "dynamic" kobject with a default release function set up.
*
* If the kobject was not able to be created, NULL will be returned.
* The kobject structure returned from here must be cleaned up with a
* call to kobject_put() and not kfree(), as kobject_init() has
* already been called on this structure.
*/
struct kobject *kobject_create(void)
{
struct kobject *kobj;
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); /* 创建一个kobj */
if (!kobj)
return NULL;
kobject_init(kobj, &dynamic_kobj_ktype); /* 初始化并绑定ktype,注销函数 */
return kobj;
}
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的空间。
3.3.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 - initialize a kset for use
* @k: kset
*/
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj); /* 初始化里面的kobj */
INIT_LIST_HEAD(&k->list); /* 初始化kset用于组织下面kobj的链表 */
spin_lock_init(&k->list_lock);
}
kset_init,该接口用于初始化已分配的kset,主要包括调用kobject_init_internal初始化其kobject,然后初始化kset的链表。需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype。
/**
* 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); /* 把kset的kobj加入到内核(创建一个文件夹) */
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
kset_register,先调用kset_init,然后调用kobject_add_internal将其kobject添加到kernel。
/**
* kset_unregister - remove a kset.
* @k: kset.
*/
void kset_unregister(struct kset *k)
{
if (!k)
return;
kobject_del(&k->kobj); /* 删除这个kset */
kobject_put(&k->kobj); /* 引用计数减1,调用release */
}
kset_unregister,直接调用kobject_put释放其kobject。当其kobject的引用计数为0时,即调用ktype的release接口释放kset占用的空间。
/**
* 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);
if (!kset)
return NULL;
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
kset_create_and_add,会调用内部接口kset_create动态创建一个kset,并调用kset_register将其注册到kernel。
==========================内部接口======================================
static void kset_release(struct kobject *kobj)
{
struct kset *kset = container_of(kobj, struct kset, kobj);
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
kfree(kset); /* 释放kset本身 */
}
static struct kobj_type kset_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = kset_release, /* 绑定释放函数 */
};
/**
* 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);
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; /* 给它里面的kobj里面的ktype绑定一个release函数 */
kset->kobj.kset = NULL; /* kobj本就属于kset,这里不需要连接 */
return kset;
}
kset_create,该接口使用kzalloc分配一个kset空间,并定义一个kset_ktype类型的ktype,用于释放所有由它分配的kset空间。
static void kset_release(struct kobject *kobj)
{
struct kset *kset = container_of(kobj, struct kset, kobj);
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
kfree(kset);
}
static struct kobj_type kset_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = kset_release,
};
最后我们分析一下释放kset的方法。
/** 该函数的使用通常和kset_register配对
* kset_unregister - remove a kset.
* @k: kset.
*/
void kset_unregister(struct kset *k)
{
if (!k)
return;
/* 此函数只是将kobject从sysfs和kset中去掉(初始化kobject的list_head),并不是删除kobject,删除kobject得靠kobject->ktype->release才行。kobject_del在把kobject从kset中去掉后,会把kset的引用值减1。*/
kobject_del(&k->kobj);
/* 此函数为kobject的引用值减1,若引用值减为0,则销毁此kobject(调用kobject_release调用kobject_cleanup,最终调用kobject_del、ktype的release),最后一步释放的name是在kobject_add中调用的kobject_set_name_vargs中分配的。 */
kobject_put(&k->kobj);
}
对比上下两个可以发现在kset_unregister里面其实调用两次konject_put函数,一次是其parent的,一次是自己的。
/**
* kobject_del - unlink kobject from hierarchy.
* @kobj: object.
*/
void kobject_del(struct kobject *kobj)
{
struct kernfs_node *sd;
if (!kobj)
return;
sd = kobj->sd;
sysfs_remove_dir(kobj); /* 删除sysfs里面的文件夹 */
sysfs_put(sd); /* 清理sysfs里面的引用计数,释放计数为0的节点等 */
kobj->state_in_sysfs = 0; /* 标明不再sysfs中了 */
kobj_kset_leave(kobj);
kobject_put(kobj->parent); /* 父节点引用数减1,如果减值0,则释放父节点kset */
kobj->parent = NULL; /* 它的parent之前是kset的kobj,现在置位NULL */
}
/**
* kref_sub - subtract a number of refcounts for object.
* @kref: object.
* @count: Number of recounts to subtract.
* @release: pointer to the function that will clean up the object when the
* last reference to the object is released.
* This pointer is required, and it is not acceptable to pass kfree
* in as this function. If the caller does pass kfree to this
* function, you will be publicly mocked mercilessly by the kref
* maintainer, and anyone else who happens to notice it. You have
* been warned.
*
* Subtract @count from the refcount, and if 0, call release().
* Return 1 if the object was removed, otherwise return 0. Beware, if this
* function returns 0, you still can not count on the kref from remaining in
* memory. Only use the return value if you want to see if the kref is now
* gone, not present.
*/
static inline int kref_sub(struct kref *kref, unsigned int count,
void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);
if (atomic_sub_and_test((int) count, &kref->refcount)) {
release(kref); /* 如果减至1,则调用kobj所在的ktype里面的release */
return 1;
}
return 0;
}
/**
* kref_put - decrement refcount for object.
* @kref: object.
* @release: pointer to the function that will clean up the object when the
* last reference to the object is released.
* This pointer is required, and it is not acceptable to pass kfree
* in as this function. If the caller does pass kfree to this
* function, you will be publicly mocked mercilessly by the kref
* maintainer, and anyone else who happens to notice it. You have
* been warned.
*
* Decrement the refcount, and if 0, call release().
* Return 1 if the object was removed, otherwise return 0. Beware, if this
* function returns 0, you still can not count on the kref from remaining in
* memory. Only use the return value if you want to see if the kref is now
* gone, not present.
*/
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
return kref_sub(kref, 1, release); /* 减1 */
}
/**
* kobject_put - decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call 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);
}
}
static inline void kset_put(struct kset *k)
{
kobject_put(&k->kobj);
}
/**
* kobject_put - decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call 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); /* 如果引用计数为0,则调用kobject_release清理 */
}
}
/* remove the kobject from its kset's list */
static void kobj_kset_leave(struct kobject *kobj)
{
if (!kobj->kset)
return;
spin_lock(&kobj->kset->list_lock);
list_del_init(&kobj->entry); /* 删除kobj在kset中的节点 */
spin_unlock(&kobj->kset->list_lock);
kset_put(kobj->kset); /* kobj的kset引用计数减1,减为0则调用它上一级的kset为其绑定的kobj_type里面的release释放kset本身 */
}
/**
* kobject_put - decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call kobject_cleanup().
*/
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized) /* 0表示已经释放过了 */
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); /* 这个函数在好多地方都时候用了,好好分析一下 */
}
}
其中kobject_release是kobject.c文件定义的。
static void kobject_release(struct kref *kref)
{
/* 先根据kref找到kobject的地址,再把这个地址传给kobject_cleanup */
kobject_cleanup(container_of(kref, struct kobject, kref));
}
/*
* kobject_cleanup - free kobject resources.
* @kobj: object to cleanup
*/
static void kobject_cleanup(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj); /* 得到kobj里面的ktype指针 */
const char *name = kobj->name;
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
if (t && !t->release) /* 如果ktype存在,但ktype里的release不存在,则ktype没法释放,打印出告警信息 */
pr_debug("kobject: '%s' (%p): does not have a release() "
"function, it is broken and must be fixed.\n",
kobject_name(kobj), kobj);
/* send "remove" if the caller did not do it but sent "add" */
/* 如果没发remove却发了add的话,则向用户层发送remove, */
if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n",
kobject_name(kobj), kobj);
kobject_uevent(kobj, KOBJ_REMOVE);
}
/* remove from sysfs if the caller did not do it */
if (kobj->state_in_sysfs) { /* 如果换处在sysfs中,则要先调用kobjcat_del,删除文件夹操作等 */
pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
kobject_name(kobj), kobj);
kobject_del(kobj);
}
if (t && t->release) { /* kobj_type和其释放函数都在的话,则调用release释放内存 */
pr_debug("kobject: '%s' (%p): calling ktype release\n",
kobject_name(kobj), kobj);
t->release(kobj);
}
/* free name if we allocated it */
if (name) {
pr_debug("kobject: '%s': free name\n", name);
kfree(name); /* 释放name占用的字节数 */
}
}
总结后我们发现:
extern void kset_init(struct kset *kset); /* 被kset_register调用 */
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_register */
如果使用kset_create_and_add函数在kset_creat的时候,它里面kobject里面的kobj_type已经被绑定,它里面的kobject里面的kset则没有,即没有而直接包含在某个设备中的kset,则直接调用kset_register注册,注册前要自己初始化好里面kobject的kobj_type和kset指针,这样会让使用变得更加灵活。
看一下kset和kobject的关系
我们前面说了,每一个 kobj 对应 文件系统 /sys 里的一个 目录,那么每一个 kset 都包含了一个 kobj,那样的话,kset也对应于 /sys 里的一个 目录
简单来说,kset 与 kobj 都是目录,既然是目录,那么在就是一个树状结构,每一个目录都将有一个 父节点,
在kset中使用kset.kobj->parent 指定
在kboject中使用 kobj->parent 指定
显然,整个树状目录结构,都是通过kobj来构建的,只不过有些kobj嵌在Kset里,分析目录结构时把kset当成一个普通的kobj会好理解很多。
父目录kset和其子目录的konject和kset之间,子目录的kobject(或kset里面的kobject)使用里面的parent指向父目录kset里面的kobject,kset用其里的list链表作为head负责连接这个文件夹里面的所有的kobject,用其里面kobject的lsit指针作为更高一层目录kset链表的node接入上层。
而kobject因为里面只有list,只能作为node计入同一层目录下的链表中。
那么kset 有何特殊之处呢?
我们可以看到 kset 结构中有一个链表,它目录下的 一些相同类型的kobj会在创建时链入链表,也就是说Kset是一些kobj的集合。
干说还是比较抽象的,那么我们就来看看 /sys 目录底下的这些东西,哪些是kset 哪些是kobj 结构又是怎样的。
看过上面代码的应该知道,想要在/sys 目录下创建 目录,那就要 构造 kset 或者 kobj 设置并注册。
可以简单点这样理解,kset是以文件夹形式组织内核中的树形结构,它通过list链表连接下面所有的kobject或kset。而下面的kobject和kset则用他们里面的kobject里面的parent指向kset里面的kobject,同时所有kobject里面的kset指针也会指向kset结构体(主要实现引用计数,只有在kset文件夹下面的所有kobject和kset都没有的话,它的下面的最后一个kobject注销的时候,会对它的kset引用计数减1,发现该kset的引用计数为0,才可以调用kset自身绑定的ktype里面的release释放kset,否则下面还有文件肯定不能释放)。除了sysfs的根目录,其它目录下的kset最掌管下面的kobject和kset,对上通过里面的kobject作为更上一层的子目录。这样就能通过kset的多级连接以及kobject实现内核sysfs中的树形目录的基础。
再看一下三者总体的关系。
上面这个图是超级经典的,指的反复学和思考树形目录的实现原理。
kobject是隐藏在sysfs虚拟文件系统后的机制,对于sysfs中的每一个目录,内核中都会存在一个对应的kobject。每一个kobject都输出一个或者多个属性,它们在kobject的sysfs目录中表现为文件,其中的内容由内核生成。
kobject在sysfs中始终是一个目录,这个目录包含一个或者多个属性。
分配给kobject的名字,是sysfs中的目录名字。
sysfs的入口目录的位置对应于kobject的parent指针。调用kobject_add的时候,如果parent为NULL,它将被设置为嵌入到心得kobject的kset中的kobject,这样,sysfs 分层结构通常与kset创建的内部结构相匹配。如果parent和kset都是null,则会在最高层创建目录。
原本我以为,较高层次的目录会是kset,因为它是个集合嘛,然而并不全是。
对kobject
kobject_init_and_add
kobject_create_and_add
对kset
kset_create_and_add
我在这3个函数中增加了prink打印语句,打印内核创建的每一个 kobj 或者 kset 的名字,以及父节点的名字,甚至它指向的kset的kobj的名字。
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval;
kobject_init(kobj, ktype);
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
/*********************************************************/
printk(KERN_INFO"the kobject is %s\n" ,kobj->name);
if(parent)
printk(KERN_INFO"the kobject parent is %s\n", kobj->parent->name);
else
printk(KERN_INFO"the kobject no parent\n");
if(kobj->kset)
printk(KERN_INFO"the kobject kset is %s\n", kobj->kset->kobj.name);
else
printk(KERN_INFO"the kobject no kset\n");
/*********************************************************/
return retval;
}
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval;
kobject_init(kobj, ktype);
kobj = kobject_create();
if (!kobj)
return NULL;
retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
printk(KERN_WARNING "%s: kobject_add error: %d\n",
__func__, retval);
kobject_put(kobj);
kobj = NULL;
}
/********************************************************/
printk(KERN_ERR"the kobject is %s\n",kobj->name);
if(parent)
printk(KERN_INFO"the kobject parent is %s\n", kobj->parent->name);
else
printk(KERN_INFO"the kobject no parent\n");
if(kobj->kset)
printk(KERN_INFO"the kobject kset is %s\n" ,kobj->kset->kobj.name)
else
printk(KERN_INFO"the kobject no kset\n");
/********************************************************/
return kobj;
}
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);
if (!kset)
return NULL;
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
/*************************************************/
printk(KERN_ERR"the kset is %s\n", kset->kobj.name);
if(kset->kobj.parent)
printk(KERN_INFO"the kset parent is %s\n",kset->kobj.parent->name );
else
printk(KERN_INFO"the kset no parent\n");
if(kset->kobj.kset)
printk(KERN_INFO"the kset of kset is %s\n", kset->kobj.kset->kobj.name);
else
printk(KERN_INFO"the kset no kset\n");
/**************************************************/
return kset;
}
为此我截图找到几个重要的目录进行分析kset和kobject的在目录中的组织。
可以发现
1.顶层的一些目录也可以是kobject,如fs,dev目录等
2.kobject下面可以放kobject
3.kset下面可以放kset
4.kset下可以放kobject
5.kobject下可以放kset
kobject里面可以放kobject(2),但上层的kobject不能联系下层,只能下层联系上层。
kset下可以放kset(3),上层可以通过kset里面的list链表连接下层kset里kobject里的list连接
kset下可以放kobj(4),上层可以通过kset里面的list链表连接下层kobject里的list连接
kobject里的kset,但上层的kobject不能联系下层,只能下层联系上层。
总结:从下层到上层很简单,只要当前kobject(或kset里面的kobject)的parent指针操作就可以
1、sys 目录下的层次结构依赖于 kobject.parent ,未指定parent时,默认使用 kobject.kset.kobject 作为 parent,如果都没有,就出现在 /sys 目录下。
2、该 kobject 目录下的属性文件依赖于 kobject.ktype
3.kset之间的下级到上级(子目录到父目录)关系是依靠,kset里面kobject里面的kset指针(只能子指向父)。而同一目录下的则是用kset里面kobject里面的list指针,和父节点彼此组成双向链表。(这样想来是不是由子目录返回上一级目录直接一个指针操作就可以,而父目录进子目录需要链表遍历然后对比名字才能进去 【想一想linux中的shell中,子目录返回父目录 [..]就可以的实现原理】)
4.父目录kset和其子目录的konject和kset之间,子目录的kobject(或kset里面的kobject)使用里面的parent指向父目录kset里面的kobject,kset用其里的list链表负责连接这个文件夹里面的所有的kobject.
5.父子关系必须是同类型的,所以文件夹之间的父子是用,子文件夹kset里面的kobject里的kset指针指向父kset。文件和文件夹的父子关系是用文件kobject里的parent指针指向文件夹kset里的kobject.
6.用ABC分表描述三个等级的文件夹,其中A是根目录,B是A的子文件夹,C是B的子文件夹或里面的文件。B文件夹连接下层C文件或文件夹是使用kset里的list链表(此时B中kset的list是作为链表head),而B被A连接使用它里面的kobject里的list(此时B中kobject里的list是作为链表node)
同时在驱动层次注册device或driver的时候会为其kobj(因为kobject单独存在无意义,通常都是和dev结合的)绑定kobj_type(大多时候都只是show和store接口)。大多数情况下kobject结构体都是作为基类被放置在某个device或driver里面的,所有释放device或driver的时候会整体释放。只有在单独调用下面两个函数,创建文件夹的时候,才需要调前面分析的创建文件夹时绑定的动态release函数。
extern struct kobject * __must_check kobject_create(void);
extern struct kobject * __must_check kobject_create_and_add(const char *name,
struct kobject *parent);
最后,这里借用国嵌的代码历程,对上面的驱动模型的核心框架说明。
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
/* 文件名和权限 */
struct attribute test_attr = {
.name = "kobj_config",
.mode = S_IRWXUGO,
};
/* 这里是数组,即目录下可以有多个文件 */
static struct attribute *def_attrs[] = {
&test_attr, /* 目录下的一个文件 */
NULL,
};
struct sysfs_ops obj_test_sysops =
{
/* 文件的操作方法 */
.show = kobj_test_show,
.store = kobj_test_store,
};
struct kobj_type ktype =
{
.release = obj_test_release,
.sysfs_ops=&obj_test_sysops,
.default_attrs=def_attrs,
};
void obj_test_release(struct kobject *kobject)
{
printk("eric_test: release .\n");
}
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{
printk("have show.\n");
printk("attrname:%s.\n", attr->name);
sprintf(buf,"%s\n",attr->name);
return strlen(attr->name)+2;
}
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)
{
printk("havestore\n");
printk("write: %s\n",buf);
return count;
}
struct kobject kobj;
static int kobj_test_init()
{
/* parent为NULL,即在跟目录下 */
printk("kboject test init.\n");
kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test"); /* 创建一个目录 */
return 0;
}
static int kobj_test_exit()
{
printk("kobject test exit.\n");
kobject_del(&kobj); /* 删除一个目录 */
return 0;
}
module_init(kobj_test_init);
module_exit(kobj_test_exit);
可以看到kobject就是在sys的根目录下创建了一个目录,其中目录下有一个文件,该文件就是由sttribute来管理的。
读取该文件的信息,就会调用到相关的show函数。红框则是驱动让系统展示的内容。
给该文件写信息,也可以原样打印出来。
该文件的权限也和注册时的是一样的。
最后留一个问题release函数什么时候执行?该如何让操作?
kset涉及到热插拔,放到下一节。
参考文档:
http://www.wowotech.net/device_model/kobject.html
https://www.cnblogs.com/xiaojiang1025/p/6193959.html
https://blog.csdn.net/lizuobin2/article/details/51523693