详解UCOS中的互斥信号量

时间:2022-09-29 20:40:02
详解UCOS中的互斥信号量

二值信号量主要用于进行共享资源的独占式访问,比如我们用一个变量来标志一个资源是否可用,当这个变量为1的时候表示资源可用,当这个资源为0的时候表示资源不可用,但是二值信号量容易产生优先级反转,影响系统的实时性。互斥信号量一般用于降解优先级反转,优先级反转就是高优先级的任务的优先级被拉低了。具体如下:
我们有三个任务Task1,Task2,Task3,三个任务的优先级依次降低。
void Task1()
{
    while(1)
    {
        OSSemPend();   //获取信号量
        ......
        OSSemPost();   //释放信号量
    }
}
void Task2()
{
    while(1)
    {
        //注意任务2不需要信号量
    }
}
void Task3()
{
    while(1)
    {
       OSSemPend();   //获取信号量


        OSSemPost();   //释放信号量
    }
}
void main()
{
    OSInit();
    CreateTask(Task1);    //1  最高
    CreateTask(Task2);    //2
    CreateTask(Task3);
    OSStart();

}

详解UCOS中的互斥信号量

如上图所示:在任务2获得信号量的时候,任务1恢复就绪态之后因为没有获得信号量而挂起,所以任务3继续执行,直到任务3执行完毕之后,任务1才开始执行。虽然任务1的优先级最高,但是因为信号量的原因而是任务1的优先级降到任务3的优先级水平。而且任务2加重了优先级反转的程度。

当我们使用了互斥信号量之后,就可以在某种程度上缓解优先级反转的问题了。当高优先级的任务请求互斥信号量时,如果低优先级的任务占有该信号量,则先提升低优先级任务的优先级,使之尽快执行完以释放互斥信号量,这样高优先级的任务也能尽快执行,在某种程度上缓解了优先级反转问题。

使用了互斥信号量之后的运行图如下:

详解UCOS中的互斥信号量

如图所示,在任务3执行的过程中,任务1请求互斥信号量,提升任务3的优先级到最高,使任务3尽快执行完,任务3执行完后释放信号量,任务1开始执行。

UCOS_II中互斥信号量相关的函数主要在os_mutex.c中,核心代码如下:

void  OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
INT8U pip; //继承优先级
INT8U mprio; //现在占有该信号量的优先级
BOOLEAN rdy; //当前任务是否就绪标志
OS_TCB *ptcb;
OS_EVENT *pevent2;
INT8U y;

OS_ENTER_CRITICAL();
//该信号量的继承优先级
pip = (INT8U)(pevent->OSEventCnt >> 8);

//该互斥信号量还没有被占用
if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE)
{
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;
//直接以当前优先级占用该信号量
pevent->OSEventCnt |= OSTCBCur->OSTCBPrio;
pevent->OSEventPtr = (void *)OSTCBCur;
if (OSTCBCur->OSTCBPrio <= pip)
{
OS_EXIT_CRITICAL();
//继承优先级比当前任务的优先级还小
*err = OS_ERR_PIP_LOWER;
}
else
{
OS_EXIT_CRITICAL();
//继承优先级至少比当前任务的优先级高
*err = OS_NO_ERR;
}
return;
}

//占用该信号量的任务的优先级
mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);

//现在占有该信号量的任务的TCB
ptcb = (OS_TCB *)(pevent->OSEventPtr);

//占有该信号量的任务的优先级比继承优先级小时才会进行反转
if (ptcb->OSTCBPrio > pip)
{
//占有该信号量的任务的优先级小于当前任务的优先级进行反转
if (mprio > OSTCBCur->OSTCBPrio)
{
//从就绪表中移除现在占有该信号量的任务
y = ptcb->OSTCBY;
if ((OSRdyTbl[y] & ptcb->OSTCBBitX) != 0)
{
OSRdyTbl[y] &= ~ptcb->OSTCBBitX;
if (OSRdyTbl[y] == 0)
{
OSRdyGrp &= ~ptcb->OSTCBBitY;
}
//占有该信号量的任务已经处于就绪态
rdy = OS_TRUE;
}
else //占有该信号量的任务现在在系统的等待列表中
{
//占有该信号量的任务的ECB
pevent2 = ptcb->OSTCBEventPtr;
if (pevent2 != (OS_EVENT *)0)
{
//将占有该信号量的任务从该信号量的等待列表中移除
if ((pevent2->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {
pevent2->OSEventGrp &= ~ptcb->OSTCBBitY;
}
}
//占有改信号量的任务现在还没有就绪
rdy = OS_FALSE;
}

//更改占有该信号量的任务的优先级
ptcb->OSTCBPrio = pip;
ptcb->OSTCBY = (INT8U)( ptcb->OSTCBPrio >> 3);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x07);
ptcb->OSTCBBitY = (INT8U)(1 << ptcb->OSTCBY);
ptcb->OSTCBBitX = (INT8U)(1 << ptcb->OSTCBX);
if (rdy == OS_TRUE)
{
//如果之前占有该信号量的任务已近处于就绪态了
//就将提升之后的优先级加入到就绪表中
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
else
{
pevent2 = ptcb->OSTCBEventPtr;
if (pevent2 != (OS_EVENT *)0)
{
//占有该信号量的任务处于等待列表中就把提升
//优先级之后的任务加入到该信号量的等待列表中
pevent2->OSEventGrp |= ptcb->OSTCBBitY;
pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
//彻底占用该优先级
OSTCBPrioTbl[pip] = ptcb;
}
}
OSTCBCur->OSTCBStat |= OS_STAT_MUTEX;
OSTCBCur->OSTCBPendTO = OS_FALSE;
OSTCBCur->OSTCBDly = timeout;
OS_EventTaskWait(pevent);
OS_EXIT_CRITICAL();
//重新调度
OS_Sched();
OS_ENTER_CRITICAL();
//该任务等待超时
if (OSTCBCur->OSTCBPendTO == OS_TRUE)
{
OS_EventTO(pevent);
OS_EXIT_CRITICAL();
*err = OS_TIMEOUT;
return;
}
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}





相关文章