kref是一个引用计数器,它被嵌套进其它的结构中,记录所嵌套结构的引用计数,并在计数清零时调用相应的清理函数。kref的原理和实现都非常简单,但要想用好却不容易,或者说kref被创建就是为了跟踪复杂情况下地结构引用销毁情况。所以这里先介绍kref的实现,再介绍其使用规则。

kref的头文件在include/linux/kref.h,实现在lib/kref.c。闲话少说,上代码。

  1. struct kref {
  2. atomic_t refcount;
  3. };

可以看到,kref的结构中就包含一个atomic_t类型的计数值。atomic_t是原子类型,对其操作都要求是原子执行的,有专门的原子操作API执行,即使在多处理器间也保持原子性。使用atomic_t类型充当计数值,就省去了加锁去锁的过程。

  1. void kref_set(struct kref *kref, int num)
  2. {
  3. atomic_set(&kref->refcount, num);
  4. smp_mb();
  5. }

kref_set 设置kref的初始计数值。具体计数值设置由原子操作atomic_set完成。之后还有一个smp_mb()是为了增加内存屏障,保证这一写操作会在之后的读写操作完成之前完成。

  1. void kref_init(struct kref *kref)
  2. {
  3. kref_set(kref, 1);
  4. }

kref_init 初始化kref的计数值为1。

  1. void kref_get(struct kref *kref)
  2. {
  3. WARN_ON(!atomic_read(&kref->refcount));
  4. atomic_inc(&kref->refcount);
  5. smp_mb__after_atomic_inc();
  6. }

kref_get递增kref的计数值。

  1. int kref_put(struct kref *kref, void (*release)(struct kref *kref))
  2. {
  3. WARN_ON(release == NULL);
  4. WARN_ON(release == (void (*)(struct kref *))kfree);
  5. if (atomic_dec_and_test(&kref->refcount)) {
  6. release(kref);
  7. return 1;
  8. }
  9. return 0;
  10. }

kref_put递减kref的计数值,如果计数值减为0,说明kref所指向的结构生命周期结束,会执行release释放函数。

所以说kref的API很简单,kref_init和kref_set基本都是初始时才会用到,平时常用的就是kref_get和kref_put。一旦在kref_put时计数值清零,立即调用结束函数。

kref设计得如此简单,是为了能灵活地用在各种结构的生命周期管理中。要用好它可不简单,好在Documentation/kref.txt中为我们总结了一些使用规则,下面简单翻译一下。

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

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

  1. struct my_data {
  2. ...
  3. struct kref refcount;
  4. ...
  5. };

kref可以出现在你结构中的任意位置。

