1、前言

struct kref结构体是一个引用计数器,它被嵌套进其它的结构体中,记录所嵌套结构的引用计数。引用计数用于检测内核中有多少地方使用了某个对象,每当内核的一个部分需要某个对象所包含的信息时,则该对象的引用计数加1,如果不需要相应的信息,则对该对象的引用计数减1,当引用计数为0时,内核知道不再需要该对象,将从内存中释放该对象。

2、kref结构体

在Linux的内核源码中,struct kref结构体的定义在include/linux/kref.h文件中,结构体定义如下所示:

struct kref {
refcount_t refcount;
};

其中,refcount_t的类型定义如下所示:

typedef struct refcount_struct {
atomic_t refs;
} refcount_t;

该数据结构比较简单,它提供了一个原子引用计数值atomic_t refs,atomic_t是原子类型,对其操作都要求是原子执行。“原子”在这里意味着,对该变量的加1和减1操作在多处理器系统上也是安全的。

3、kref操作

(1)初始化引用计数

Linux内核中提供了初始化struct kref结构体的函数接口,如下:

#define KREF_INIT(n)    { .refcount = REFCOUNT_INIT(n), }

/**
* kref_init - initialize object.
* @kref: object in question.
*/
static inline void kref_init(struct kref *kref)
{
refcount_set(&kref->refcount, );
}

KREF_INIT(n)是一个宏定义,该宏用于将引用计数变量初始化为n,另外Linux内核还提供了一个kref_init()函数接口,该函数的功能用于将引用计数变量初始化为1。

(2)读取引用计数器的值

Linux内核中,读取引用计数器的值的函数接口为kref_read(),该函数的定义如下:

static inline unsigned int kref_read(const struct kref *kref)
{
return refcount_read(&kref->refcount);
}

该函数传入的参数为struct kref指针,函数的返回值为读取到的引用计数器的值。

(3)引用计数器加1操作

Linux内核中,引用计数器加1操作的函数接口为kref_get(),函数的定义如下所示:

static inline void kref_get(struct kref *kref)
{
refcount_inc(&kref->refcount);
}

该函数的传入参数为struct kref结构体体指针。

(4)引用计数器减1操作

同时,内核也提供了引用计数器减1操作的函数接口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 ;
}
return ;
}

参数:

  kref:要减1操作的struct kref结构体指针

  release:函数指针,当引用计数器的值为0时,则调用此函数

返回值:

成功:如果引用对象被成功释放则返回1

其他情况:返回0

4、kref的使用规则

在Documentation/kref.txt文件中总结了struct kref引用计数的一些规则,下面是该文件的简单翻译:

(1)介绍

对于哪些用在多种场合,被到处传递的结构,如果没有引用计数,出现bug几乎是肯定的事,因此,我们需要用到kref,它允许我们在已有的结构中方便地添加引用计数。

可以用如下的方式添加kref到已有的数据结构中:

struct my_data {
  …
  struct kref refcount;
  …
};

struct kref可以出现在自己定义结构体中的任意位置。

(2)初始化

在分配kref后,必须将kref进行初始化,可以调用kref_init()函数,将kref的计数值初始为1:

struct my_data *data;

data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
  return –ENOMEM;
kref_init(&data->refcount);

(3)kref的使用规则

初始化kref之后,kref的使用应该遵循以下三条规则:

1)如果你创建了一个结构指针的非暂时性副本,特别是当这个副本指针会被传递到其它执行线程时,你必须在传递副本指针之前执行kref_get():

kref_get(&data->refcount);

2)当你使用完,不再需要结构的指针,必须执行kref_put,如果这是结构指针的最后一个引用,release()函数会被调用,如果代码绝不会在没有拥有引用计数的请求下去调用kref_get(),在kref_put()时就不需要加锁:

kref_put(&data->refcount, data_release);

3)如果代码试图在还没有拥有引用计数的情况下就调用kref_get(),就必须串行化kref_put()和kref_get()的执行,因为很可能在kref_get()执行之前或者执行中,kref_put()就被调用并把整个结构释放掉:

例如,你分配了一些数据并把它传递到其它线程去处理:

