之前在和rawos作者的闲聊中,rawos作者认为实时操作系统中最大的特色就是互斥量的问题。一开始,我对这个看法其实是有保留意见的,直到我看到了修改优先级的相关代码,才开始对作者的看法有了很大的认同感。实话说,在嵌入式实时系统中修改优先级是一个很复杂的事情,为什么呢,因为这其中涉及到了互斥量的问题。我们大家都知道,在嵌入式系统中优先级反转是一个绕不去的砍。低优先级的任务因为获得了互斥量因而比高优先级的任务获得了更多的运行机会,这从根本上违背了实时系统设计的初衷。所以,人们为了解决除了这一问题,提出了优先级互斥量和优先级继承两种方法。
优先级互斥量的办法比较简单,ucos也是这么做的。我们在分配一个互斥量的时候,也给这个互斥量分配一定的优先级。任何获得该互斥量的任务都会把自己的优先级修改为互斥量的优先级,这样保证了获得该优先级的任务可以在最短的时间内完成,尽快释放资源。但是,这也会导致一个问题,那就是该互斥量需要占用一个优先级,而且比此优先级高的任务也没办法获得该互斥量。另外一种方法就是优先级继承的方法,简单一点说就是任何对阻塞线程优先级的修改,都会导致拥有此互斥量的任务进行优先级的修改。闲话不多说,我们看看rawos上面的代码是怎么描述的,
RAW_U16 raw_task_priority_change (RAW_TASK_OBJ *task_ptr, RAW_U8 new_priority, RAW_U8 *old_priority)
{
RAW_U8 ret_pri;
RAW_U16 error;
RAW_SR_ALLOC();
#if (RAW_TASK_FUNCTION_CHECK > 0)
if (task_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (old_priority == 0) {
return RAW_NULL_OBJECT;
}
#endif
/*Idle task is not allowed to change priority*/
if (task_ptr->priority >= IDLE_PRIORITY) {
return RAW_CHANGE_PRIORITY_NOT_ALLOWED;
}
/*Not allowed change to idle priority*/
if (new_priority == IDLE_PRIORITY) {
return RAW_CHANGE_PRIORITY_NOT_ALLOWED;
}
RAW_CRITICAL_ENTER();
#if (CONFIG_RAW_MUTEX > 0)
ret_pri = chg_pri_mutex(task_ptr, new_priority, &error);
if (error != RAW_SUCCESS) {
goto error_exit;
}
task_ptr->bpriority = new_priority;
new_priority = ret_pri;
#else
task_ptr->bpriority = new_priority;
#endif
*old_priority = task_ptr->priority;
error = change_interal_task_priority(task_ptr, new_priority);
if (error != RAW_SUCCESS) {
goto error_exit;
}
RAW_CRITICAL_EXIT();
do_possible_sche();
return RAW_SUCCESS;
error_exit:
RAW_CRITICAL_EXIT();
return error;
}
这个函数是系统本身提供的一个函数,内容不算少,但是重点可以放在两个子函数上面。一个部分是函数chg_pri_mutex,另外一个函数是change_interal_task_priority。前者判断当前优先级是否可以修改,后者判断如何对当前的任务进行修改,后面我们会看到会对这个问题有一个详细的说明。
RAW_U8 chg_pri_mutex(RAW_TASK_OBJ *tcb, RAW_U8 priority, RAW_U16 *error)
{
RAW_MUTEX *mtxcb;
RAW_U8 hi_pri, low_pri, pri;
RAW_TASK_OBJ *first_block_task;
LIST *block_list_head;
hi_pri = priority;
low_pri = 0;
mtxcb = (RAW_MUTEX *)(tcb->block_obj);
if (mtxcb) {
/*if it is blocked on mutex*/
if (mtxcb->common_block_obj.object_type == RAW_MUTEX_OBJ_TYPE) {
if (mtxcb->policy == RAW_MUTEX_CEILING_POLICY) {
pri = mtxcb->ceiling_prio;
if (pri > low_pri) {
low_pri = pri;
}
}
}
}
/* Locked Mutex */
pri = hi_pri;
for (mtxcb = tcb->mtxlist; mtxcb != 0; mtxcb = mtxcb->mtxlist) {
switch (mtxcb->policy) {
case RAW_MUTEX_CEILING_POLICY:
pri = mtxcb->ceiling_prio;
if ( pri > low_pri ) {
low_pri = pri;
}
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:
/* nothing to do */
break;
}
if ( pri < hi_pri ) {
hi_pri = pri;
}
}
if (priority < low_pri) {
*error = RAW_EXCEED_CEILING_PRIORITY;
return RAW_EXCEED_CEILING_PRIORITY;
}
*error = RAW_SUCCESS;
return hi_pri;
}
上面的代码还是比较复杂的,我们看到其实任务的优先级不是可以随便修改的,因为当前任务可能已经占有了许多互斥量资源,而这些互斥量资源其实是有约束条件的。如果占有的互斥量类型是那种带优先级的互斥量,那么必须找出的那个最低优先级的互斥量,至少修改的任务优先级不能比它高。剩下的工作就是在继承优先级的体制下寻找到最高的优先级,仅此而已。
RAW_U16 change_interal_task_priority(RAW_TASK_OBJ *task_ptr, RAW_U8 new_priority)
{
RAW_U8 old_pri;
switch (task_ptr->task_state) {
case RAW_RDY:
remove_ready_list(&raw_ready_queue, task_ptr);
task_ptr->priority = new_priority;
if (task_ptr == raw_task_active) {
add_ready_list_head(&raw_ready_queue, task_ptr);
}
else {
add_ready_list_end(&raw_ready_queue, task_ptr);
}
break;
case RAW_DLY: /* Nothing to do except change the priority in the OS_TCB */
case RAW_SUSPENDED:
case RAW_DLY_SUSPENDED:
task_ptr->priority = new_priority; /* Set new task priority*/
break;
case RAW_PEND:
case RAW_PEND_TIMEOUT:
case RAW_PEND_SUSPENDED:
case RAW_PEND_TIMEOUT_SUSPENDED:
old_pri = task_ptr->priority;
task_ptr->priority = new_priority;
change_pend_list_priority(task_ptr);
#if (CONFIG_RAW_MUTEX > 0)
mtx_chg_pri(task_ptr, old_pri);
#endif
break;
default:
#if (CONFIG_RAW_ASSERT > 0)
RAW_ASSERT(0);
#endif
return RAW_STATE_UNKNOWN;
}
return RAW_SUCCESS;
}
前面,我们说到了优先级的修改函数,而change_interal_task_priority就是完成这一功能的函数。当然,我们需要针对目前任务的状态对任务的优先级进行修改,如果任务此时正在运行或者延迟运行,那么还好办,关键是如果此时任务已经阻塞了,那么考虑的情况就多了。
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) {
/* Since the highest priority of the lock wait task
became higher, raise the lock get task priority
higher */
change_interal_task_priority(mtxtsk, tcb->priority);
}
/*the highest priority task blocked on this mutex may decrease priority so reset the mutex task priority*/
else if(mtxtsk->priority == oldpri) {
release_mutex(mtxtsk, 0);
}
}
}
}
mtx_chg_pri函数此时考虑的不光是它自己优先级的问题,它还需要考虑此时占有互斥量的这个任务优先级该怎么修改。我们进一步看看release_mutex下面做了哪些工作。
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 from list */
*prev = mtxcb->mtxlist;
continue;
}
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;
}
if (newpri > pri) {
newpri = pri;
}
prev = &mtxcb->mtxlist;
}
if ( newpri != tcb->priority ) {
/* Change priority of lock get task */
change_interal_task_priority(tcb, newpri);
}
}
这个函数的工作就是修改那个获得互斥量的任务的优先级的,在寻找到最高优先级之后,那么又需要调用change_internall_task_priority函数了。有过递归函数编写经验的朋友就知道了,这其实就是一个典型的递归函数。change_internall_task_priority函数调用到release_mutex,然而release_mutex又调用到change_internall_task_priority,感觉上没完没了了,其实不是这样。递归函数都是需要出口的,这个函数的出口就是change_internall_task_priority,一切等到获得的那个任务不再是阻塞任务为止,整个修改的过程就结束了。当然,任务优先级恢复的工作也是非常麻烦的,不管是带优先级的互斥量还是优先级继承机制的互斥量,额外的优先级修改和计算都是必须的,不知道我讲清楚了没有。rawos在互斥量的最大进步就是进一步说明了拥有多互斥量的任务该如何修改优先级,当然了,函数迭代的过程多了堆栈使用的空间也多了,有没有溢出的危险,这是我们需要考虑的另外一个重要因素。