Skip to content

LV060-引用计数器简介

一、引用计数器简介

1. 什么是引用计数器?

引用计数器(reference counting) 是一种内存管理技术, 用于跟踪对象或资源的引用数量。它通过在对象被引用时增加计数值, 并在引用被释放时减少计数值, 以确定何时可以安全地释放对象或资源。

2. kref 简介

kref 是 Linux 内核中提供的一种引用计数器实现, 它是一种轻量级的引用计数技术, 用于管理内核中的对象的引用计数。在 Linux 系统中, 引用计数器用结构体 kref 来表示。struct kref 定义在 kref.h - include/linux/kref.h - struct kref

c
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, 以实现引用计数的管理。

image-20260119113755081

为了实现引用计数功能, struct kobject 通常会包含一个嵌入的 struct kref 对象。 这样可以通过对 struct kref 的操作来对 struct kobject 进行引用计数的管理, 并在引用计数减少到 0 时释放相关资源。

二、相关 API

1. kref_init()

kref_init() 定义如下:

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

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

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;
}

减少 struct kref 的引用计数, 并在引用计数减少到零时调用 release 函数来进行资源的释放。 通常, release 函数会在其中执行对象的销毁和内存释放等操作。

4. refcount_set()

refcount_set() 定义如下:

c
/**
 * 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

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

// 定义三个 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. 开发板验证

我们把生成的驱动拷贝到开发板,然后加载,再卸载,可以看到计数值的变化情况:

image-20250105202747415

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

image-20250105203726480

可以扩展一下,加深理解:如上图(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 博客

【3】Linux 设备模型(1)_基本概念

【4】一张图掌握 Linux platform 平台设备驱动框架!【建议收藏】-CSDN 博客

【5】关于 kobjects、ksets 和 ktypes 的一切你没想过需要了解的东西 — The Linux Kernel documentation

【6】【原创】linux 设备模型之 kset/kobj/ktype 分析 - LoyenWang - 博客园

【7】kobject / kset / ktype(linux kernel 中的面向对象) - 知乎