LV06-04-linux设备模型-04-kobject的释放

kobjet对象怎么释放的?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

在《LV06-04-linux设备模型-02-kobject相关的数据结构.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 相关的资源。该函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
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() 函数定义如下:

1
2
3
4
5
6
7
8
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是一个函数指针,它指向一个这样的函数:

1
void (*release)(struct kref *kref)

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

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

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

1.2 kobject_release()

kobject_release() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 操作:

1
2
3
4
static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};

2.1 dynamic_kobj_release()

dynamic_kobj_release()函数定义如下:

1
2
3
4
5
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定义如下:

1
2
3
4
5
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 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

点击查看详情
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
#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"); /* 字符串常量内容为模块别名 */