Skip to content

LV090-kobject的释放

kobjet 对象怎么释放的?

在《LV060-引用计数器简介.md》中已经了解了引用计数器,知道当引用计数器的值变为 0 后,会自动调用自定义的释放函数去执行释放的操作。那么为什么到 0 的时候就会释放呢?我们来看一下。

一、kobject 怎么创建的?

要知道 kobject 是如何释放的,那么我们要先明白 kobject 是如何创建的。对于 kobject 的创 建,我们可以进一步分析这两种方法的实现细节。

1. kobject_create_and_add()

kobject_create_and_add() 函数创建 kobject 的实现过程大概如下:

image-20250106192234812

2. kobject_init_and_add()

kobject_init_and_add() 创建 kobject 时需要手动分配内存,并通过 kobject_init() 函数对分配的内存进行初始化。此时需要自己实现 ktype 结构体。初始化完成后,调用 kobject_add_varg() 函数将 kobject 添加到系统中。

image-20250106192920732

二、kobject 的释放

我们来看一下 kobject 的释放函数——kobject_put() 函数的实现。

1. kobject_put()

在 Linux 内核中,kobject_put() 函数用于减少 kobject 的引用计数,并在引用计数达到 0 时 释放 kobject 相关的资源。该函数定义如下:

c
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() 函数定义如下:

c
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 是一个函数指针,它指向一个这样的函数:

c
void (*release)(struct kref *kref)

这个函数是啥?我们看上一层调用它的函数(kobject_put())给它传的什么,可以看到传入的是:

c
ref_put(&kobj->kref, kobject_release);

所以最终其实调用的是 kobject_release() 函数来释放 kobject 对象。

1.2 kobject_release()

kobject_release() 函数定义如下:

c
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。接下来我们详细分析一下:

image-20250106195217638

kobject_cleanup() 函数的参数为一个指向 struct kobject 结构体的指针 kobj。函数内部定义了一个指向 struct kobj_type 结构体的指针 t,用于获取 kobj 的类型信息。还定义了一个指向常量字符的指针 name,用于保存 kobj 的名称。

image-20250106195313188

接下来,使用 pr_debug 打印调试信息,显示 kobject 的名称、地址、函数名称和父对象的地址。

image-20250106195427442

检查 kobj 的类型信息 t 是否存在,并且检查 t-> release 是否为 NULL。如果 t 存在但 t-> release 为 NULL,表示 kobj 的类型没有定义释放函数,会打印调试信息指示该情况。

image-20250106195522524

检查 kobj 的状态变量 state_add_uevent_sent 和 state_remove_uevent_sent。如果 state_add_uevent_sent 为真而 state_remove_uevent_sent 为假,表示调用者没有发送 "remove" 事件,会自动发送 "remove" 事件。

image-20250106195819946

检查 kobj 的状态变量 state_in_sysfs。如果为真,表示调用者没有从 sysfs 中删除 kobj,会自动调用 kobject_del() 函数将其从 sysfs 中删除。

image-20250106200226834

再次检查 t 是否存在,并且检查 t-> release 是否存在。如果存在,表示 kobj 的 类型定义了释放函数,会调用该释放函数进行资源清理。从这里可以知道,最后调用的这个 release 函数是 kobj_type 结构体中定义的。

image-20250106200351948

检查 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 操作:

c
static struct kobj_type dynamic_kobj_ktype = {
	.release	= dynamic_kobj_release,
	.sysfs_ops	= &kobj_sysfs_ops,
};

2.1 dynamic_kobj_release()

dynamic_kobj_release() 函数定义如下:

c
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 定义如下:

c
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() 函数中设置的:

image-20250106201828643

3. 自定义释放函数?

自定义的话,好像要自己采用手动申请内存,创建 kobject 那种方式,这里有一个 demo:05_device_model/05_ktype

c
#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"); /* 字符串常量内容为模块别名 */