分析的内核版本截止到2014-04-15,基于1.05正式版,blogs会及时跟进最新版本的内核开发进度,若源码注释出现”???”字样,则是未深究理解部分。
Raw-OS官方网站:http://www.raw-os.org/
Raw-OS托管地址:https://github.com/jorya/raw-os/
今天来说说Raw-OS的互斥锁Mutex模块,这个Mutex用起来很容易,用户api也只有5个函数,但是内核关于这个模块的代码有点复杂,看明白要花点时间,所以要做好心理准备啊~亲~
一、优先级反转问题
先来看看所谓的“优先级反转”问题
任务访问某个硬件,那么硬件对于系统来说是一个共享资源,用互斥量来保护,那么
1.task z访问硬件,获取互斥锁,然后执行硬件相关操作(读写)
2.然后task x优先级比task z高,那么抢占task z
3.Task x也要访问硬件进行读写,然后发现硬件被占用,那么task就被阻塞
4.系统调度,task y执行,等待task y执行完毕
5.系统调度,task z执行,执行完硬件操作后,释放互斥锁
6.最后才轮到task x执行硬件操作
这里task x是最高优先级,反而被低优先级任务抢占运行,这就是所谓的“优先级反转”问题的由来。
二、“优先级反转”问题的一般解决方法
按照一般RTOS理论,解决“优先级反转”的问题有两种一般方法:
优先级继承协议、优先级天花板协议
下面简单描述下两种协议的原理:
优先级继承协议:
按照“优先级反转”问题的延续,关键在于步骤4的优先级提升
1.task z访问硬件,获取互斥锁,然后执行硬件相关操作(读写)
2.然后task x优先级比task z高,那么抢占task z
3.Task x也要访问硬件进行读写,然后发现硬件被占用,那么task就被阻塞
4.将task z的优先级提升至task x的优先级,然后执行系统调度
5.task z提升优先级后,那么task y优先级就不足够高来抢占task z,所以task z继续执行
6.task z执行完硬件操作后,释放信号量,还原原来的基优先级
7.系统调度,执行task x进行硬件读写
这里“优先级反转”的问题可以通过继承搞优先级任务就得到解决
优先级天花板协议
优先级天花板协议就要比优先级继承协议简单得多
1.给互斥锁定义一个优先级,这个优先级比所有要获取互斥锁的任务的优先级都要高
2.task z获取互斥锁时,直接将优先级提升到预定义优先级
3.因为预定义优先级比所有要获取互斥锁的任务优先级都要高,所以获取互斥锁后的task z无法被更高的优先级任务抢占,就可以顺利执行硬件操作
4.执行完硬件操作后,task z还原原来的基优先级,执行系统调度
5.唤醒阻塞的task x
6.task x执行硬件操作
三、获取互斥锁操作
1.互斥锁占用任务
内核中的互斥锁被占用时,用互斥锁控制块的mtxtsk变量表示被哪个任务占用
mutex_ptr->mtxtsk;
2.互斥锁链表
内核的互斥锁链表,当然这个名词是我自己想出来的哈,看一个情况
Task x在某一时刻内顺利的占用3个互斥锁,那么,在内核中,同一个任务占用多个互斥锁时,被同一个任务占用的互斥锁会形成一个互斥锁链表
互斥锁间通过互斥锁控制块的mtxlist指针组成单向链表,第一个被任务占用的锁的互斥锁链表指针指向0;然后task通过mtxlist指针指向最迟获取的互斥锁的链表指针。
互斥锁链表在例子中再讲解内核是如何使用的
3.获取互斥锁情况举例
因为使用优先级天花板协议的互斥锁相关操作都非常之简单,所以仅仅举例使用优先级继承协议的互斥锁的情况
这是一个比较简单的情况:获取属于运行任务的互斥锁
1.task x占有互斥锁
2.task x成功占有互斥锁,返回
3.task y抢占task x获取互斥锁,互斥锁被task x占有
4.task x提升优先级
5.task y阻塞在互斥锁上
那么看一个比较复杂的例子:获取属于阻塞任务的互斥锁
重点关注第5步骤之后的操作,特别是第9步
1.task x获取互斥锁mutex1
2.task x获取互斥锁mutex1成功,返回
3.task y抢占task x,获取互斥锁mutex2
4.task y获取互斥锁mutex2成功,返回
5.task y执行过程,获取互斥锁mutex1
6.由于mutex已经被task x占用,又因为task y优先级高于task x,所以task x提升到task y的优先级
7.task y因为不能获取互斥锁mutex1,阻塞在mutex1上,等待mutex1被释放,然后系统调度后返回task x执行
8.非常之不幸的是,又来一个更高优先级的任务task z抢占task x,获取互斥锁mutex2
9.由于mutex2已经被task y占有,所以,task y必须提升到task z的优先级,好了,这里重点来了,因为task y是阻塞在mutex1上面的,当task y提升了优先级,那么task x也需要提升优先级,这里发生的就是所谓的“互斥锁嵌套”的情况
10.task z由于不能获取互斥锁mutex2,所以阻塞在mutex2上,返回task x执行
这里需要注意的几点是:
1.互斥锁优先级是所有要获取互斥锁任务的优先级的最高那个
2.使用优先级继承协议的互斥锁被任务占有时,占有互斥锁过程的任务优先级等于可能的互斥锁任务优先级
3.互斥锁嵌套时,会发生连续提升任务优先级的情况
四、创建互斥锁
创建互斥锁就是创建一个互斥锁控制块,操作类似创建任务控制块过程类似
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;
}
/* 检查Raw-OS互斥量支持的协议:优先级继承、优先级天花板、无协议,不是这三种协议时返回 */
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;
/* 这个是当同一个任务占有多个互斥锁时,被同一个任务占用的互斥锁会形成互斥锁链表 */
mutex_ptr->mtxlist = 0;
/* 互斥锁的协议:优先级继承、优先级天花板、无协议之一 */
mutex_ptr->policy = policy;
#if (CONFIG_RAW_TASK_0 > 0)
if (policy == RAW_MUTEX_CEILING_POLICY) {
if (ceiling_prio == 0) {
return RAW_CEILING_PRIORITY_NOT_ALLOWED;
}
}
#endif
/* 设置优先级天花板的值 */
mutex_ptr->ceiling_prio = ceiling_prio;
/* 互斥锁类型:互斥锁对象 */
mutex_ptr->common_block_obj.object_type = RAW_MUTEX_OBJ_TYPE;
/* trace调试系统??? */
TRACE_MUTEX_CREATE(raw_task_active, mutex_ptr, name_ptr, policy, ceiling_prio);
return RAW_SUCCESS;
}
五、获取互斥锁内核代码分析
因为内核获取互斥锁的代码综合了各种各样的获取互斥锁的情况,光看代码其实是很难理解的,这里建议对上上面列举的两种获取情况来跟踪代码,互斥锁的内核代码要花点时间去看啊~各位亲~
RAW_U16 raw_mutex_get(RAW_MUTEX *mutex_ptr, RAW_TICK_TYPE wait_option)
{
RAW_U16 error_status;
RAW_TASK_OBJ *mtxtsk;
/* 定义CPU状态机字变量 */
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
/* 保存CPU状态字 */
RAW_CRITICAL_ENTER();
/* 检查操作传入的互斥锁控制块,控制块内标识不是互斥锁对象类型时,返回 */
if (mutex_ptr->common_block_obj.object_type != RAW_MUTEX_OBJ_TYPE) {
RAW_CRITICAL_EXIT();
return RAW_ERROR_OBJECT_TYPE;
}
/* 如果当前任务已经获取互斥锁后,再获取同一个互斥锁,那么会发生死锁,标记后返回 */
if (raw_task_active == mutex_ptr->mtxtsk) {
mutex_ptr->owner_nested++;
RAW_CRITICAL_EXIT();
return RAW_MUTEX_OWNER_NESTED;
}
/*
* 这里是对于任务访问使用优先级天花板协议的互斥锁的边界判断
*
* 这里注意Raw-OS的优先级数值越小优先级越高,那么根据优先级天花板协议,任何访问互斥锁
* 的任务的优先级必须小于或等于互斥锁的天花板优先级
*/
if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
/* 当访问互斥锁的任务基优先级大于互斥锁的优先级时,错误返回 */
if (raw_task_active->bpriority < mutex_ptr->ceiling_prio) {
RAW_CRITICAL_EXIT();
/* trace调试系统??? */
TRACE_MUTEX_EX_CE_PRI(raw_task_active, mutex_ptr, wait_option);
return RAW_EXCEED_CEILING_PRIORITY;
}
}
/* 获取当前使用互斥锁的任务控制块 */
mtxtsk = mutex_ptr->mtxtsk;
/* 如果互斥锁有没被任务占用,任务将获取互斥锁 */
if (mtxtsk == 0) {
/* 将当前使用互斥锁的任务记录到互斥锁控制块的任务指针中 */
mutex_ptr->mtxtsk = raw_task_active;
/*
* 将当前互斥锁插入到当前任务控制块的互斥锁链表中
* 当某一个任务占用多个互斥锁时,互斥锁组成互斥锁链表
*
* 0.raw_task_active->mtxlist未空,表示任务不占有任何互斥锁
* 1.当raw_task_active获取一个互斥锁时,mutex_ptr->mtxlist指向0,raw_task_active->mtxlist指向当前锁
* 2.当任务此时再获取一个互斥锁,那么,新锁mtxlist指向任务mtxlist,又因为任务的mtxlist指向前一个锁,那么新锁就指向前一个锁
* 3.任务的mtxlist更新指向新获取的锁(总是指向最新占用的互斥锁)
*
* note:互斥锁的mtxlist是这样形式单项链表的
*/
mutex_ptr->mtxlist = raw_task_active->mtxlist;
raw_task_active->mtxlist = mutex_ptr;
/* 互斥锁的使用次数+1 */
mutex_ptr->owner_nested = 1u;
/* 当互斥锁使用优先级天花板协议时,需要在临界区提升获取互斥锁的任务优先级=优先级天花板 */
if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
if (raw_task_active->priority > mutex_ptr->ceiling_prio) {
/*
* 临时提升任务互优先级=互斥锁优先级天花板互斥锁
* 提升优先级的当前运行任务,所以改变优先级时仅仅更改优先级后,重新添加到就绪链表
*/
change_internal_task_priority(raw_task_active, mutex_ptr->ceiling_prio);
}
}
RAW_CRITICAL_EXIT();
/* trace调试系统 */
TRACE_MUTEX_GET(raw_task_active, mutex_ptr, wait_option);
/* 成功占用互斥锁,返回 */
return RAW_SUCCESS;
}
/* 如果当不能获取互斥锁时,并且用户不设置等待,那么直接返回 */
if (wait_option == RAW_NO_WAIT) {
RAW_CRITICAL_EXIT();
return RAW_NO_PEND_WAIT;
}
/* 当系统系统上锁时,任务不能被阻塞,直接返回 */
SYSTEM_LOCK_PROCESS();
/*
* 当前任务无法获取互斥锁时,并且当互斥锁使用的是优先级继承协议,
* 检查当前获取互斥锁任务优先级,如果当前任务优先级高于占用互斥锁任务的优先级,
* 那么之前占用互斥锁的任务,要继承当前无法获取互斥锁任务的优先级(即需要提升占用互斥锁任务的优先级)
*/
if (mutex_ptr->policy == RAW_MUTEX_INHERIT_POLICY) {
if (raw_task_active->priority < mtxtsk->priority) {
/* trace调试系统??? */
TRACE_TASK_PRI_INV(raw_task_active, mtxtsk);
/*
* 将此时占用互斥锁的任务继承当前无法获取互斥锁任务的优先级
* 改变优先级后重新加入就绪链表适当位置,所谓适当位置,就是按优先级大小排序的位置
*/
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();
/* trace调试系统??? */
TRACE_MUTEX_GET_BLOCK(raw_task_active, mutex_ptr, wait_option);
/* 执行任务调度 */
raw_sched();
/*
* 执行到这里,是因为某些条件唤醒新的最高优先级任务,根据之前阻塞的情况,
* 判断之前阻塞的情况,置位新任务TCB阻塞标志,目的是得知是什么操作唤醒任务
*/
error_status = block_state_post_process(raw_task_active, 0);
return error_status;
}
六、释放互斥锁内核代码分析
那么,当任务执行完临界区的时候,任务就要释放获取的互斥锁,并且,根据需要还原成任务原本的优先级,分两种情况:
1.如果task仅仅占有一个互斥锁时,那么释放互斥锁后,就还原task原来的基优先级
2.如果task占有两个以上互斥锁时,那么释放其中一个互斥锁后,就会检查其余互斥锁的互斥锁优先级,然后改变成其余互斥锁优先级中最高优先级的那一个
那么这里占有两个以上互斥锁时,互斥锁链表就发挥作用了,检查所有互斥锁优先级时,就是历遍整个互斥锁链表,检查每一个互斥锁链表项的互斥锁阻塞链表来得到互斥锁优先级,这里我可能讲得不明不白的,看下面我自己理解的图吧~
之前说过
互斥锁优先级:定义为所有是所有要获取互斥锁任务的优先级的最高那个
互斥锁项优先级:实际上等于互斥锁项的阻塞链表的阻塞任务的最高优先级(阻塞链表第1个任务,因为阻塞链表是按优先级大小排序的)
互斥锁链表最高优先级:定义为整条互斥锁链表中所有互斥锁项的互斥锁优先级最大值
那么当任务占用2个或2个以上互斥锁时,互斥锁就会形成互斥锁链表,那么在任务释放互斥锁后,任务必须更改任务的运行优先级至互斥锁链表最高优先级
这些是Raw-OS的互斥锁模块比较难理解的部分,之前提过的互斥锁链表并结合源码去理解,稍微话点时间去看
RAW_U16 raw_mutex_put(RAW_MUTEX *mutex_ptr)
{
LIST *block_list_head;
RAW_TASK_OBJ *tcb;
/* 定义CPU状态机字变量 */
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
RAW_CRITICAL_ENTER();
/* 检查操作传入的互斥锁控制块,控制块内标识不是互斥锁对象类型时,返回 */
if (mutex_ptr->common_block_obj.object_type != RAW_MUTEX_OBJ_TYPE) {
RAW_CRITICAL_EXIT();
return RAW_ERROR_OBJECT_TYPE;
}
/* 检查占用互斥锁的任务是否是当前任务,系统要求必须是获取互斥锁的任务自行释放互斥锁 */
if (raw_task_active != mutex_ptr->mtxtsk) {
RAW_CRITICAL_EXIT();
return RAW_MUTEX_NOT_RELEASE_BY_OCCYPY;
}
/* 释放互斥锁后,互斥锁使用次数-- */
mutex_ptr->owner_nested--;
/* 当此时互斥锁使用次数不为0,那么只有一种情况,就是当时已经获取互斥锁的任务又一次获取互斥锁,死锁状态 */
if (mutex_ptr->owner_nested) {
RAW_CRITICAL_EXIT();
return RAW_MUTEX_OWNER_NESTED;
}
/*
* 历遍传入互斥锁的互斥锁链表,并且将传入的互斥锁从互斥链表中删除,
* 并且设置释放互斥锁的任务的优先级,或者是基优先级,或者是改变成互斥锁链表的最高优先级
*
* 1.当任务仅占用一个互斥锁时,释放后还原成基优先级
* 2.当任务占用2个或2个以上互斥锁时,改变成互斥锁链表的最高优先级
*/
release_mutex(raw_task_active, mutex_ptr);
/* 获取互斥锁阻塞链表头 */
block_list_head = &mutex_ptr->common_block_obj.block_list;
/* 如果互斥锁阻塞链表为空,那么没有其他任务阻塞在这个互斥锁上 */
if (is_list_empty(block_list_head)) {
/* 设置互斥锁被任务占有的标志为0,返回 */
mutex_ptr->mtxtsk = 0;
RAW_CRITICAL_EXIT();
TRACE_MUTEX_RELEASE_SUCCESS(raw_task_active, mutex_ptr);
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;
mutex_ptr->owner_nested = 1u;
/*
* 这里为什么释放互斥锁后,唤醒互斥锁阻塞链表中的最高优先级任务后,要还改变任务的运行优先级 ???
* 因为当任务阻塞在互斥锁上后,只会受到优先级继承协议的影响,而被唤醒后,任务优先级也只会是互斥锁优先级
* 而如果任务是阻塞在使用优先级天花板协议的互斥锁上被唤醒后,就要提升至互斥锁的优先级天花板
*/
/* 如果互斥锁是优先级天花板协议 */
if (mutex_ptr->policy == RAW_MUTEX_CEILING_POLICY) {
/* 当任务的运行优先级低于互斥锁的天花板优先级, */
if (tcb->priority > mutex_ptr->ceiling_prio) {
/* 将任务运行优先级提升到互斥锁的优先级天花板,重新加入到就绪队列 */
change_internal_task_priority(tcb, mutex_ptr->ceiling_prio);
}
}
TRACE_MUTEX_WAKE_TASK(raw_task_active, tcb);
RAW_CRITICAL_EXIT();
/* 执行系统调度 */
raw_sched();
return RAW_SUCCESS;
}
七、互斥锁中的任务优先级改变
经过上面的分析就可以知道获取互斥锁,释放互斥锁等等的操作都随时涉及到任务运行优先级的改变,这也是之前blog中任务篇没涉及的操作,首先要理解好上面互斥锁的操作再去看如何根据各种各样的情况更改任务的运行优先级,这个在内核中也是比较难理解的
总结互斥锁的几点,再结合看代码再理解,确实mutex的代码是比较复杂的啊~亲~
1.当一个任务占有多个互斥锁时,同一个任务的所有互斥锁组成互斥锁链表
2.互斥锁优先级等于被阻塞在互斥锁上阻塞链表所有任务的最高优先级
3.互斥锁链表最高优先级等于所有互斥锁链表项的互斥锁优先级的最大值
4.取得互斥锁的任务的运行优先级等于互斥锁链表的最高优先级
5.注意任务获取运行任务占有的互斥锁时,运行任务的运行优先级如何改变
6.注意任务获取阻塞任务占有的互斥锁时,阻塞任务的运行优先级如何改变