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. PLSQL 登录时弹出(没有登录)空白提示框

    如题,在登录的时候莫名出现了plsql登录时弹出(没有登录)的空白提示框,在网上找了很多方法之后都不行,然后发现plsql的 oracle主目录名莫名的成了空,然后直接重新把它的目录设置上 重启pls ...

  2. Python多任务—进程

    一.进程以及状态 1.进程 正在运行的应用程序就是一个进程.进程是资源分配的基本单元. Python多进程可以在多核CPU上运行,多进程充分利用了多核的资源. 2. 进程的状态 工作中,任务数往往大于 ...

  3. day 48

    目录 js BOM(浏览器对象模型) window对象 window子对象 弹出框 计时事件 DOM(文档对象模型) 查找标签 节点操作 事件 常用事件 绑定方式 jQuery jQuery介绍 jQ ...

  4. ES6 变量与解构(二)

    一.变量的声明与使用 [测试示例需要在node环境中测试,浏览器环境下并不完全兼容ES6代码]ES6中可以使用 {} 来包含任意一段代码,被 {} 包裹的内容称为一个代码块(局部作用域) let关键字 ...

  5. 阻止鼠标右键和F5刷新

    //禁止刷新的事件发生 document.onkeydown = function(){ if(event.keyCode==116){ event.keyCode = 0; event.cancel ...

  6. android studio学习----Failed to resolve: com.android.support:design:22.1.1

    这个目前好像没有合适的办法,唯一可行的就是 点击那个提示  进行SDK Manager下载就可以了 但是天朝的网啊,我试了很多次,突然的可以下载,运气啊 类似这一系列问题解决办法就是  重新更新SDK ...

  7. iOS 原生推送(APNS)的实现

    1.前期准备工作 创建你的APNs keys 或者 创建推送证书,这两个创建一个即可实现推送.这两个创建一个即可实现推送.这两个创建一个即可实现推送.重要的事情说三遍,我看评论区有小伙伴误解. 1.  ...

  8. idea 忽略提交文件

    https://blog.csdn.net/wangjun5159/article/details/74932433 https://blog.csdn.net/m0_38001814/article ...

  9. 微信小程序+php 授权登陆,完整代码

    先上图        实现流程: 1.授权登陆按钮和正文信息放到了同一个页面,未授权的时候显示登陆按钮,已授权的时候隐藏登陆按钮,显示正文信息,当然也可以授权和正文分开成两个页面,在授权页面的onlo ...

  10. STL杂记

    STL介绍: 我所理解的stl: 容器: 是一种数据结构,如list,vector,和deques ,以模板类的方法提供.为了访问容器中的数据,可以使用由容器类输出的迭代器: 算法:  是用来操作容器 ...