概述

klist是list的线程安全版本,他提供了整个链表的自旋锁,查找链表节点,对链表节点的插入和删除操作都要获得这个自旋锁。klist的节点数据结构是klist_node,klist_node引入引用计数,只有点引用计数减到0时才允许该node从链表中移除。当一个内核线程要移除一个node,必须要等待到node的引用计数释放,在此期间线程处于休眠状态,为了方便线程等待,klist引入等待移除节点者结构体klist_waiter,klist-waiter组成klist_remove_waiters(内核全局变量)链表,为保护klist_remove_waiters线程安全,引入klist_remove_lock(内核全局变量)自旋锁。为方便遍历klist,引入了迭代器klist_iter。其整体结构图如下:

没有找到一个方便的画数据结构的工具,图有空添加

用法

定义klist:

常规定义:

struct klist myklist;
klist_init(&myklist,get,put);

便捷宏方式定义:

DEFINE_KLIST(myklist,get,put);//定义一个myklist,并初始化

插入节点:

struct klist_node mynode;
klist_add_tail(&mynode,&mylist);

klist_add_xxx函数初始化node,并插入链表,插入链表后,引用计数为1

klist_add_tail向后插入,klist_add_head向前插入,kilst_add_after在某个节点的后面插入,klist_add_before在某个节点的前面插入。

删除节点:

klist_del(&mynode);

klist_del调用klist-put,减少引用计数,并设dead标记,当应用计数到0时,自动调用klist_release,把节点从klist中删除。

klist_remove(&mynode);

klist_remove把当前线程加入等待移除链表,减少引用计数,如果有其他内核线程占用引用计数,把当前线程休眠。

推荐:Linux内核源代码分析工具