void data_release(struct kref *kref)
{
  struct my_data *data = container_of(kref, struct my_data, refcount);
  kfree(data);
} void more_data_handling(void *cb_data)
{
  struct my_data *data = cb_data;
  …
  do stuff with data here
  …
  kref_put(&data->refcount, data_release);
} int my_data_handler(void)
{
  int rv = ;
  struct my_data *data;
  struct task_struct *task;   data = kmalloc(sizeof(*data), GFP_KERNEL);
  if (!data)
    return –ENOMEM;   kref_init(&data->refcount);
  kref_get(&data->refcount);
  task = kthread_run(more_data_handling, data, “more_data_handling”);
  if (task == ERR_PTR(-ENOMEM)) {
    rv = -ENOMEM;
    goto out;
  }
  …   do stuff with data here
  … out:
  kref_put(&data->refcount, data_release);
  return rv;
}

这样做,无论两个线程的执行顺序是怎么样都无所谓,kref_put()知道何时数据不再有引用计数,结构体可以被销毁,kref_get()调用不需要再加锁,因为在my_data_handler()中调用kref_get()时已经拥有一个引用,同样,kref_put()也不需要加锁。

注意规则一中的要求,必须在传递指针之前调用kref_get(),决不能写成下面的代码:

task = kthread_run(more_data_handling, data, “more_data_handling”);

if (task == ERR_PTR(-ENOMEM)) {
rv = -ENOMEM;
goto out;
} else {
/* BAD BAD BAD – get is after the handoff */
kref_get(&data->refcount);

不要认为自己在使用上面的代码时知道自己在做什么,首先,你可能并不知道你在做什么,其次,你可能知道你在做什么(在部分加锁的情况下上面的代码也是正确的),但一些修改或者复制你代码的人并不知道在做什么,这是一种不好的使用方式。

当然,在部分情况下也可以优化对get和put的使用,例如,你已经完成了对这个数据的处理,并要把它传递给其它线程,就不需要做多多余的get和put了:

/* Silly extra get and put */
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);

只需要做enqueue操作即可,可以在最后加一条注释:

enqueue(obj);
/* We are done with obj, so we pass our refcount off to the queue. DON'T TOUCH obj AFTER HERE! */

第三条规则处理起来是最麻烦的,例如,你有一列数据,每条数据都有kref计数,你希望获取第一条数据,但是你不能简单地把第一条数据从链表中取出并调用kref_get(),这违背了第三条规则,在调用kref_get()以前你并没有一个引用,因此,你需要增加一个mutex(或者其它锁):

static DEFINE_MUTEX(mutex);
static LIST_HEAD(q);
struct my_data {
struct kref refcount;
struct list_head link;
}; static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex); if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
kref_get(&entry->refcount);
}
mutex_unlock(&mutex);
return entry;
} static void release_entry(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
list_del(&entry->link);
kfree(entry);
} static void put_entry(struct my_data *entry)
{
mutex_lock(&mutex);
kref_put(&entry->refcount, release_entry);
mutex_unlock(&mutex);
}

如果你不想在整个释放过程中都加锁,kref_put的返回值就很有用了,例如,你不想在加锁的情况下调用kfree,可以像下面这样使用kref_put:

static void release_entry(struct kref *ref)
{
/* All work is done after the return from kref_put() */
} static void put_entry(struct my_data *entry)
{
mutex_lock(&mutex);
if (kref_put(&entry->refcount, release_entry)) {
list_del(&entry->link);
mutex_unlock(mutex);
kfree(entry);
} else
mutex_unlock(&mutex);
}

如果在撤销结构体的过程中需要调用其它的需要更长时间的函数,或者函数也可能获取同样的互斥锁,代码可以进行优化:

static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex);
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
if (!kref_get_unless_zero(&entry->refcount))
entry = NULL;
}
mutex_unlock(&mutex);
return entry;
} static void release_entry(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount); mutex_lock(&mutex);
list_del(&entry->link);
mutex_unlock(&mutex);
kfree(entry);
} static void put_entry(struct my_data *entry)
{
kref_put(&entry->refcount, release_entry);
}

5、小节

在Linux内核中,使用了struct kref这个结构体来进行对象管理的引用计数,对该结构体的操作为“原子”操作,常用的函数接口有用来初始化引用计数器的kref_init()、引用计数加1操作的kref_get()和引用计数减1操作的kref_put(),通过引用计数,能够方便地进行对象管理。

参考:

https://www.bbsmax.com/A/6pdDB7DXJw/

https://blog.csdn.net/qb_2008/article/details/6840387

