LV060-引用计数器简介
一、引用计数器简介
1. 什么是引用计数器?
引用计数器(reference counting) 是一种内存管理技术, 用于跟踪对象或资源的引用数量。它通过在对象被引用时增加计数值, 并在引用被释放时减少计数值, 以确定何时可以安全地释放对象或资源。
2. kref 简介
kref 是 Linux 内核中提供的一种引用计数器实现, 它是一种轻量级的引用计数技术, 用于管理内核中的对象的引用计数。在 Linux 系统中, 引用计数器用结构体 kref 来表示。struct kref 定义在 kref.h - include/linux/kref.h - struct kref:
struct kref {
refcount_t refcount;
};
// include/linux/refcount.h
typedef struct refcount_struct {
atomic_t refs;
} refcount_t;
// include/linux/types.h
typedef struct {
int counter;
} atomic_t;本质是一个 int 型变量。在使用引用计数器时, 通常会将结构体 kref 嵌入到其他结构体中, 例如 struct kobject, 以实现引用计数的管理。

为了实现引用计数功能, struct kobject 通常会包含一个嵌入的 struct kref 对象。 这样可以通过对 struct kref 的操作来对 struct kobject 进行引用计数的管理, 并在引用计数减少到 0 时释放相关资源。
二、相关 API
1. kref_init()
kref_init() 定义如下:
/**
* kref_init - initialize object.
* @kref: object in question.
*/
static inline void kref_init(struct kref *kref)
{
refcount_set(&kref->refcount, 1);
}初始化一个 struct kref 对象。 在使用引用计数之前, 必须先调用此函数进行初始化。 初始化 kerf 的值为 1 。
2. kref_get()
kref_get() 定义如下:
/**
* kref_get - increment refcount for object.
* @kref: object.
*/
static inline void kref_get(struct kref *kref)
{
refcount_inc(&kref->refcount);
}增加 struct kref 的引用计数。 每次调用此函数, 引用计数都会增加。 kref 计数值加 1 。
3. 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;
}减少 struct kref 的引用计数, 并在引用计数减少到零时调用 release 函数来进行资源的释放。 通常, release 函数会在其中执行对象的销毁和内存释放等操作。
4. refcount_set()
refcount_set() 定义如下:
/**
* refcount_set - set a refcount's value
* @r: the refcount
* @n: value to which the refcount will be set
*/
static inline void refcount_set(refcount_t *r, unsigned int n)
{
atomic_set(&r->refs, n);
}设置 kerf 的计数值。
三、kref demo
1. demo 源码
可以看这里:05_device_model/04_kref
#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
// 定义三个 kobject 指针变量:skobject1、skobject2、skobject3
struct kobject *skobject1 = NULL;
struct kobject *skobject2 = NULL;
struct kobject *skobject3 = NULL;
struct kobj_type stype = {0}; // 定义一个 kobj_type 结构体变量 stype,用于描述 kobject 的类型。
/**
* @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 的第一种方法
// 创建并添加一个名为 "skobject1" 的 kobject 对象,父 kobject 为 NULL
skobject1 = kobject_create_and_add("skobject1", NULL);
PRT("skobject1 kref is %d\n", skobject1->kref.refcount.refs.counter);
// 创建并添加一个名为 "skobject2" 的 kobject 对象,父 kobject 为 skobject1。
skobject2 = kobject_create_and_add("skobject2", skobject1);
PRT("skobject2 kref is %d\n", skobject2->kref.refcount.refs.counter);
// 创建 kobject 的第二种方法
// 1.使用 kzalloc 函数分配了一个 kobject 对象的内存
skobject3 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2.初始化并添加到内核中,名为 "skobject3"。
ret = kobject_init_and_add(skobject3, &stype, NULL, "%s", "skobject3");
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add;
}
PRT("skobject3 kref is %d\n", skobject3->kref.refcount.refs.counter);
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)
{
PRT("skobject1 kref is %d\n", skobject1->kref.refcount.refs.counter);
PRT("skobject2 kref is %d\n", skobject2->kref.refcount.refs.counter);
PRT("skobject3 kref is %d\n", skobject3->kref.refcount.refs.counter);
PRT("before kobject_put all!\n\n");
// 释放之前创建的 kobject 对象
kobject_put(skobject1);
PRT("skobject1 kref is %d\n", skobject1->kref.refcount.refs.counter);
PRT("skobject2 kref is %d\n", skobject2->kref.refcount.refs.counter);
PRT("skobject3 kref is %d\n", skobject3->kref.refcount.refs.counter);
PRT("after kobject_put skobject1!\n\n");
kobject_put(skobject2);
PRT("skobject1 kref is %d\n", skobject1->kref.refcount.refs.counter);
PRT("skobject2 kref is %d\n", skobject2->kref.refcount.refs.counter);
PRT("skobject3 kref is %d\n", skobject3->kref.refcount.refs.counter);
PRT("after kobject_put skobject2!\n\n");
kobject_put(skobject3);
PRT("skobject1 kref is %d\n", skobject1->kref.refcount.refs.counter);
PRT("skobject2 kref is %d\n", skobject2->kref.refcount.refs.counter);
PRT("skobject3 kref is %d\n", skobject3->kref.refcount.refs.counter);
PRT("after kobject_put skobject3!\n\n");
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"); /* 字符串常量内容为模块别名 */2. 开发板验证
我们把生成的驱动拷贝到开发板,然后加载,再卸载,可以看到计数值的变化情况:

驱动加载之后, 第一条打印为“skobject1 kref is 1”, 因为创建了 skobject1, 所以引用计数器的值为 1, 如下图所示的(1)。 第二条打印为: “skobject1 kref is 2”, 因为在 skobject1 目录下创建了子目录 skobject2,所以 skobject1 的计数器值为 2, skobject2 的计数器值为 1, 如下图所示的(2)。

可以扩展一下,加深理解:如上图(3)所示, 如果在 objectA 下面创建俩个 object, objectA 的 计数器值为 3。 如上图所示(4), 如果在 objectA 下面创建两个 object, 那么 objectA 的计数器值 为 3, 在 objectB 下创建 object, 那么 objectB 的计数器值为 2, objectC 的计数器值为 1。
最后我们卸载驱动,当引用计数器的值为 0 时, 表示没有任何引用指向对象或资源, 可以安全地释放对象或资源, 并进行相关的清理操作。
参考资料
【1】linux 驱动开发—— 6、linux 设备驱动模型_什么是 kobject-CSDN 博客
【2】Linux 设备模型剖析系列一(基本概念、kobject、kset、kobj_type)_device 下的 kobj-CSDN 博客
【4】一张图掌握 Linux platform 平台设备驱动框架!【建议收藏】-CSDN 博客
【5】关于 kobjects、ksets 和 ktypes 的一切你没想过需要了解的东西 — The Linux Kernel documentation