rtmutex赏析
【摘要】
rtmutex作为futex的底层实现,有两个比較重要的特性。一个是优先级继承,一个是死锁检測。本文对这两个特性的实现进行说明。
一、优先级继承
2007年火星探路者号的vxworks上发生了优先级反转。导致设备不断重新启动。
(http://research.microsoft.com/en-us/um/people/mbj/mars_pathfinder/mars_pathfinder.html),
优先级反转问题在大多数操作系统教材上都有提及,大概意思就是,A、B、C三个进程,优先级各自是Pa<Pb<Pc,如果有资源S,被A持有,某个时刻C来尝试获取S。被堵塞,接着B进程抢占A进程。而B又是一个死循环进程,这样C永远得不到调度的机会。
看上去就是优先级低的B比优先级相对高的C抢占了。
优先级反转的解决方式主要有两种。一种是优先级继承,还有一种是优先级天花板。
优先级继承的思路就是在进程获取资源假设被堵塞,则改动资源持有者的优先级(大多数情况是提升优先级),让资源持有者尽快完毕资源的操作后释放资源。优先级天花板则须要事先知道竞争资源的全部进程的优先级,当当中一个进程获取到资源后,则将进程优先级提升至最高的那个进程。这两者的差别是。前者是在获取资源堵塞时改动优先级,后者是获取资源成功后改动优先级。前者对系统调度影响较小,但实现较复杂。后者对系统调度影响大,但实现较简单。
Linux在2006年引入了优先级继承方案,在rtmutex中完毕。内核文档文件夹的rt-mutex-design.txt介绍了优先级反转和优先级继承的概念。并描写叙述了rtmutex的实现方案。本节以一种更白话的方式介绍rtmutex的优先级继承实现。
rtmutex.c有几个重要的数据结构,我们以因果顺序来描写叙述这些结构。
首先。你得有一把锁,这用struct rt_mutex来表示。有了锁之后,锁就可能有一个拥有者。于是struct rt_mutex内就有一个成员叫structtask_struct owner;这把锁可能会堵塞一些进程,那么struct rt_mutex里有一个链表。叫structplist_head wait_list,能够看出。这是一个优先级队列。队列的元素,是一些被封装成struct rt_mutex_waiter的进程描写叙述符,按进程的优先级来排序。既然一些进程会堵塞在这把锁上面,依据优先级继承的原理。锁的持有者owner,就必须參考一个优先级最高的堵塞进程。将owner的优先级提升至最高的这个堵塞进程,那么owner就须要维护一个链表,这个链表里保存了owner进程拥有的资源里,被堵塞的优先级最高的那些进程。这就是task
struct里struct plist_head pi_waiters的由来;另外,怎样知道一个进程是否被rt_mutex堵塞?于是又在task_struct里引入了structrt_mutex_waiter *pi_blocked_on,用来指示该进程被堵塞在哪个rt_mutex上。
以下,我们以样例来说明一下上面的数据结构是怎样联系起来的。
内核里,一个task_struct P,可能拥有n个资源,然后这n个资源堵塞了T个其它进程(T>=n)。
对于P拥有的某个资源,堵塞了总共T[i]个进程(∑T[i] = T。 0<=i<n)。这些进程都以rt_mutex_waiter的形式,通过按优先级顺序挂接到资源rt_mutex的wait_list链表上。接着,还要将这T[i]个进程中,优先级最高的那一个m(rt_mutex_waiter),通过pi_list_entry。链接到P的pi_waiters队列中。 也就是说,P的pi_waiters队列拥有n个元素,每一个元素都是一个封装成rt_mutex_waiter的task_struct,P的优先级。为这n个进程最高的那个。
为什么要维护这么一个pi_waiters链表,为什么不只保存一个P堵塞的最高优先级进程?
考虑这样的情况:
优先级为p的进程P。先后占有资源s1、s2,优先级为p1、p2的进程先后堵塞在这两个资源上。
(p<p1<p2)根据优先级继承协议。P的优先级先后变为p1、p2。 当P释放资源s2后。优先级应该降为多少?毫无疑问。应该减少为p1而不是p。 这也就是链表的来由。即我们须要跟踪该进程获取资源的一个路径,以此作为优先级调整的根据。
须要注意的是。一个rt_mutex_waiter,同一时间仅仅可能被链接进一个rt_mutex的wait_list里,由于一个进程m不能同一时候等待两个资源而被堵塞。
以下以futex的加解锁为例,说明rt_mutex的流程。
1.1 futex_lock_pi
能够看出。优先级继承属性的锁。须要严重关注锁的owner属性,以便实现优先级传递。
进程加锁的函数是futex_lock_pi,当进程进入内核态。发现自己是第一个挂起在此锁的
进程时,会通过 lock & FUTEX_TID_MASK获取用户态设置的owner的pid,
然后find_task_by_pid得到owner的task struct。
接着新分配一个pi_state结构:
- pi_state = alloc_pi_state();
接下来,初始化pi_state中的rtmutex,特别是owner字段赋值:
- rt_mutex_init_proxy_locked(&pi_state->pi_mutex)->rt_mutex_set_owner(lock, proxy_owner, 0);
这样就给rtmutex lock赋值了owner了。这些操作是在函数lookup_pi_state中完毕的。
这里我们引入了一个结构struct futex_pi_state ,该结构主要作用就是内置了一个rtmutex。
而全部涉及到优先级继承、传递等概念的实现,事实上都靠这个rtmutex来实现。
- futex_lock_pi(unsigned long uaddr)
- {
- struct rt_mutex_waiter waiter;
- struct futex_q q;
- //依据futex地址获取页框,来计算key
- get_user_pages_fast(addr, 1, 1, &page);
- q.key->both.offset |= FUT_OFF_INODE; /* inode-based key */
- q.key->shared.inode = page->mapping->host;
- q.key->shared.pgoff = page->index;
- //第一步,就是依据uaddr来找到相应的rtmutex。
- //首先。依据uaddr和共享内存相应的inode、page frame的组合为key。找到曾被该锁堵塞的futex_q对象。
- //(假设其它进程,线程以前在这把锁上堵塞过一次,
- //就至少能找到一个key匹配的futex_q对象)
- //找到futex_q对象后,就借用他的pi_state成员。也即rtmutex成员
- struct futex_q *find_q = find_match_key(q.key,hash_bucket[hash(uaddr)]);
- struct futex_pi_state *pi_state;
- //假设找不到匹配的 futex_q,说明我们是第一个堵塞在此锁的对象,
- //就分配futex_q里的pi_state成员
- //总之,到眼下为止,得到一个可用的pi_state也即rtmutex
- if(!find_q){
- q->pi_state = alloc_pi_state();
- pi_state = q->pi_state;
- }else
- pi_state = find_q->pi_state;
- //当然每次都须要将本次堵塞的对象以futex_q的形式增加hash冲突链
- q->task = current;
- plist_add(&q->list, &hash_bucket[hash(uaddr)]->chain);
- //開始将当前进程封装task struct
- waiter->task = current;
- struct rt_mutex *lock = &pi_state->pi_mutex;
- //获取原先的最高等待优先级任务,留待兴许比較
- old_top_waiter = rt_mutex_top_waiter(lock);
- //将本次rt_mutex_waiter加到futex_state->rtmutex的等待链表中
- plist_add(&waiter->list_entry, &lock->wait_list);
- //假设本次增加的waiter是该lock堵塞的最高优先级的进程,则须要改动
- //lock持有者task struct的pi_waiters链表。并提高lock持有者优先级。
- //这个就是优先级继承实现的精华所在。
- struct task_struct *owner = rt_mutex_owner(lock);
- if (waiter == rt_mutex_top_waiter(lock)) {
- //这里把以前的那个最高优先级的等待进程从持有者链表删除
- //有个疑问,这里是否会存在内存泄露?
- //不会,由于rt_mutex_waiter 是局部栈变量
- //这里也能够看出。为什么rt_mutex_waiter 要做成局部变量而不是动态分配变量,
- //是为了避免内存泄露。
- plist_del(&old_top_waiter->pi_list_entry, &owner->pi_waiters);
- plist_add(&waiter->pi_list_entry, &owner->pi_waiters);
- //一连串复杂的优先级修正
- __rt_mutex_adjust_prio(owner);
- }
- }
1.2 futex_unlock_pi
- futex_unlock_pi(unsigned long uaddr)
- {
- struct futex_hash_bucket *hb;
- //依据futex地址获取页框,来计算key
- get_user_pages_fast(addr, 1, 1, &page);
- q.key->both.offset |= FUT_OFF_INODE; /* inode-based key */
- q.key->shared.inode = page->mapping->host;
- q.key->shared.pgoff = page->index;
- //以key为基准,查找出hash冲突链里第一个被堵塞的futex_q
- //并尝试唤醒
- hb = hash_futex(&key);
- head = &hb->chain;
- plist_for_each_entry_safe(this, next, head, list) {
- if (!match_futex (&this->key, &key))
- continue;
- ret = wake_futex_pi(uaddr,uval,this);
- goto out_unlock;
- }
- }
- //详细的唤醒函数,尝试唤醒futex_q *this指向的进程。
- //并调整优先级
- wake_futex_pi(u32 __user *uaddr, unsigned long uval,struct futex_q *this)
- {
- //获取到该futex_q(进程)所持有的锁pi_state->rtmutex对象
- struct futex_pi_state *pi_state = this->pi_state;
- //获取下一个优先级最高的被堵塞者
- new_owner = rt_mutex_next_owner(&pi_state->pi_mutex);
- //将用户态lock字段更新owner为下一个持有者
- newval = FUTEX_WAITERS | task_pid_vnr(new_owner);
- cmpxchg_futex_value_locked(uaddr, uval, newval);
- //眼下,此锁的全部者已经不是当前进程了。因此将它从本进程
- //的链表中取下。加入到下一个owner的链表中
- list_del(&pi_state->list);
- list_add(&pi_state->list, &new_owner->pi_state_list);
- pi_state->owner = new_owner;
- //释放锁,优先级调整
- rt_mutex_unlock(&pi_state->pi_mutex);
- }
- rt_mutex_unlock(struct rt_mutex* rtmutex)
- {
- //唤醒一个最高优先级堵塞者
- wakeup_next_waiter(lock, 0);
- //调整当前进程的优先级。由于已经释放资源了,须要往下调一下优先级
- rt_mutex_adjust_prio(current);
- }
- static void wakeup_next_waiter(struct rt_mutex *lock)
- {
- //找出最高优先级的等待者(前面futex流程里也找过一次,用来更新用户态owner值)
- struct rt_mutex_waiter *waiter;
- waiter = rt_mutex_top_waiter(lock);
- //找到后,先从lock的堵塞队列里摘下来,由于该进程立即就不会被堵塞了
- plist_del(&waiter->list_entry, &lock->wait_list);
- //接着从当前进程的最高优先级堵塞队列里摘除。由于该进程是lock的最高优先级等待者,
- //也一定会被链接到锁持有者的最高优先级堵塞队列里
- pendowner = waiter->task;
- plist_del(&waiter->pi_list_entry, ¤t->pi_waiters);
- wake_up_process(pendowner);
- //设置rt_mutex的owner
- rt_mutex_set_owner(lock, pendowner, RT_MUTEX_OWNER_PENDING);
- //还没完。新的owner的pi_waiters链表还须要更新,由于新owner获取到锁之后,也開始
- //堵塞别人了。
- //注意,新owner不须要调高优先级,由于新owner已经是眼下为止,持有该锁
- //的最高优先级。仅仅有当新的高优先级进程尝试获取该锁被堵塞时,
- //才须要继续往上调整优先级
- next = rt_mutex_top_waiter(lock);
- plist_add(&next->pi_list_entry, &pendowner->pi_waiters);
- }
- void rt_mutex_adjust_prio(task)
- {
- prio = min(task_top_pi_waiter(task)->pi_list_entry.prio,
- task->normal_prio);
- task->prio = prio;
- }
好,到这一步。锁的持有者已经变成了新的owner,BUT!,
新的owner还不一定获取到了这把锁,仅仅是一个pending状态。
假设要真正获取到这把锁,还须要新owner被唤醒后,走
try_to_take_rt_mutex,将锁真正抓到。这个道理也是能够理解的。
新owner从堵塞到被唤醒。会走try_to_take_rt_mutex再次尝试
加锁。
- static int try_to_take_rt_mutex(struct rt_mutex *lock)
- {
- //假设该锁有一个owner。那么就尝试偷取。
- //如何算一次偷取呢?为什么要有偷取的概念呢?
- //以下再看。
- if (rt_mutex_owner(lock) && !try_to_steal_lock(lock, current))
- return 0;
- /* We got the lock. */
- //抓到锁,设置锁真正持有者,并清空可能的锁pending状态。
- rt_mutex_set_owner(lock, current, 0);
- return 1;
- }
什么叫偷锁? 当owner是pending状态,且当前进程的优先级比pending的
owner还要大,那么非常明显,应该让当前进程而不是pending的那个进程
来获取资源。这就叫偷。
这个情况在什么时候会发生?futex_unlock_pi时,选取了一个当时最高优先级
的进程作为候选者,但候选者没有唤醒时,这个时候又来了一个更高优先级
的进程尝试抓这把锁。结果更高优先级的进程就把这个锁抓走了。
能够类比一下。比方,某个时刻。你去面试一家公司,面试也通过了。这个公司就
会给你一个口头offer,但在这个书面offer下来之前,那家公司又面试了一个更牛逼
的程序猿,公司就找了个理由拒绝给你发书面offer,而是把书面offer给了那个更牛逼
的程序猿。这就是说,那个牛逼程序猿偷走了你的offer。于是你又不得不等待那个
牛逼程序猿辞职后,再次面试这家公司。
- static inline int try_to_steal_lock(struct rt_mutex *lock,
- struct task_struct *task)
- {
- struct task_struct *pendowner = rt_mutex_owner(lock);
- if (!rt_mutex_owner_pending(lock))
- return 0;
- if (pendowner == task)
- return 1;
- if (task->prio >= pendowner->prio) {
- return 0;
- }
- /* No chain handling, pending owner is not blocked on anything: */
- //找到lock的下一个最高优先级堵塞者。
- //这个堵塞者已经被挂在pending owner的pi_waiters最高优先级堵塞进程队列上了。
- //须要将其改挂到当前偷取者的pi_waiters上。让后调整pending owner的优先级,
- //由于pending owner已经不持有该锁了
- next = rt_mutex_top_waiter(lock);
- plist_del(&next->pi_list_entry, &pendowner->pi_waiters);
- __rt_mutex_adjust_prio(pendowner);
- //将pending owner改挂后,当前偷取者的优先级也得
- //依据偷取者的pi_waiters优先级来调整。
- plist_add(&next->pi_list_entry, &task->pi_waiters);
- __rt_mutex_adjust_prio(task);
- return 1;
- }
能够看出,进程优先级调整的时机,主要是在进程堵塞的最高优先级进程链pi_waiters,
成员被改动后,运行。
可是这个搜索的代价非常高。有点得不偿失,由于经典死锁检測会关注进程的全部可能路径
(如上图的节点D就是一个进程,他尝试去获取S和T),经典死锁检測会遍历S和T方向的路径。
而linux对这点做了简化。进程D仅仅须要关注他被堵塞的那个资源所在的路径就能够了,
并且不须要对资源图的全部节点搜索。仅须要以D为起点,进行一次遍历。
这套代码
正好嵌入在链式反应的函数实现中。
以下我们对链式反应的函数进行分析。
- static int rt_mutex_adjust_prio_chain(struct task_struct *task,
- int deadlock_detect,
- struct rt_mutex *orig_lock,
- struct rt_mutex_waiter *orig_waiter,
- struct task_struct *top_task)
- {
- struct rt_mutex *lock;
- struct rt_mutex_waiter *waiter, *top_waiter = orig_waiter;
- retry:
- //当前锁持有者task0是否被其它锁lock1堵塞,
- //假设堵塞的话则须要调整lock1->owner ,即task1的优先级
- //否则返回不须要处理。
- waiter = task->pi_blocked_on;
- if (!waiter)
- goto out;
- //得到lock1
- lock = waiter->lock;
- //死锁检測:假设遍历过程中,出现了一个环,
- //即要么锁反复了。要么进程反复了,就是一个死锁
- /* Deadlock detection */
- if (lock == orig_lock || rt_mutex_owner(lock) == top_task) {
- ret = deadlock_detect ?
- -EDEADLK : 0;
- goto out;
- }
- //获取lock1的最高优先级被堵塞者
- top_waiter = rt_mutex_top_waiter(lock);
- //将task0的优先级调整后。又一次加到lock1的等待者队列
- /* Requeue the waiter */
- plist_del(&waiter->list_entry, &lock->wait_list);
- waiter->list_entry.prio = task->prio;
- plist_add(&waiter->list_entry, &lock->wait_list);
- //获取lock1的持有者task1,作为下一个须要遍历的节点
- /* Grab the next task */
- task = rt_mutex_owner(lock);
- //假设改动优先级后插入lock1等待队列的task0,是最高优先级等待者。则
- //须要把task0插入到task1的最高优先级等待者队列,即task1->pi_waiters
- //然后继续尝试改动task1的优先级后。继续遍历链表。
- if (waiter == rt_mutex_top_waiter(lock)) {
- /* Boost the owner */
- plist_del(&top_waiter->pi_list_entry, &task->pi_waiters);
- waiter->pi_list_entry.prio = waiter->list_entry.prio;
- plist_add(&waiter->pi_list_entry, &task->pi_waiters);
- __rt_mutex_adjust_prio(task);
- //否则。说明task0改动优先级后。不是lock1的最高优先级等待者,
- //而且,task0以前是lock1的最高优先级等待者(即下句推断)
- //那么说明task0的优先级被减少了,须要将task0从task1的最高优先级
- //等待队列中删去。取下一个lock1的最高优先级等待者,加入到
- //task1的最高优先级等待队列pi_waiter中,再调整task1的优先级,
- //最后进行下一次节点遍历。
- } else if (top_waiter == waiter) {
- /* Deboost the owner */
- plist_del(&waiter->pi_list_entry, &task->pi_waiters);
- waiter = rt_mutex_top_waiter(lock);
- waiter->pi_list_entry.prio = waiter->list_entry.prio;
- plist_add(&waiter->pi_list_entry, &task->pi_waiters);
- __rt_mutex_adjust_prio(task);
- }
- goto again;
- out
- return ret;
- }
当然,这个链式反应也是有深度限制的。假设层数太多,可能会内核栈溢出,
因此内核给了一个上限。1024层。以避免这样的情况。
rtmutex赏析的更多相关文章
- 关注经典:CSS Awards 获奖网站作品赏析《第一季》
每天都有很多新的网站推出,其中不乏一些设计极其优秀的作品.这个系列的文章,我为大家挑选了2012年赢得 CSS Awards 大奖的50个最佳网站.这些鼓舞人心的网站作品代表了网页设计的最高水平,相信 ...
- chart.js图表库案例赏析,饼图添加文字
chart.js图表库案例赏析,饼图添加文字 Chart.js 是一个令人印象深刻的 JavaScript 图表库,建立在 HTML5 Canvas 基础上.目前,它支持6种图表类型(折线图,条形图, ...
- 计算机网络协议包头赏析-UDP
之前我们已经针对以太网.IP.TCP协议,进行了包头赏析.本次,我们继续UDP协议包头赏析. 提到TCP,想必大家会有所了解,它早已是家喻户晓的一个网络协议了,而UDP远没有他的大哥那么的有名,所以, ...
- 国际C语言混乱代码大赛代码赏析(一)【转】
本文转载自:http://blog.csdn.net/ce123_zhouwei/article/details/9073869 国际C语言混乱代码大赛代码赏析(一) 近段时间在看<C专家编程& ...
- DC游戏《斑鸠》原创赏析[转载]
游戏背景: 凤来之国本来只是边远地区的一个小国.但现在他们却自称为得到“神之力”的“神通者”,在“选民思想”“和平统一”之类的名义下开始了对各地的武力侵略. 事情的起因是因为凤来之 ...
- 老李分享:qtp自动化测试框架赏析-关键字自动化测试框架
老李分享:qtp自动化测试框架赏析-关键字自动化测试框架 QTP从2005年继winrunner,robot逐渐退出历史舞台之后,占领主流自动化测试工具市场已经10年之久.当初为了提高在自动化测试 ...
- 漫画赏析:Linux 内核到底长啥样(转)
知乎链接:https://zhuanlan.zhihu.com/p/51679405 来自 http://TurnOff.us 的漫画 “InSide The Linux Kernel” 本文转载自: ...
- Cocos2dx源码赏析(4)之Action动作
Cocos2dx源码赏析(4)之Action动作 本篇,依然是通过阅读源码的方式来简单赏析下Cocos2dx中Action动画的执行过程.当然,这里也只是通过这种方式来总结下对Cocos2dx引擎的理 ...
- Cocos2dx源码赏析(3)之事件分发
Cocos2dx源码赏析(3)之事件分发 这篇,继续从源码的角度赏析下Cocos2dx引擎的另一模块事件分发处理机制.引擎的版本是3.14.同时,也是学习总结的过程,希望通过这种方式来加深对Cocos ...
随机推荐
- Spring中使用Quartz之MethodInvokingJobDetailFactoryBean配置任务
Quartz是一个强大的企业级任务调度框架,Spring中继承并简化了Quartz. Spring中使用Quartz的3种方法(MethodInvokingJobDetailFactoryBean,i ...
- springboot项目封装为docker镜像
1.本次镜像的基础镜像是:https://www.cnblogs.com/JoeyWong/p/9173265.html 2.将打包好的项目文件放在与Dockerfile同级的目录下 3.Docker ...
- Jenkins学习总结(5)——免费DevOps开源工具简介
一:开发工具 1.版本控制系统 Git Git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理. 2.代码托管平台 GitLab GitLab是一个利用Ruby on ...
- MySQL创建表时加入的约束以及外键约束的的意义
1,创建表时加入的约束 a) 非空约束,not null b) 唯一约束,unique c) 主键约束,primary key d) 外键约束,foreign key 1,非空约束,针对某个字段设置其 ...
- Mybatis解决了JDBC编程哪些问题
一:Mybatis简介 MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动.创建connection.创 ...
- 使用iTools、PP助手清理垃圾前后文件夹对照图
1.1 documents清理前 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHl4am4=/font/5a6L5L2T/fontsize/400/fi ...
- libLAS1.8.0 编译和配置(VS2013+Win7 64)(一)
libLAS 是一个用来读写三维激光雷达数据(LiDAR) 的 C++ 库.在学习.科研和研发中都会广泛运用.怎样编译和配置自己所须要版本号的libLAS库确是一件麻烦耗时的事情. 笔者在Win7 6 ...
- [JZOJ 5894] [NOIP2018模拟10.5] 同余方程 解题报告(容斥)
题目链接: http://172.16.0.132/senior/#contest/show/2523/0 题目: 题解:(部分内容来自https://blog.csdn.net/gmh77/arti ...
- sicily 题目分类
为了方便刷题,直接把分类保存下来方便来找. 转自:http://dengbaoleng.iteye.com/blog/1505083 [数据结构/图论] 1310Right-HeavyTree笛卡尔树 ...
- BZOJ 2793: [Poi2012]Vouchers(调和级数)
Time Limit: 20 Sec Memory Limit: 64 MBSubmit: 582 Solved: 250[Submit][Status][Discuss] Description ...