在分配kref后你必须初始化它,可以调用kref_init,把kref计数值初始为1。

  1. struct my_data *data;
  2. data = kmalloc(sizeof(*data), GFP_KERNEL);
  3. if(!data)
  4. return -ENOMEM;
  5. kref_init(&data->refcount);

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

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

  1. kref_put(&data->refcount);

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

  1. kref_put(&data->refcount, data_release);

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

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

  1. void data_release(struct kref *kref)
  2. {
  3. struct my_data *data = container_of(kref, struct my_data, refcount);
  4. kree(data);
  5. }
  6. void more_data_handling(void *cb_data)
  7. {
  8. struct my_data *data = cb_data;
  9. .
  10. .  do stuff with data here
  11. .
  12. kref_put(&data->refcount, data_release);
  13. }
  14. int my_data_handler(void)
  15. {
  16. int rv = 0;
  17. struct my_data *data;
  18. struct task_struct *task;
  19. data = kmalloc(sizeof(*data), GFP_KERNEL);
  20. if (!data)
  21. return -ENOMEM;
  22. kref_init(&data->refcount);
  23. kref_get(&data->refcount);
  24. task = kthread_run(more_data_handling, data, "more_data_handling");
  25. if (task == ERR_PTR(-ENOMEM)){
  26. rv = -ENOMEM;
  27. goto out;
  28. }
  29. .
  30. .  do stuff with data here
  31. .
  32. out:
  33. kref_put(&data->refcount, data_release);
  34. return rv;
  35. }

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

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

  1. task = kthread_run(more_data_handling, data, "more_data_handling");
  2. if(task == ERR_PTR(-ENOMEM)) {
  3. rv = -ENOMEM;
  4. goto out;
  5. }
  6. else {
  7. /* BAD BAD BAD - get is after the handoff */
  8. kref_get(&data->refcount);

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

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

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

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

  1. enqueue(obj);
  2. /* 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(或者其它锁)。

  1. static DEFINE_MUTEX(mutex);
  2. static LIST_HEAD(q);
  3. struct my_data
  4. {
  5. struct kref refcount;
  6. struct list_head link;
  7. };
  8. static struct my_data *get_entry()
  9. {
  10. struct my_data *entry = NULL;
  11. mutex_lock(&mutex);
  12. if(!list_empty(&q)){
  13. entry = container_of(q.next, struct my_q_entry, link);
  14. kref_get(&entry->refcount);
  15. }
  16. mutex_unlock(&mutex);
  17. return entry;
  18. }
  19. static void release_entry(struct kref *ref)
  20. {
  21. struct my_data *entry = container_of(ref, struct my_data, refcount);
  22. list_del(&entry->link);
  23. kfree(entry);
  24. }
  25. static void put_entry(struct my_data *entry)
  26. {
  27. mutex_lock(&mutex);
  28. kref_put(&entry->refcount, release_entry);
  29. mutex_unlock(&mutex);
  30. }

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

  1. static void release_entry(struct kref *ref)
  2. {
  3. }
  4. static void put_entry(struct my_data *entry)
  5. {
  6. mutex_lock(&mutex);
  7. if(kref_put(&entry->refcount, release_entry)){
  8. list_del(&entry->link);
  9. mutex_unlock(&mutex);
  10. kfree(entry);
  11. }
  12. else
  13. mutex_unlock(&mutex);
  14. }

如果你在撤销结构的过程中需要调用其它的需要较长时间的函数,或者函数也可能要获取同样地互斥锁,这样做就很有用了。但要注意在release函数中做完撤销工作会使代码看起来更整洁。

Linux内核部件分析 记录生命周期的kref相关推荐

  1. Linux内核部件分析 设备驱动模型之driver ---mark 详细

    Linux内核部件分析 设备驱动模型之driver 转载:https://www.linuxidc.com/Linux/2011-10/44627p7.htm 上节我们分析设备驱动模型中的device ...

  2. linux内核部件分析之——设备驱动模型之class

    前面看过了设备驱动模型中的bus.device.driver,这三种都是有迹可循的.其中bus代表实际的总线,device代表实际的设备和接口,而driver则对应存在的驱动.但本节要介绍的class ...

  3. linux内核部件分析(十)——设备驱动模型之class,linux内核部件分析(十)——设备驱动模型之class...

    前面看过了设备驱动模型中的bus.device.driver,这三种都是有迹可循的.其中bus代表实际的总线,device代表实际的设备和接口,而driver则对应存在的驱动.但本节要介绍的class ...

  4. uboot启动linux内核流程分析(三)

    uboot bootz命令流程图 Uboot启动linux内核是使用bootz命令,bootz是如何启动linux内核?uboot的生命周期是怎么终止的?linux是如何启动? 启动linux内核的时 ...

  5. 《Linux内核情景分析》阅读笔记

    <Linux内核情景分析>这本书读过了一遍,不想继续读第二遍了. <Linux Kernel Development>这本书前后读了3遍,写得实在是好,正所谓"布衣暖 ...

  6. linux内核源代码分析----内核基础设施之klist

    概述 klist是list的线程安全版本,他提供了整个链表的自旋锁,查找链表节点,对链表节点的插入和删除操作都要获得这个自旋锁.klist的节点数据结构是klist_node,klist_node引入 ...

  7. Linux内核源代码分析-目录

    第一部分 Linux 内核源代码 arch/i386/kernel/entry.S 2 arch/i386/kernel/init_task.c 8 arch/i386/kernel/irq.c 8 ...

  8. Linux内核源代码分析——可执行文件header处理(二进制文件读写范例,写DUL工具入门指引)...

    在把Linux内核源代码生成Image之前,需要把执行文件头结构信息剔除出来.这个过程对理解Linux内核具有很大的帮助.同时,由于是对可执行文件进行直接读写操作,想写DUL工具的童鞋可以在这里学习到 ...

  9. Linux内核协议栈分析之——tcp/ip通信并不神秘

    Jack:计算机如何进行通信? 我:我可以告诉你带Linux操作系统的计算机如何进行通信. Jack:带Linux操作系统的计算机?这和不带操作系统的计算机有区别吗? 我:有的. Jack:哦.那你说 ...

  10. linux 内核抢占分析

    linux 内核抢占分析 在 Linux 2.6 以后版本的 Linux 内核中,一个内核任务可以被抢占,从而提高系统的实时性.这样做最主要的优势在于,可以极大地增强系统的用户交互性,用户将会觉得鼠标 ...

最新文章

  1. 架构师成长之路-个人学习经验分享(公司研发峰会演讲ppt)
  2. wine清除软件残余图标
  3. laravel 5.8 guzzle get 参数_Laravel速查表 Cache Cookie Request
  4. 拯救者Y7000P 2020H款安装deepin20.5后资源空闲时经常出现风扇狂转现象
  5. 【Python】RotatingFileHandler:log日志文件自定义大小+滚动输出
  6. 现代软件工程系列 学生的精彩文章 (4) 为用户服务
  7. WIN7 系统破解LoadRunner 11
  8. 如何用python做模型_python的模型如何使用?
  9. jQuery图片轮播插件 jQuery Cycle Plugin
  10. 如何让Java文件在虚拟机中运行_深入理解JVM--Java程序如何在虚拟机中运行
  11. Python正则表达式指南下半部
  12. java爬虫(二)- Jsoup
  13. 谈谈时间序列的平稳性
  14. 夏昕ibatisiBATIS 2.0 开发指南配置文件说明
  15. win10电脑打开计算机快捷键,win10如何打开计算器工具 快速打开Win10计算器的四种方法...
  16. 金彩教育:高转化详情页怎么优化
  17. android手机截图,安卓手机怎么截屏,安卓手机怎么截屏幕图OPPO
  18. lisp 车位块自动编号_CAD如何生成自动编号
  19. 2003年新版CCNP考试大纲---CITV5.0(转)
  20. Method breakpoint reached日志问题

热门文章

  1. 深入解析Invoke and BeginInvoke, 同步与异步解析
  2. DEFCON China倒计时 没想到炸屏“玩坏”百度
  3. python面向对象编程(1)
  4. poj——3349 哈希加模拟
  5. Rotate List leetcode
  6. Docker-registry + GlusterFS
  7. DNS服务器上的IP地址修改
  8. 六神不安,生死命悬一线
  9. AgileEAS.NET平台开发实例-药店系统-视频教程系列-索引
  10. mysql xa事务简单实现