介绍互斥信号量之前,首先给大家简单的说一下优先级反转,优先级反转是实时操作系统中常要面对的一个问题,而且它只出现在使用基于优先级的可剥夺型内核时。
优先级反转栗子
在这种情况下,任务H的优先级实际上降到了任务L的优先级水平。因为任务H要一直等待直到任务L释放其占用的那个共享资源。由于任务M剥夺了任务L的CPU使用权,使得任务H的情况更加恶化,这样就相当于任务M的优先级高于任务H,导致优先级反转。
有一种方法可以避免这一现象的出现:在UCOSIII中有一种特殊的信号量可以胜任这一任务,那就是互斥信号量。
互斥信号量
UCOSIII支持一种特殊的二进制信号量,其被称作互斥信号量。用它可以解决无界优先级反转的问题。
注意!只有任务才能使用互斥信号量(中断服务程序则不可以),UCOSIII允许用户嵌套使用互斥型信号量,一旦一个任务获得了一个互斥型信号量,则该任务最多可以对该互斥型信号量嵌套使用250次,当然该任务只有释放相同的次数才能真正释放这个互斥型信号量。
至于互斥信号量的思想:将拥有信号量的任务优先级暂时提升至目前正在等待该信号量的任务中的最高优先级!
互斥信号量API函数
与普通信号量一样,在本节中只讨论创建OSMutexCreate(),等待OSMutexPend(),发布OSMutexPost()。
如何创建互斥信号量
创建互斥信号量使用函数OSMutexCreate(),函数如下:
void OSMutexCreate (OS_MUTEX *p_mutex, //指向互斥型信号量控制块
CPU_CHAR *p_name, //互斥信号量的名字
OS_ERR *p_err)
{
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
p_mutex->Type = OS_OBJ_TYPE_MUTEX; /* Mark the data structure as a mutex */
p_mutex->NamePtr = p_name;
p_mutex->OwnerTCBPtr = (OS_TCB *)0;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0; /* Mutex is available */
p_mutex->TS = (CPU_TS )0;
p_mutex->OwnerOriginalPrio = OS_CFG_PRIO_MAX;
OS_PendListInit(&p_mutex->PendList); /* Initialize the waiting list */
OSMutexQty++;
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}
可以得知互斥信号量是一个二进制信号量。
请求互斥型信号量
当一个任务需要对资源进行独占式访问的时候就可以使用函数OSMutexPend(),如果该互斥信号量正在被其他的任务使用,那么UCOSIII就会将请求这个互斥信号量的任务放置在这个互斥信号量的等待表中。任务会一直等待,直到这个互斥信号量被释放掉,或者设定的超时时间到达为止。如果在设定的超时时间到达之前信号量被释放,UCOSIII将会恢复所有等待这个信号量的任务中优先级最高的任务。
void OSMutexPend (OS_MUTEX *p_mutex, //指向互斥信号量
OS_TICK timeout, //指定等待互斥信号量的超时时间(时钟节拍数)
OS_OPT opt, //用于选择是否使用阻塞模式
CPU_TS *p_ts, //指向一个时间戳
OS_ERR *p_err)
{
OS_PEND_DATA pend_data;
OS_TCB *p_tcb;
CPU_SR_ALLOC();
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0; /* Initialize the returned timestamp */
}
CPU_CRITICAL_ENTER();
if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0) { /* Resource available? */
p_mutex->OwnerTCBPtr = OSTCBCurPtr; /* Yes, caller may proceed */
p_mutex->OwnerOriginalPrio = OSTCBCurPtr->Prio;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1;
if (p_ts != (CPU_TS *)0) {
*p_ts = p_mutex->TS;
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return;
}
if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) { /* See if current task is already the owner of the mutex */
p_mutex->OwnerNestingCtr++;
if (p_ts != (CPU_TS *)0) {
*p_ts = p_mutex->TS;
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MUTEX_OWNER; /* Indicate that current task already owns the mutex */
return;
}
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /* Caller wants to block if not available? */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK; /* No */
return;
} else {
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't pend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
}
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(); /* Lock the scheduler/re-enable interrupts */
p_tcb = p_mutex->OwnerTCBPtr; /* Point to the TCB of the Mutex owner */
if (p_tcb->Prio > OSTCBCurPtr->Prio) { /* See if mutex owner has a lower priority than current */
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY:
OS_RdyListRemove(p_tcb); /* Remove from ready list at current priority */
p_tcb->Prio = OSTCBCurPtr->Prio; /* Raise owner's priority */
OS_PrioInsert(p_tcb->Prio);
OS_RdyListInsertHead(p_tcb); /* Insert in ready list at new priority */
break;
case OS_TASK_STATE_DLY:
case OS_TASK_STATE_DLY_SUSPENDED:
case OS_TASK_STATE_SUSPENDED:
p_tcb->Prio = OSTCBCurPtr->Prio; /* Only need to raise the owner's priority */
break;
case OS_TASK_STATE_PEND: /* Change the position of the task in the wait list */
case OS_TASK_STATE_PEND_TIMEOUT:
case OS_TASK_STATE_PEND_SUSPENDED:
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
OS_PendListChangePrio(p_tcb,
OSTCBCurPtr->Prio);
break;
default:
OS_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
return;
}
}
OS_Pend(&pend_data, /* Block task pending on Mutex */
(OS_PEND_OBJ *)((void *)p_mutex),
OS_TASK_PEND_ON_MUTEX,
timeout);
OS_CRITICAL_EXIT_NO_SCHED();
OSSched(); /* Find the next highest priority task ready to run */
CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK: /* We got the mutex */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_NONE;
break;
case OS_STATUS_PEND_ABORT: /* Indicate that we aborted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_PEND_ABORT;
break;
case OS_STATUS_PEND_TIMEOUT: /* Indicate that we didn't get mutex within timeout */
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0;
}
*p_err = OS_ERR_TIMEOUT;
break;
case OS_STATUS_PEND_DEL: /* Indicate that object pended on has been deleted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_OBJ_DEL;
break;
default:
*p_err = OS_ERR_STATUS_INVALID;
break;
}
CPU_CRITICAL_EXIT();
}
发送互斥信号量
我们可以通过调用函数OSMutexPost()来释放互斥型信号量,只有之前调用过函数OSMutexPend()获取互斥信号量,才需要调用OSMutexPost()函数来释放这个互斥信号量
void OSMutexPost (OS_MUTEX *p_mutex, //指向互斥信号量
OS_OPT opt, //用来指定是否进行任务调度操作
OS_ERR *p_err)
{
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
CPU_TS ts;
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
if (OSTCBCurPtr != p_mutex->OwnerTCBPtr) { /* Make sure the mutex owner is releasing the mutex */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MUTEX_NOT_OWNER;
return;
}
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
ts = OS_TS_GET(); /* Get timestamp */
p_mutex->TS = ts;
p_mutex->OwnerNestingCtr--; /* Decrement owner's nesting counter */
if (p_mutex->OwnerNestingCtr > (OS_NESTING_CTR)0) { /* Are we done with all nestings? */
OS_CRITICAL_EXIT(); /* No */
*p_err = OS_ERR_MUTEX_NESTING;
return;
}
p_pend_list = &p_mutex->PendList;
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) { /* Any task waiting on mutex? */
p_mutex->OwnerTCBPtr = (OS_TCB *)0; /* No */
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0;
OS_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return;
}
/* Yes */
if (OSTCBCurPtr->Prio != p_mutex->OwnerOriginalPrio) {
OS_RdyListRemove(OSTCBCurPtr);
OSTCBCurPtr->Prio = p_mutex->OwnerOriginalPrio; /* Lower owner's priority back to its original one */
OS_PrioInsert(OSTCBCurPtr->Prio);
OS_RdyListInsertTail(OSTCBCurPtr); /* Insert owner in ready list at new priority */
OSPrioCur = OSTCBCurPtr->Prio;
}
/* Get TCB from head of pend list */
p_tcb = p_pend_list->HeadPtr->TCBPtr;
p_mutex->OwnerTCBPtr = p_tcb; /* Give mutex to new owner */
p_mutex->OwnerOriginalPrio = p_tcb->Prio;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1;
/* Post to mutex */
OS_Post((OS_PEND_OBJ *)((void *)p_mutex),
(OS_TCB *)p_tcb,
(void *)0,
(OS_MSG_SIZE )0,
(CPU_TS )ts);
OS_CRITICAL_EXIT_NO_SCHED();
if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) {
OSSched(); /* Run the scheduler */
}
*p_err = OS_ERR_NONE;
}
何时可以用普通信号量代替互斥信号量
如果没有任务对共享资源的访问有截至时间,那么普通信号量就可以代替互斥信号量,反之则必须使用互斥信号量。因为前者会造成无界优先级反转,而后者却不会。