[   凡是尝试做过内核分析的人都知道,Linux的内核组织结构虽然非常有条理,但是,它毕竟是众人合作的结果,在阅读代码的时候要将各个部分结合起来,确实是件非常困难的事

遍历klist

klist没有像list一样定义一系列的list_for_each_xxx宏。klist提供专门的迭代器结构提klist_iter,遍历前首先要初始化迭代器 ,klist_next()函数把当前迭代器向后移动,并返回移动后的node,klist_iter_init(),klist_iter_init_node()都是迭代器初始化函数,前者把迭代器当前位置设置为NULL,klist_next()自动从第一个node开始,后者可以指定当前node,迭代器指向node时会增加node的引用计数,当迭代器不用时必须调用klist_iter_exit退出迭代器,释放当前node的引用计数。

分析

klist_node有个dead字段,联合在n_klist指针中,n_klist只能默认指向klist,我们来看klist的定义

struct klist {spinlock_t        k_lock;struct list_head    k_list;void            (*get)(struct klist_node *);void            (*put)(struct klist_node *);
} __attribute__ ((aligned (4)));

4字节对齐,意味着klist实例的地址低两位总是0,所以这个低2位刚好可以作为其他用处,对该指针解引用前只须与掉这2位,来看源码

#define KNODE_DEAD        1LU
#define KNODE_KLIST_MASK    ~KNODE_DEAD
static struct klist *knode_klist(struct klist_node *knode)
{return (struct klist *)((unsigned long)knode->n_klist & KNODE_KLIST_MASK);//与掉低2位
}static bool knode_dead(struct klist_node *knode)
{
    return (unsigned long)knode->n_klist & KNODE_DEAD;  //根据低2位判断
}

那么为什么要引入这个dead标识呢?如果一个内核线程要让某个node无效,不能简单的从klist中把node摘下来,只能减少node的引用计数,但是由于其他内核线程也拥有该node的引用计数,所以节点还是在klist链中,遍历节点等操作时无法避开该node。引入这个标识后,只要设置这个标识,尽管该node还在klist链上,但是迭代操作的时候通过这个标识避开dead的节点。这样在该节点上不会有新的操作,通过链表遍历也无法获取到该节点,当其他内核线程不引用该node后,该node自动从klist链中移除。所以dead的作用是禁止再使用该node,但是已经被人家在用了还是继续可以再用。调用klist_del()会标示该node为dead。我们来看迭代器移动操作函数klist_next()

/*** klist_next - Ante up next node in list.* @i: Iterator structure.** First grab list lock. Decrement the reference count of the previous* node, if there was one. Grab the next node, increment its reference* count, drop the lock, and return that next node.*/
struct klist_node *klist_next(struct klist_iter *i)
{void (*put)(struct klist_node *) = i->i_klist->put;struct klist_node *last = i->i_cur;struct klist_node *next;spin_lock(&i->i_klist->k_lock);if (last) {next = to_klist_node(last->n_node.next);//如果迭代器当前指向一个nodeif (!klist_dec_and_del(last))  //如果引用计数减到0,会调用put函数put = NULL;} else               //如果迭代器当前指向null,返回首个nodenext = to_klist_node(i->i_klist->k_list.next);i->i_cur = NULL;while (next != to_klist_node(&i->i_klist->k_list)) {if (likely(!knode_dead(next))) {  //跳过dead的节点kref_get(&next->n_ref);i->i_cur = next;break;}next = to_klist_node(next->n_node.next);}spin_unlock(&i->i_klist->k_lock);if (put && last)put(last);return i->i_cur;
}

第二个问题klist是怎么迫使remove某个node的线程休眠的,又是怎么唤醒的?为了方便进程管理,引入了 klist_waiter结构,如下:

struct klist_waiter {struct list_head list;struct klist_node *node;//等待删除的nodestruct task_struct *process;//进程或者线程指针int woken;//唤醒标记
};

来看使线程进入休眠的代码,klist_remove函数:

/*** klist_remove - Decrement the refcount of node and wait for it to go away.* @n: node we're removing.*/
void klist_remove(struct klist_node *n)
{struct klist_waiter waiter;  //创建一个waiterwaiter.node = n;waiter.process = current;waiter.woken = 0;spin_lock(&klist_remove_lock); //锁住klist_remove_lock,klist_remove_lock专门是用来保护                 
                                      //klist_remove_waiters的list_add(&waiter.list, &klist_remove_waiters);//把waiter加入到klist_remove_waiters中//这里把一个局部变量加入到一个全局的链表结构中,//会不会引起内存越界后续讨论spin_unlock(&klist_remove_lock);klist_del(n);         //减小引用计数并判死刑for (;;) {set_current_state(TASK_UNINTERRUPTIBLE); //设置进程进入休眠状态if (waiter.woken)break;schedule(); //调度进程时当前进程进入休眠状态}__set_current_state(TASK_RUNNING);
}

klist_del函数调用klist_put调用klist_dec_and_del调用kref_put,kref_put当引用计数减到0时回调到klist_release函数,klist_release会释放等待者。进程的休眠是klist_remove和klist_release作用的结果,我们来看klist_release的源码:

static void klist_release(struct kref *kref)
{struct klist_waiter *waiter, *tmp;struct klist_node *n = container_of(kref, struct klist_node, n_ref);WARN_ON(!knode_dead(n));//要释放的节点一定是被判死刑的节点list_del(&n->n_node);   //把node从klist移除spin_lock(&klist_remove_lock);//保护&klist_remove_waiters,
/*遍历&klist_remove_waiter*/list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {if (waiter->node != n)continue;waiter->woken = 1;//如果发现有等待该node的等待着,设唤醒标示mb();wake_up_process(waiter->process);//唤醒该进程list_del(&waiter->list);//把waiter结构体从&klist_remove_waiters,移除}spin_unlock(&klist_remove_lock);knode_set_klist(n, NULL);
}

klist_remove函数把局部变量加入到全局链表中,但是由于klist_remove会使线程休眠,它返回前总是由klist_release把waiter从klist_remove_waiters移走,所以不会导致崩溃。其实klist_remove_waiters的链表节点实际都是一些内核栈中的waiter结构,这些线程都休眠在klist_remove中。

linux内核源代码分析----内核基础设施之klist相关推荐

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

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

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

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

  3. linux VFS概述以及内核源代码分析

    linux VFS概述以及内核源代码分析 一.   概述 Linux能够支持各种不同的文件系统是通过VFS实现的,由于不同的物理文件系统具有不同的组织结构和不同的处理方式,为了能够处理各种不同的物理文 ...

  4. 《LINUX3.0内核源代码分析》第一章:内存寻址

    https://blog.csdn.net/ekenlinbing/article/details/7613334 摘要:本章主要介绍了LINUX3.0内存寻址方面的内容,重点对follow_page ...

  5. 《LINUX3.0内核源代码分析》第二章:中断和异常 【转】

    转自:http://blog.chinaunix.net/uid-25845340-id-2982887.html 摘要:第二章主要讲述linux如何处理ARM cortex A9多核处理器的中断.异 ...

  6. linux关机机器语言,Linux 核心源代码分析 - 第十章 开机 关机 [续二] [超星]...

    版权声明:本文档录自超星阅览器"免费图书馆"中的<Linux 核心源代码分析>.原文为扫描版,本文本文档是在下(大天赐)[http://blog.sina.com.cn ...

  7. Linux内核源代码分析——fork()原理多进程网络模型

    今晚和一位500强的leader喝喝小酒吃吃烤鱼,生活乐无边.这位兄弟伙才毕业2年,已经做到管理层了,机遇和能力不可谓不好.喝酒之余,聊到Linux内核的两个问题--fork().exec()的原理. ...

  8. Linux内核源代码分析经验

      Linux的最大的好处之一就是它的源码公开.同时,公开的核心源码也吸引着无数的电脑爱好者和程序员:他们把解读和分析Linux的核心源码作为自己的 最大兴趣,把修改Linux源码和改造Linux系统 ...

  9. LSM内核源代码分析与测试(二)

    LSM内核相关源代码分析见:http://blog.csdn.net/lwyeluo/article/details/55215686 本文修改内核代码来测试自定义的安全模块 测试 操作系统ubunt ...

最新文章

  1. ios wallet开发_iOS: 使用UICollectionView实现Wallet效果
  2. 1. Nest Js
  3. [转]html中offsetTop、clientTop、scrollTop、offsetTop各属性介绍
  4. aspx repeater 用法_ASP.NET - Repeater 控件
  5. springboot系列(十)springboot整合shiro实现登录认证
  6. 构建基本的嵌入式Linux根文件系统
  7. Jmeter负载和压力测试
  8. android 下拉刷新listview,实现Android下拉刷新的ListView
  9. 通过反编译深入理解Java String及intern
  10. hightopo学习笔记---入门
  11. 炸了炸了~翻译器中的王者,科大讯飞翻译器2.0横空出世!| 钛空智慧星球推荐...
  12. 7-128 大于m的最小素数
  13. c语言小饭店等位就餐程序,小饭店预约登记管理系统,
  14. 【毕业设计】JSP+Java+MySql+微信小程序 基于微信小程序的扫码点餐系统
  15. bat获取管理员权限运行
  16. 浅谈防火墙对 FTP 的影响及故障排除
  17. 怎样做出完美的高达模型
  18. 大一寒假训练九(map,set,vector)【更新完成】
  19. 记一次服务器被木马注入攻击
  20. GEWV2.3L22A-SLIC烧坏问题经验案例

热门文章

  1. [转]2006年十二星座运程 目录
  2. Memory-Associated Differential Learning论文及代码解读
  3. 如何创建一个自记录的Makefile
  4. 成功的秘诀是什么_学习编码的10个成功秘诀
  5. Amazon Personalize:帮助释放精益数字业务的高级推荐解决方案的功能
  6. 位运算-查找数组中唯一成对的数
  7. python爬虫从入门到精通
  8. 单文件快速体验使用react输出hello_world
  9. Spring 实现数据库读写分离
  10. 【JQuery】初始化页面当异步加载时构建页面元素的顺序