Linux内核引用计数器kref结构的更多相关文章

  1. Linux基础系列—Linux内核源码目录结构

    /** ****************************************************************************** * @author    暴走的小 ...

  2. Linux 内核引用计数的操作

    一个 kobject 的其中一个关键函数是作为一个引用计数器, 给一个它被嵌入的对象. 只 要对这个对象的引用存在, 这个对象( 和支持它的代码) 必须继续存在. 来操作一个 kobject 的引用计 ...

  3. Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)

    转自:http://blog.csdn.net/shanshanpt/article/details/21024465 在2.6.24之后这个结构体有了较大的变化,此处先说一说2.6.16版本的sk_ ...

  4. Linux 内核源码目录结构

    arch:包含和硬件体系结构相关的代码,每种平台占用一个相应的目录. block:块设备驱动程序 I/O 调度. crypto:常用加密和散列算法(如AES.SHA等),还有一些压缩和CRC校验算法. ...

  5. linux内核驱动模型

    linux内核驱动模型,以2.6.32内核为例.(一边写一边看的,有点乱.) 1.以内核对象为基础.用kobject表示,相当于其它对象的基类,是构建linux驱动模型的关键.具有相同类型的内核对象构 ...

  6. Linux内核同步

    Linux内核剖析 之 内核同步 主要内容 1.内核请求何时以交错(interleave)的方式执行以及交错程度如何. 2.内核所实现的基本同步机制. 3.通常情况下如何使用内核提供的同步机制. 内核 ...

  7. Linux 内核里的“智能指针”【转】

    转自:http://blog.jobbole.com/88279/ 众所周知,C/C++语言本身并不支持垃圾回收机制,虽然语言本身具有极高的灵活性,但是当遇到大型的项目时,繁琐的内存管理往往让人痛苦异 ...

  8. 戴文的Linux内核专题:02源代码

    转自Linux中国 在下载并解压内核源代码后,用户可以看到许多文件夹和文件.尝试去找一个特定的文件或许是一个挑战.谢天谢地,源代码以一个特定的方式组织的.这使开发者能够轻松找到任何文件或者内核的一部分 ...

  9. 戴文的Linux内核专题:02 源代码【转】

    转自:https://linux.cn/article-2190-1.html?pr 在下载并解压内核源代码后,用户可以看到许多文件夹和文件.尝试去找一个特定的文件或许是一个挑战.谢天谢地,源代码以一 ...

随机推荐

  1. set容器与map容器的简单应用

    set容器中一些函数,取自百度其他大佬已总结好的,如有侵权,请联系删除! set的各成员函数列表如下: c++ stl容器set成员函数:begin()--返回指向第一个元素的迭代器 c++ stl容 ...

  2. 进程、线程、轻量级进程、协程与 go 的 goroutine

    本文内容 进程 线程 协程 Go 中的 goroutine 参考资料 最近,看一些文章,提到“协程”的概念,心想,进程,线程,协程,前两个很容易,任何一本关于操作系统的书都有说,开发时也经常用,但是协 ...

  3. android studio学习----添加项目库

    Library Project(库项目) compile project(':library') 引用名称为 library 的 module .需要注意的是,被引用的 module 需要在 {@pr ...

  4. Get SAP Code Page by External Name

    CL_ABAP_CODEPAGE - SAP_CODEPAGE 取SAP对应code page内码:

  5. loadrunner总结

    loadrunner总结 1.性能测试包含了哪些测试(至少举出3种) 负载测试,压力测试,疲劳强度测试,大数据量测试,并发测试. 2.负载测试和压力测试的区别 性能测试: 是通过自动化的测试工具模拟多 ...

  6. 简单mvc---模拟Springmvc

    1.注解篇 Auwowrited package org.aaron.mvc.annaotation; import java.lang.annotation.Documented; import j ...

  7. python-job

  8. Linux(Centos7)安装Oracle11.2.0数据字典初始化,监听,网络,创建用户等部分配置

    #创建数据字典和pl/sql包 @/u01/app/oracle/product/11.2.0/db_1/rdbms/admin/catalog.sql; @/u01/app/oracle/produ ...

  9. javascript中事件总结&通用的事件侦听器函数封装&事件委托

    前言: JAVASCRIPT与HTML之间的交互是通过事件来实现的.事件,就是文档或浏览器窗口中发生的一些特定交互瞬间.可以使用侦听器( 或处理程序 )来预定事件,以便事件发生时执行相应的代码.这种在 ...

  10. sql字符转义

    /** * sql 内的like % 代表一个或多个字符, _代表一个字符,这都是需要转义的 * @param s * @return */ public static String escapeSp ...