mutex的出现是为了解决优先级反转的问题,由于优先级反转对实时性影响太大,所以mutex 的稳定性直接影响了实时性。纵观目前多种实时操作系统mutex 的设计原理是多多少少有一点问题的,raw os 的mutex 模块成功弥补了其它实时系统在这方面的不足。
raw os 的mutex同时支持优先级置顶和优先级继承的方式来解决优先级反转的问题。
raw os 的mutex比较其它实时系统,比如ucos, ecos, freertos 等占尽了优势,因为当一个任务经历两次以上的优先级提升时,其它系统基本都不支持优先级的逐步还原,那样带来的后果是不能解决优先级反转带来的问题。而且像ucos系统去改变一个优先级被提升之后的任务的优先级的话,整个逻辑都会出错。而且rawos 的mutex 同时支持优先级置顶和优先级反转的策略,增加了用户使用的策略,显得更为灵活强大。
当有一个临界区资源同时存在不同优先级的任务抢占时,推荐使用mutex而不是semaphore, 因为优先级反转的问题对实时性伤害太大。
上一段话来着raw-os的作者jorya_txj的博客:http://blog.csdn.net/jorya_txj/article/details/8545145
下面可以通过mutex相关源码来看看raw-os是如何做到优先级的逐步还原,最主要看raw_mutex_get和raw_mutex_put函数
//创建一个mutex
RAW_U16 raw_mutex_create(RAW_MUTEX *mutex_ptr, RAW_U8 *name_ptr, RAW_U8 policy, RAW_U8 ceiling_prio)
{
#if (RAW_MUTEX_FUNCTION_CHECK > 0)
if (mutex_ptr == 0) {
return RAW_NULL_OBJECT;
}
if ((policy != RAW_MUTEX_CEILING_POLICY) && (policy != RAW_MUTEX_INHERIT_POLICY) && (policy != RAW_MUTEX_NONE_POLICY)) {
return RAW_MUTEX_NO_POLICY;
}
#endif
list_init(&mutex_ptr->common_block_obj.block_list);//初始化该互斥锁的阻塞任务链表
mutex_ptr->common_block_obj.block_way = RAW_BLOCKED_WAY_PRIO;//初始化任务阻塞方式,规定使用基于优先级
mutex_ptr->common_block_obj.name = name_ptr;//初始化互斥锁名称
mutex_ptr->mtxtsk = 0;//初始化该互斥锁获得者,初始化为0
mutex_ptr->mtxlist = 0;//初始化该互斥锁链接的下一个节点指针为0
mutex_ptr->policy = policy;//初始化该互斥锁使用策略(RAW_MUTEX_INHERIT_POLICY:优先级继承
RAW_MUTEX_CEILING_POLICY:优先级置顶//)
/*
***************************************************
*优先级继承策略:如果有高优先级的任务task1进入运行态并且要获取互斥锁mutex,而此时mutex被低优先级任务task2占用,
*这时需要把task2的任务暂时置为与task1一样的优先级,让task2快点运行然后释放mutex,再恢复task2的优先级,让task1能够得以尽快执行。
*优先级置顶策略:如果任务需要获取互斥锁,并且该互斥锁没有被占用,则把该任务的优先级先置为该互斥锁设定的优
*先级置顶值。(使用优先级置顶策略时,该优先级置顶值必须比可能获取该互斥锁的所有任务优先级别都高)
***************************************************
*/
mutex_ptr->ceiling_prio = ceiling_prio;//初始化ceiling_prio,用于使用优先级置顶策略,设定置顶的优先级号,
//该优先级必须高于所有可能会获取该锁的所有任务级别。
mutex_ptr->common_block_obj.object_type = RAW_MUTEX_OBJ_TYPE;//object类型标准
return RAW_SUCCESS;
}
/*用于获取指定互斥锁*/
RAW_U16 raw_mutex_get(RAW_MUTEX *mutex_ptr, RAW_U32 wait_option)
{
RAW_U16 error_status;
RAW_TASK_OBJ *mtxtsk;
RAW_SR_ALLOC();
#if (RAW_MUTEX_FUNCTION_CHECK > 0)
if (mutex_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (raw_int_nesting) { //中断中不允许获取互斥锁,因为该函数可能引起任务阻塞
return RAW_NOT_CALLED_BY_ISR;
}
#endif
if (mutex_ptr->common_block_obj.object_type != RAW_MUTEX_OBJ_TYPE) {
return RAW_ERROR_OBJECT_TYPE;
}
RAW_CRITICAL_ENTER();
if (raw_task_active == mutex_ptr->mtxtsk) { //如果当前任务已经占用着这个互斥锁了,返回RAW_MUTEX_DEADLOCK
#if (CONFIG_RAW_ASSERT > 0)
RAW_ASSERT(0);
#endif
RAW_CRITICAL_EXIT();
return RAW_MUTEX_DEADLOCK;
}
//互斥锁使用优先级置顶策略的处理
if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
if (raw_task_active->bpriority < mutex_ptr->ceiling_prio) {//如果当前任务基优先级高于设定的
//置顶优先级,则违反了优先级置顶的规则,
//设定置顶优先级必须比所有可能获取该锁的任务的优先级都高
RAW_CRITICAL_EXIT();
return RAW_EXCEED_CEILING_PRIORITY;
}
}
mtxtsk = mutex_ptr->mtxtsk; //取出互斥锁当前占用任务的任务块链表
//如果当前没有任务占有它,可以获取该互斥锁
if (mtxtsk == 0) {
/* Get lock */
mutex_ptr->mtxtsk = raw_task_active;
/*
下面两句代码把该互斥锁插到当前任务块raw_task_active->mtxlist所指互斥锁链表的头部
*/
mutex_ptr->mtxlist = raw_task_active->mtxlist;
raw_task_active->mtxlist = mutex_ptr;
if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {//如果使用的是优先级置顶策略,
//并且任务优先级小于置顶优先级,
//则还需要改变当前任务优先级
if (raw_task_active->priority > mutex_ptr->ceiling_prio) {
/*
*****************************************************************************************************
*如果tcb是就绪态的,则改变当前任务优先级后,更新就绪队列。如果是当前运行任务,则插到相应链表的最前端,
*否则插到相应链表的最后端
*如果tcb是带有阻塞的,而且必须是阻塞在该互斥锁上,函数才会调用mtx_chg_pri(tcb, old_pri)的有效部分,
*在mtx_chg_pri函数中
*对于采用优先级置顶的,因为所有可能获取该mutex的任务优先级都不会高于置顶优先级,所有不需要做什么。
*而对于采用优先级继承的,一旦有阻塞在该mutex的任务改变了优先级,则需要判断并按照需要改变当前占有锁任务的优先级:
*(情况①:当前占有锁任务优先级 低于 已被改变优先级的任务的新优先级,则需要把占有锁任务优先级拉高。
* 情况②:当前占有锁任务优先级 高于或等于 已被改变优先级的任务的新优先级,
* 则需要先判断占有锁任务优先级是否跟被改变优先级的任务的旧优先级一样,
* 如果一样,说明占有锁任务必须重新在该mutex的阻塞任务链表找到最高优先级,并赋给占有锁任务优先级)
**************************************************************************************************
*/
//任务为就绪态,下面函数只是简单的改变优先级并更新就绪任务队列
change_internal_task_priority(raw_task_active, mutex_ptr->ceiling_prio);
}
}
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
//下面是对互斥锁已被占有的处理
if (wait_option == RAW_NO_WAIT) {//如果等待方式选择RAW_NO_WAIT,则退出返回RAW_NO_PEND_WAIT
RAW_CRITICAL_EXIT();
return RAW_NO_PEND_WAIT;
}
/*system is locked so task can not be blocked just return immediately*/
SYSTEM_LOCK_PROCESS();
/*
**********************************************************************************
下面是对于互斥锁已被占用情况的处理,只需要对使用优先级继承策略的锁才需要,
因为优先级置顶的锁的占用任务当前优先级一定是可能获取该锁的任务中最高的
**********************************************************************************
*/
if (mutex_ptr->policy == RAW_MUTEX_INHERIT_POLICY) {
//只有在当前任务的优先级 高于 占用锁任务优先级 ,才让占用锁任务继承当前任务优先级
if (raw_task_active->priority < mtxtsk->priority) {
/*
***************************************************************************
*如果tcb是就绪态的,则改变当前任务优先级后,更新就绪队列。如果是当前运行任务,则插到相应链表的最前端,
*否则插到相应链表的最后端。
*如果tcb是带有阻塞的,而且必须是阻塞在该互斥锁上,函数才会调用mtx_chg_pri(tcb, old_pri)的有效部分。
*在mtx_chg_pri函数中
*对于采用优先级置顶的,因为所有可能获取该mutex的任务优先级都不会高于置顶优先级,所有不需要做什么。
*而对于采用优先级继承的,一旦有阻塞在该mutex的任务改变了优先级,则需要判断并按照需要改变当前占有锁任务的优先级:
*(情况①:当前占有锁任务优先级 低于 已被改变优先级的任务的新优先级,则需要把占有锁任务优先级拉高。
* 情况②:当前占有锁任务优先级 高于或等于 已被改变优先级的任务的新优先级,
* 则需要先判断占有锁任务优先级是否跟被改变优先级的任务的旧优先级一样,
* 如果一样,说明占有锁任务必须重新在该mutex的阻塞任务链表找到最高优先级,并赋给占有锁任务优先级)
*
***************************************************************************
*/
//mtxtsk又么是就绪态,又么是阻塞在其它obj上,下面函数只是简单的改变优先级并更新就绪任务队列
change_internal_task_priority(mtxtsk, raw_task_active->priority);
}
}
/*最后需要把获取不到锁并且需要等待的任务阻塞起来*/
raw_pend_object((RAW_COMMON_BLOCK_OBJECT *)mutex_ptr, raw_task_active, wait_option);
RAW_CRITICAL_EXIT();
/*执行一次调度*/
raw_sched();
/*
*****************************************************************
获取阻塞结束状态,一般出现以下两种情况:
RAW_SUCCESS:成功获取互斥锁
RAW_BLOCK_TIMEOUT:超时退出阻塞
*****************************************************************
*/
error_status = block_state_post_process(raw_task_active, 0);
return error_status;
}
注:从上面的源码可以总结出在任务获取互斥锁时:
①如果该锁使用的是优先级置顶策略时,那么只有在锁不被占用时才会更新该任务(此时该任务也会作为该锁的占有者)的优先级为置顶优先级(如果任务的所有占有锁都是优先级置顶,那该任务当前优先级肯定是所有锁置顶优先级最高的那一个)。
②如果该锁使用的是优先级继承策略时,那么只有在锁被占用了才会更改锁占有者的优先级为企图获取锁的任务的优先级,前提是企图获取锁的任务优先级比占有者高。(如果任务的所有占有锁都是优先级继承,那该任务的当前优先级肯定是所有锁阻塞任务中最高优先级的哪个)
③综上所述,不管任务当前所拥有的锁是采用什么策略的,任务的当前优先级总是所有锁中涉及到最高优先级的那一个优先级。
举个栗子,如下图所示:
上面刚开始任务task的优先级为11,占有锁mutex1,mutex2,mutex6,当有任务task1来获取它,这时阻塞在该锁上,task优先级继承task1变为10。又有任务task2来获取mutex2,task2被阻塞但是优先级不高于11不高于10,所以不继承。之后task获取mutex3,置顶优先级为9比task当前优先级高,所以需要拉高task优先级变为9,以此类推下去。
/*用于释放指定互斥锁*/
RAW_U16 raw_mutex_put(RAW_MUTEX *mutex_ptr)
{
LIST *block_list_head;
RAW_TASK_OBJ *tcb;
RAW_SR_ALLOC();
#if (RAW_MUTEX_FUNCTION_CHECK > 0)
if (mutex_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (raw_int_nesting) {
return RAW_NOT_CALLED_BY_ISR;
}
#endif
if (mutex_ptr->common_block_obj.object_type != RAW_MUTEX_OBJ_TYPE) {
return RAW_ERROR_OBJECT_TYPE;
}
block_list_head = &mutex_ptr->common_block_obj.block_list;//获取阻塞链表头
RAW_CRITICAL_ENTER();
if (raw_task_active != mutex_ptr->mtxtsk) {//互斥锁只能由占有它的任务释放
RAW_CRITICAL_EXIT();
return RAW_MUTEX_NOT_RELEASE_BY_OCCYPY;
}
release_mutex(raw_task_active, mutex_ptr);//释放指定互斥锁,会还原任务前一阶段的优先级
if (is_list_empty(block_list_head)) {//没有阻塞任务则仅仅返回就行了
mutex_ptr->mtxtsk = 0;
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
/*以下是对存在阻塞任务的情况进行处理*/
//获取阻塞链表的第一个任务控制块(也是优先级别最高的)
tcb = list_entry(block_list_head->next, RAW_TASK_OBJ, task_list);
//唤醒该任务,但还不会调度
raw_wake_object(tcb);
/*填充该互斥锁的成员 */
mutex_ptr->mtxtsk = tcb;
mutex_ptr->mtxlist = tcb->mtxlist;
tcb->mtxlist = mutex_ptr;
if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {//如果采用优先级置顶策略,还需要改变新占有锁任务优先级
if (tcb->priority > mutex_ptr->ceiling_prio) {
/*
********************************************************************************************************************
*如果tcb是就绪态的,则改变当前任务优先级后,更新就绪队列。如果是当前运行任务,则插到相应链表的最前端,
*否则插到相应链表的最后端
*
*
*如果tcb是带有阻塞的,而且必须是阻塞在该互斥锁上,函数才会调用mtx_chg_pri(tcb, old_pri)的有效部分,
*在mtx_chg_pri函数中
*
*对于采用优先级置顶的,因为所有可能获取该mutex的任务优先级都不会高于置顶优先级,所有不需要做什么。
*
*而对于采用优先级继承的,一旦有阻塞在该mutex的任务改变了优先级,则需要判断并按照需要改变当前占有锁任务的优先级:
*(情况①:当前占有锁任务优先级 低于 已被改变优先级的任务的新优先级,则需要把占有锁任务优先级拉高。
* 情况②:当前占有锁任务优先级 高于或等于 已被改变优先级的任务的新优先级,
* 则需要先判断占有锁任务优先级是否跟被改变优先级的任务的旧优先级一样,
* 如果一样,说明占有锁任务必须重新在该mutex的阻塞任务链表找到最高优先级,并赋给占有锁任务优先级)
*
*********************************************************************************************************************
*/
//占有锁任务已被更新并为就绪态,下面函数只是简单的改变优先级并更新就绪任务队列
change_internal_task_priority(tcb, mutex_ptr->ceiling_prio);
}
}
RAW_CRITICAL_EXIT();
//最后需要执行一次可能的调度
do_possible_sche();
return RAW_SUCCESS;
}
/*
******************************************************************************************
*释放指定任务的指定互斥锁,部分说明参见该函数后的流程图
*①relmtxcb为0,该函数相当于寻找整条阻塞任务链表中的最高优先级然后赋给指定任务tcb
*②relmtxcb不为0,把指定mutex从tcb的占有锁链表里释放,并把tcb的mtxlist中mutex的最高优先级
* 赋给tcb的当前优先级
******************************************************************************************
*/
static RAW_VOID release_mutex(RAW_TASK_OBJ *tcb, RAW_MUTEX *relmtxcb)
{
RAW_MUTEX *mtxcb, **prev;
RAW_U8 newpri, pri;
RAW_TASK_OBJ *first_block_task;
LIST *block_list_head;
/* (B) The base priority of task */
newpri = tcb->bpriority;
/* (A) The highest priority in mutex which is locked */
pri = newpri;
prev = &tcb->mtxlist;
while ((mtxcb = *prev) != 0) {
if (mtxcb == relmtxcb) {//如果当前提取的互斥锁与指定互斥锁相匹配,则把该互斥锁从链表里摘除
/* Delete self from list and tcb->mtxlist point to next*/
*prev = mtxcb->mtxlist;
continue;//重新回到while开始处,对剩余mutex进行处理
}
//以下switch是用于对每个匹配不到的mutex所涉及优先级的判断及处理
switch (mtxcb->policy) {
case RAW_MUTEX_CEILING_POLICY: //如果该互斥锁的策略为优先级置顶,则只需取出置顶优先级值
pri = mtxcb->ceiling_prio;
break;
case RAW_MUTEX_INHERIT_POLICY://如果使用优先级继承,则需要取出阻塞在该锁的第一个任务的优先级值
block_list_head = &mtxcb->common_block_obj.block_list;
if (!is_list_empty(block_list_head)) {
first_block_task = list_entry(block_list_head->next, RAW_TASK_OBJ, task_list);
pri = first_block_task->priority;
}
break;
default:
break;
}
//newpri永远保持当前对链表遍历过的mutex涉及优先级最高的那一个
if (newpri > pri) {
newpri = pri;
}
prev = &mtxcb->mtxlist;
}
//最后改变任务的优先级
if ( newpri != tcb->priority ) {
/* Change priority of lock get task */
change_internal_task_priority(tcb, newpri);
}
}
注:
从上面的源码及分析可以得知:在任务每次释放互斥锁后总是要更新任务的当前优先级为所以占有锁中涉及到的最高优先级,这样对应与获取互斥锁时,总是保持占有锁任务的优先级为最高值。从而实现了优先级的逐步还原。
/*
*****************************************************************************************************************
**********************************主要用在阻塞于mutex的任务被改变了优先级了**************************************
*
*对于采用优先级置顶的,因为所有可能获取该mutex的任务优先级都不会高于置顶优先级,所有不需要做什么。
*
*而对于采用优先级继承的,一旦有阻塞在该mutex的任务改变了优先级,则需要判断并按照需要改变当前占有锁任务的优先级:
*(情况①:当前占有锁任务优先级 低于 已被改变优先级的任务的新优先级,则需要把占有锁任务优先级拉高。
* 情况②:当前占有锁任务优先级 高于或等于 已被改变优先级的任务的新优先级,
* 则需要先判断占有锁任务优先级是否跟被改变优先级的任务的旧优先级一样,
* 如果一样,说明占有锁任务必须重新在该mutex的阻塞任务链表找到最高优先级,并赋给占有锁任务优先级)
*****************************************************************************************************************
*/
RAW_VOID mtx_chg_pri(RAW_TASK_OBJ *tcb, RAW_U8 oldpri)
{
RAW_MUTEX *mtxcb;
RAW_TASK_OBJ *mtxtsk;
mtxcb = (RAW_MUTEX *)(tcb->block_obj);
if (mtxcb->common_block_obj.object_type == RAW_MUTEX_OBJ_TYPE) {//必须是阻塞于互斥锁的任务才能进行该优先级转换
if (mtxcb->policy == RAW_MUTEX_INHERIT_POLICY) {//对优先级继承才需要处理
mtxtsk = mtxcb->mtxtsk;
if (mtxtsk->priority > tcb->priority) {//指定任务的优先级高于当前占有锁任务优先级
/*
********************************************************************************************************************
*如果mtxtsk是就绪态的,则改变当前任务优先级后,更新就绪队列。如果是当前运行任务,则插到相应链表的最前端,
*否则插到相应链表的最后端
*
*
*如果mtxtsk是带有阻塞的,而且必须是阻塞在该互斥锁上,函数才会调用mtx_chg_pri(mtxtsk, old_pri)的有效部分,
*在mtx_chg_pri函数中
*
*对于采用优先级置顶的,因为所有可能获取该mutex的任务优先级都不会高于置顶优先级,所有不需要做什么。
*
*而对于采用优先级继承的,一旦有阻塞在该mutex的任务改变了优先级,则需要判断并按照需要改变当前占有锁任务的优先级:
*(情况①:当前占有锁任务优先级 低于 已被改变优先级的任务的新优先级,则需要把占有锁任务优先级拉高。
* 情况②:当前占有锁任务优先级 高于或等于 已被改变优先级的任务的新优先级,
* 则需要先判断占有锁任务优先级是否跟被改变优先级的任务的旧优先级一样,
* 如果一样,说明占有锁任务必须重新在该mutex的阻塞任务链表找到最高优先级,并赋给占有锁任务优先级)
*
*********************************************************************************************************************
*/
//在这个函数下,mtxtsk只有两种情况,①就绪态 ②非mutex的阻塞
//所有该函数不会执行mtx_chg_pri的有效部分,也就是该函数在mtx_chg_pri下最多被执行一次。
change_internal_task_priority(mtxtsk, tcb->priority);
}
/*
*如果指定任务(被改变了优先级别的任务)的优先级 低于或等于 占有锁任务优先级,
*并且 占有锁任务优先级 等于 指定任务的旧优先级,
*则说明占有锁任务必须重新改变优先级为该mutex的最高优先级
*/
else if(mtxtsk->priority == oldpri) {//使用用户指定优先级来填充
/*
******************************************************************************************
*static RAW_VOID release_mutex(RAW_TASK_OBJ *tcb, RAW_MUTEX *relmtxcb)
*①relmtxcb为0,该函数相当于寻找整条阻塞任务链表中的最高优先级然后赋给指定任务tcb
*②relmtxcb不为0,把指定mutex从tcb的占有锁链表里释放,并把tcb的mtxlist中mutex的最高优先级
* 赋给tcb的当前优先级
******************************************************************************************
*/
release_mutex(mtxtsk, 0);
}
}
}
}
/*
*********************************************************************************************
*主要用在阻塞于mutex的任务超时了或异常或被删除了(改变了tcb优先级的不能使用该函数,而应该使用mtx_chg_pri)
*这时,需要更新该mutex的占有锁任务的优先级(把摘除了tcb后的mutex的最高优先级赋给占有锁任务)
**********************************************************************************************
*/
RAW_VOID mutex_state_change(RAW_TASK_OBJ *tcb)
{
RAW_MUTEX *mtxcb;
RAW_TASK_OBJ *mtxtsk;
mtxcb = (RAW_MUTEX *)(tcb->block_obj);
if (mtxcb->common_block_obj.object_type == RAW_MUTEX_OBJ_TYPE) {//必须是阻塞于mutex的
if (mtxcb->policy == RAW_MUTEX_INHERIT_POLICY) {//对优先级继承才需要处理
mtxtsk = mtxcb->mtxtsk;
/*the highest priority task blocked on this mutex may decrease priority so reset the mutex task priority*/
if(mtxtsk->priority == tcb->priority) {
release_mutex(mtxtsk, 0);
}
}
}
}
//释放指定任务当前占有的所有锁
RAW_VOID raw_task_free_mutex(RAW_TASK_OBJ *tcb)
{
RAW_MUTEX *mtxcb, *next_mtxcb;
RAW_TASK_OBJ *next_tcb;
LIST *block_list_head;
next_mtxcb = tcb->mtxlist;
//下面前两句用于遍历任务的占有mutex链表
while ((mtxcb = next_mtxcb) != 0) {
next_mtxcb = mtxcb->mtxlist;
//取出当前mutex的阻塞任务链表
block_list_head = &mtxcb->common_block_obj.block_list;
if (!is_list_empty(block_list_head)) {//对于有阻塞在该mutex的任务的处理
//取出阻塞任务链表的第一个任务块
next_tcb = list_entry(block_list_head->next, RAW_TASK_OBJ, task_list);
/*唤醒该任务,还不会调度*/
raw_wake_object(next_tcb);
/*改变mutex的拥有者*/
mtxcb->mtxtsk = next_tcb;
mtxcb->mtxlist = next_tcb->mtxlist;
next_tcb->mtxlist = mtxcb;
if (mtxcb->policy == RAW_MUTEX_CEILING_POLICY) {//如果是采用优先级置顶的话,还有修改任务优先级
if (next_tcb->priority > mtxcb->ceiling_prio) {
//next_tcb处于就绪态,所有只是做简单的更新
change_internal_task_priority(next_tcb, mtxcb->ceiling_prio);
}
}
}
else { //对于没有阻塞任务的mutex的处理
/*直接释放互斥锁 */
mtxcb->mtxtsk = 0;
}
}
}