转载:Linux kernel rt_mutex的背景和简介

时间:2021-03-15 20:46:57

From: http://blog.chinaunix.net/uid-23146151-id-3074045.html

最近在看linux rtmutex部分的实现过程,就像他注释中写的一样,还是比较简单的。其中比较复杂的部分就是关于任务优先级继承的部分。

首先介绍优先级反转的背景。

何为优先级反转?通俗来说就是低优先级的任务抢占了高优先级的运行权。下面举出一个优先级翻转的场景。

系统中有三个具有不同优先级的任务A,B,C。其中A的优先级最高,B次之,C的优先级最低。系统中A与C共享一个资源,此时C任务先就绪获得此资源的使用权限,这时A任务就绪,也想使用此资源,但是由于C已经在访问状态,所以A被阻塞。此时B任务就绪,如果系统中无优先级翻转处理等机制,那么B任务就会剥夺C任务的运行权限,从而也就剥夺了A任务的运行权限,这样稍低优先级的任务就抢占了高优先级的任务。此种现象就是优先级翻转问题。

解决优先级翻转的问题通常有几种,一种是优先级继承方式,还有就是天花板方式。

  1. 优先级继承方式是指在有任务阻塞到相应共享资源时,所获得资源的任务继承被阻塞任务的优先级,当然只是继承更高的优先级,小于等于的无视。在上例中就为A获得资源被C阻塞时,C任务继承A任务的高优先级,这样B再就绪时就不会抢占C的运行权,从而就避免了优先级翻转现象。
  2. 优先级天花板办法,就是将获得共享资源时,直接提升任务的优先级到可以获得此种资源的最高优先级。在上例中,当C获得此资源时,直接升到最高的优先级,如果系统中只有A,C使用此资源,那么就升级到A的优先级,这样B就绪时也无法抢占C的运行权,从而避免了优先级反转现象。

两者的区别主要在获得优先级提升的时间,前者在有高优先级任务发生阻塞时,后者是直接在获得资源时提升优先级。

再补充一个背景介绍,来自:http://blog.csdn.net/kendiv/article/details/1788966

1. 优先级反转(Priority Inversion)
    由于多进程共享资源,具有最高优先权的进程被低优先级进程阻塞,反而使具有中优先级的进程先于高优先级的进程执行,导致系统的崩溃。这就是所谓的优先级反转(Priority Inversion)。

2. 产生原因
      其实,优先级反转是在高优级(假设为A)的任务要访问一个被低优先级任务(假设为C)占有的资源时,被阻塞.而此时又有优先级高于占有资源的任务(C)而低于被阻塞的任务(A)的优先级的任务(假设为B)时,于是,占有资源的任务就被挂起(占有的资源仍为它占有),因为占有资源的任务优先级很低,所以,它可能一直被另外的任务挂起.而它占有的资源也就一直不能释放,这样,引起任务A一直没办法执行.而比它优先低的任务却可以执行.

所以,一个解决办法就是提高占有资源任务的优先级,让它正常执行,然后释放资源,以让任务A能正常获取资源而得以执行.

3. 解决方案 ( 优先级继承 / 优先级天花板 )

目前解决优先级反转有许多种方法。其中普遍使用的有2种方法:一种被称作优先级继承(priority inheritance);另一种被称作优先级极限(priority ceilings)。

A. 优先级继承(priority inheritance) 
      优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级.当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会自动被提升.   
    
  B. 优先级天花板(priority ceilings)
      优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级.(这个优先级称为该资源的优先级天花板)   
    
  A 和B的区别: 
    
  优先级继承,只有当占有资源的低优先级的任务被阻塞时,才会提高占有资源任务的优先级,而优先级天花板,不论是否发生阻塞,都提升.

-------------------------

rtmutex主要数据结构

rt_mutex,就是一个互斥锁的结构,只有三个元素。

  1. struct rt_mutex {
  2. raw_spinlock_t wait_lock; /* 保护此结构的自旋锁 */
  3. struct plist_head wait_list; /* 等待此互斥锁的所有waiter链表 */
  4. struct task_struct *owner; /* 获得此锁的task,如果没有为NULL */
  5. }

rtmutex waiter的数据结构:用于记录等待互斥锁得结构

  1. struct rt_mutex_waiter {
  2. struct plist_node    list_entry; /* 用于将此结构连接到对应的互斥锁上 */
  3. struct plist_node    pi_list_entry; /* 用于将此结构连接到互斥锁所有者上等待链表中 */
  4. struct task_struct    *task;  /* 此waiter所属的task,也即等待此mutex的task */
  5. struct rt_mutex        *lock; /* 此waiter等待的mutex */
  6. };

主要的数据结构如上所示,后续介绍rtmutex的具体函数调用流程,由于是mutex,那么主要从最大需求的三个方面来介绍,一个是init,一个是lock,最后一个就是unlock

1)初始化函数

  1. void __rt_mutex_init(struct rt_mutex *lock, const char *name)
  2. {
  3. lock->owner = NULL;
  4. raw_spin_lock_init(&lock->wait_lock);
  5. plist_head_init(&lock->wait_list);
  6. debug_rt_mutex_init(lock, name);
  7. }

此函数已经简单到不能再简单了,将rt_mutex中的相关item初始化。

2)lock函数

  1. void __sched rt_mutex_lock(struct rt_mutex *lock)
  2. {
  3. might_sleep();
  4. rt_mutex_fastlock(lock, TASK_UNINTERRUPTIBLE, 0, rt_mutex_slowlock);
  5. }

其中,rt_mutex_fastlock首先尝试快速获得lock,如果尝试无法成功,则调用rt_mutex_slowlock 继续获得锁。

3)unlock函数

  1. void __sched rt_mutex_unlock(struct rt_mutex *lock)
  2. {
  3. rt_mutex_fastunlock(lock, rt_mutex_slowunlock);
  4. }

此函数与上述lock函数一样,首先尝试快速的unlock,如果不符合条件,那么再调用rt_mutex_slowunlock函数完成解锁操作。

内部机制,我是很想好好阐述出来的,但是由于自己表达能力有限,等后续总结好了,再更改此篇博客。想写一篇好的技术博客真TM难啊。看懂一个东西简单,讲明白一个东西真TM难,想要理解为啥这么搞更TM难。