LV090-kobject的释放
kobjet 对象怎么释放的?
在《LV060-引用计数器简介.md》中已经了解了引用计数器,知道当引用计数器的值变为 0 后,会自动调用自定义的释放函数去执行释放的操作。那么为什么到 0 的时候就会释放呢?我们来看一下。
一、kobject 怎么创建的?
要知道 kobject 是如何释放的,那么我们要先明白 kobject 是如何创建的。对于 kobject 的创 建,我们可以进一步分析这两种方法的实现细节。
1. kobject_create_and_add()
kobject_create_and_add() 函数创建 kobject 的实现过程大概如下:

2. kobject_init_and_add()
kobject_init_and_add() 创建 kobject 时需要手动分配内存,并通过 kobject_init() 函数对分配的内存进行初始化。此时需要自己实现 ktype 结构体。初始化完成后,调用 kobject_add_varg() 函数将 kobject 添加到系统中。
二、kobject 的释放
我们来看一下 kobject 的释放函数——kobject_put() 函数的实现。
1. kobject_put()
在 Linux 内核中,kobject_put() 函数用于减少 kobject 的引用计数,并在引用计数达到 0 时 释放 kobject 相关的资源。该函数定义如下:
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);可以看到内部最终调用的是 kref_put() 函数来对引用计数进行操作。
1.1 kref_put()
kref_put() 函数定义如下:
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;
}在该函数中,当引用计数器的值变为 0 以后,会调用 release 函数执行释放的操作,这个 release 是一个函数指针,它指向一个这样的函数:
void (*release)(struct kref *kref)这个函数是啥?我们看上一层调用它的函数(kobject_put())给它传的什么,可以看到传入的是:
ref_put(&kobj->kref, kobject_release);所以最终其实调用的是 kobject_release() 函数来释放 kobject 对象。
1.2 kobject_release()
kobject_release() 函数定义如下:
static void kobject_release(struct kref *kref)
{
struct kobject *kobj = container_of(kref, struct kobject, kref);
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
unsigned long delay = HZ + HZ * (get_random_int() & 0x3);
pr_info("kobject: '%s' (%p): %s, parent %p (delayed %ld)\n",
kobject_name(kobj), kobj, __func__, kobj->parent, delay);
INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);
schedule_delayed_work(&kobj->release, delay);
#else
kobject_cleanup(kobj);
#endif
}去内核源码搜一下就会发现,正常情况是没有这个 CONFIG_DEBUG_KOBJECT_RELEASE 宏的,后面会调用 kobject_cleanup() 函数。
1.3 kobject_cleanup()
kobject_cleanup()函数定义在 kobject.c - lib/kobject.c - kobject_cleanup。接下来我们详细分析一下:
kobject_cleanup() 函数的参数为一个指向 struct kobject 结构体的指针 kobj。函数内部定义了一个指向 struct kobj_type 结构体的指针 t,用于获取 kobj 的类型信息。还定义了一个指向常量字符的指针 name,用于保存 kobj 的名称。
- 第 638 行
接下来,使用 pr_debug 打印调试信息,显示 kobject 的名称、地址、函数名称和父对象的地址。
- 第 641 行
检查 kobj 的类型信息 t 是否存在,并且检查 t-> release 是否为 NULL。如果 t 存在但 t-> release 为 NULL,表示 kobj 的类型没有定义释放函数,会打印调试信息指示该情况。
- 第 646 行
检查 kobj 的状态变量 state_add_uevent_sent 和 state_remove_uevent_sent。如果 state_add_uevent_sent 为真而 state_remove_uevent_sent 为假,表示调用者没有发送 "remove" 事件,会自动发送 "remove" 事件。
- 第 653 行
检查 kobj 的状态变量 state_in_sysfs。如果为真,表示调用者没有从 sysfs 中删除 kobj,会自动调用 kobject_del() 函数将其从 sysfs 中删除。
- 第 659 行
再次检查 t 是否存在,并且检查 t-> release 是否存在。如果存在,表示 kobj 的 类型定义了释放函数,会调用该释放函数进行资源清理。从这里可以知道,最后调用的这个 release 函数是 kobj_type 结构体中定义的。
- 第 666 行
检查 name 是否存在。如果存在,表示 kobj 的名称是动态分配的,会释放该名称 的内存。
这就是 kobject_cleanup() 函数的实现。它负责执行 kobject 的资源清理和释放操作, 包括处理类型信息、发送事件、删除 sysfs 中的对象以及调用释放函数。
1.4 总结
kobject_cleanup() 函数的实现表明,最终调用的释放函数是在 kobj_type 结构体中定义的。 这解释了为什么在使用 kobject_init_and_add() 函数时,kobj_type 结构体不能为空的原因。因 为释放函数是在 kobj_type 结构体中定义的,如果不实现释放函数,就无法进行正确的资源释放。
2. dynamic_kobj_ktype
dynamic_kobj_ktype 是一个 kobj_type 结构体对 象,用于定义动态创建的 kobject 的类型。它指定了释放函数和 sysfs 操作:
static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};2.1 dynamic_kobj_release()
dynamic_kobj_release() 函数定义如下:
static void dynamic_kobj_release(struct kobject *kobj)
{
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}使用 kfree 函数对创建的 kobj 进行了释放。总结起来, kobj_type 结构体中的释放函数是为了确保在释放 kobject 时执行必要的资源清理和释放操作,以确保系统的正确运行。
2.2 kobj_sysfs_ops
kobj_sysfs_ops 定义如下:
const struct sysfs_ops kobj_sysfs_ops = {
.show = kobj_attr_show,
.store = kobj_attr_store,
};
EXPORT_SYMBOL_GPL(kobj_sysfs_ops);2.3 在哪里被调用?
这个 dynamic_kobj_ktype 是在 kobject_create() 函数中设置的:
3. 自定义释放函数?
自定义的话,好像要自己采用手动申请内存,创建 kobject 那种方式,这里有一个 demo:05_device_model/05_ktype
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */
#include <linux/kobject.h>
#include <linux/slab.h>
#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"
// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif
struct kobject *skobject1 = NULL; // 定义 kobject 指针变量:skobject1
/**
* @brief dynamic_kobj_release()
* @note 定义 kobject 的释放函数
* @param [in]
* @param [out]
* @retval
*/
static void dynamic_kobj_release(struct kobject *kobj)
{
PRT("kobject: (%p)\n", kobj);
kfree(kobj);
}
// 定义了一个 kobj_type 结构体变量 stype,用于描述 kobject 的类型。
struct kobj_type stype = {
.release = dynamic_kobj_release,
};
/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");
// 创建 kobject 的第二种方法
// 1.使用 kzalloc 函数分配了一个 kobject 对象的内存
skobject1 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2.初始化并添加到内核中,名为 "skobject1"。
ret = kobject_init_and_add(skobject1, &stype, NULL, "%s", "skobject1");
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add;
}
return 0;
err_kobject_init_and_add:
return ret;
}
/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 释放之前创建的 kobject 对象
kobject_put(skobject1);
PRT("sdriver_demo module exit!\n");
}
module_init(sdriver_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数
/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */