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

概述

    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中。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/403845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

近期H5项目开发小结

前言:2016差不多又过了半啦,最近参与了公司好几个h5项目(严格来说,也只能算是推广页面活动)。主要是新品牌的推广需要,当然也有给公司以前老客户做的案例。今天主要总结下为新品牌开发的2个h5推广&#xff…

vue-cli3中的vue.config.js配置

vue-cli3中的vue.config.js配置 我的跨域是配置通过chrome浏览器的跨域设置,前端修改跨域问题,以此解决跨域的, 故如果需要配置代理,就看proxy部分; const path require(path) const resolve (dir) > path.join…

获取python版本

import sys# global variablepyVersion 2 # set default python version to 2.x# init global variable(s)def init():global pyVersionpyVersion sys.version_info[0] # get major version of pythondef test():print(pyVersion)if __name__ __main__:init()test()运行情况…

会计的思考(37):“弱水三千,只取一瓢饮”--业务人员的财务意识

关键字: 会计体系 财务意识 投入产出 "任凭弱水三千,我只取一瓢饮", 出自《红楼梦》,第九十一回里,贾宝玉曾经这样语带机锋地试图去化解林黛玉刚刚上来的醋劲。本文以此引出对业务人员的财务意识的思考。 成功企业家,如…

编译QtAV工程库

去https://github.com/wang-bin/QtAV下载源代码 去https://sourceforge.net/projects/qtav/files/depends/QtAV-depends-windows-x86%2Bx64.7z/download下载依赖库QtAV-depends-windows-x86x64.7z 将里面的include目录内容和lib内容分别拷贝到Qt的include和lib目录下 QtAV解压后…

make -j32 21 | tee show.log

2. 2>&1是什么意思?2>&1应该分成两个部分来看,一个是2>以及另一个是&1,其中2>就是将标准出错重定向到某个特定的地方;&1是指无论标准输出在哪里。所以2>&1的意思就是说无论标准出错在哪里(哪怕…

依赖包报错Invalid options object. Less Loader has been initialized using an options object that does not

1.问题:yarn安装依赖包,启动项目报错 error in ./node_modules/ant-design-vue/dist/antd.less Module build failed: ValidationError: Invalid options object. Less Loader has been initialized using an options object that does not match the A…

Windows下显示目录大小及文件个数

From: http://blog.csdn.net/wmnothing/article/details/6590376 用批处理实现Windows下子目录大小统计功能 吴旻 泰岩网络工作室 统计当前目录下各文件夹的大小,在Linux下面比较简单,一个 du -sh * 命令就基本解决问题了。虽然在资源管理器是单击右键…

Asp.net页面和Html页面之间的关系

Asp.net页面显然要转化为普通的html页面才能在浏览器中显示,但是对于两者的关系,或者说从服务器在接受请求处理请求这段时间内对asp.net页面的操作一直不是很明白,下边的一段话可以让人豁然开朗,虽然并未谈论技术,但简…

kmalloc/kfree,vmalloc/vfree函数用法和区别

1.kmalloc1>kmalloc内存分配和malloc相似&#xff0c;除非被阻塞否则他执行的速度非常快&#xff0c;而且不对获得空间清零.< tiger说明&#xff1a;在用kmalloc申请函数后&#xff0c;要对起清零用memset()函数对申请的内存进行清零。> 2>kamlloc函数原型&#xf…

SQL Server开发接口生成方法

为提高开发效率&#xff0c;生成固定格式的接口是必须的&#xff0c;以下以提供新增/修改/删除/读取接口为例&#xff1a; 以常见的表结构为例&#xff0c;特殊表结构可自己尝试去调整方法 主要通过系视图 sys.columns生成方法:为包含列的对象&#xff08;如视图或表&#xff0…

vue-cli2定制ant-design-vue主题

本篇是vue-cli2定制主题&#xff0c;vue-cli3通过vue.config.js定制主题点击此处 1.引入less和less-loader&#xff08;如果报错&#xff0c;请将less-loader版本更改到5.0.0&#xff09; npm install less less-loader --save2.在 vue cli 2 中定制主题&#xff0c;修改build…

python中thread的setDaemon、join的用法

From: http://doudouclever.blog.163.com/blog/static/17511231020127232303469/ python中得thread的一些机制和C/C不同&#xff1a;在C/C中&#xff0c;主线程结束后&#xff0c;其子线程会默认被主线程kill掉。而在python中&#xff0c;主线程结束后&#xff0c;会默认等待子…

谈谈软件兼容性测试

1.软件兼容性测试兼容性测试之待测试项目在特定的硬件平台上&#xff0c;不同的应用软件不同&#xff0c;不同的操作系统平台上&#xff0c;在不同的网络等环境中能正常的运行的测试。兼容性测试的目的&#xff1a;带测试项目在不同的操作系统上正常运行&#xff0c;包括待测试…

YUV视频格式分析

Andrew Huang <bluedrum163.com> 转载请注明作者及联络方式在摄像头之类编程经常是会碰到YUV格式,而非大家比较熟悉的RGB格式. 我们可以把YUV看成是一个RGB的变种来理解.YUV的原理是把亮度与色度分离&#xff0c;研究证明,人眼对亮度的敏感超过色度。利用这个原理&#x…

自定义ant中table表格的展开图标 修改ant-vue-design中嵌套表格table的expandIcon自定义图标

效果&#xff1a; 1. <a-table:expandIcon"expandIcon":loading"loading":columns"columns":data-source"data"class"components-table-demo-nested"change"onChangeTable":scroll"{x:1300,y:y}":p…

Foundationd和Application Kit的类层次

Foundationd类 Application Kit类 转载于:https://www.cnblogs.com/ikodota/archive/2012/08/01/2618590.html

python多线程编程(1): python对多线程的支持

From: http://www.cnblogs.com/holbrook/archive/2012/03/01/2376408.html 前面介绍过多线程的基本概念&#xff0c;理解了这些基本概念&#xff0c;掌握python多线程编程就比较容易了。 在开始之前&#xff0c;首先要了解一下python对多线程的支持。 虚拟机层面 Python虚拟机…

三个数从小到大排序

描述 现在要写一个程序&#xff0c;实现给三个数排序的功能 输入输入三个正整数 输出给输入的三个正整数排序 样例输入20 7 33 样例输出 7 20 33 测试代码 1 #include "stdio.h"2 3 int main()4 {5 int a, b, c, t;6 scanf("%d%d%d", &a, &…

监听vuex的某条数据

需要现在计算属性里获取 computed: {tabType: {get() {return this.$store.state.tabType;},set(val) {this.$store.state.tabType val;},}},watch: {tabType(val) {this.allType this.$store.state.tabType;}, }