当我们使用独占资源的时候,该资源只能提供给一个任务访问,
例如说串口,USB、IO、定时器等,这个时候我们就要用到互斥信号量了。
互斥信号量时信号量的一个特例,信号量的计数值只能为0和1。
但是互斥信号量和信号量之间还是有区别的。互斥信号量需要解决优先级反转的问题。
由于UCOSII是优先级实时系统,所以必须要保证优先级高的任务能够尽快执行。
所谓优先级反转就是指低优先级占有着资源,而使高优先级由于低优先级任务获取资源而没有得到资源而阻塞迟迟得不到运行。
例如任务A的优先级为3,任务B的优先级为30。A任务在申请互斥信号量的时候,由于互斥信号量被任务B占用而阻塞。
同时若有优先级任务7和8任务就绪,那么系统就会先运行优先级7和8的任务,若是又有优先级比任务为B优先级高的任务就绪,系统就会去执行这些任务。
而优先级为30的B任务迟迟得不到执行,就没有办法释放信号量,导致任务A也迟迟得不到互斥信号量而阻塞。这样就发生了优先级反转。
解决优先级反转的方法就是暂时提高占有互斥信号量任务的优先级(至少不低于申请资源的所有任务的最高优先级要高)。
比如刚才那个例子中,当任务B占有互斥信号的时候,将优先级提升为2,在资源使用完之后,释放互斥信号量,然后又将优先级退回到原来的优先级。这样就可以解决优先级反转的问题。
这种方法称为优先级继承机制。
解决优先级反转是互斥信号量和信号量的最显著特点。
信号量的管理主要由下面7个函数实现,定义都在os_mutex.c中定义。
1:互斥信号量的创建OSMutexCreate
互斥信号量的创建除了信号量计数赋值和类型名称跟普通的信号量之外和函数的参数有区别,其他基本上一样。
函数原型如下:OS_EVENT *OSMutexCreate (INT8U prio,INT8U *perr);
其参数prio是取得信号量的任务需要提升到的优先级。
这里就只分析信号量计数的那一部分了。
pevent->OSEventCnt = (INT16U)((INT16U)prio << 8u) | OS_MUTEX_AVAILABLE;
由于优先级的值prio 最大为255(V2.90版本),最多用8bit表示。
将优先级向左移八位,也就是将优先级数值存在信号量计数值OSEventCnt的高8位。
然后将宏OS_MUTEX_AVAILABLE的值存储在低8位,其值为0x00FF。若低八位全为1,表示该互斥信号量可以用,否则就是不可用。
2:请求互斥信号量OSMutexPend
由于涉及到优先级继承的问题,所以互斥信号量和普通信号量在这方面还是有不少差别的。
函数的原型void OSMutexPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)
具体的功能实现源码如下:
OS_ENTER_CRITICAL();
pip = (INT8U)(pevent->OSEventCnt >> 8u); //先得到高八位的数值,即继承的优先级。
if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE) { //提取低8位,看是否全为1,来判断互斥信号量是否可用。
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8; //修改高8位的数据,这个时候高8位数据不变,还是表示继承优先级,低8位清零。
pevent->OSEventCnt |= OSTCBCur->OSTCBPrio; //将申请任务的优先级保存在低8位之中。低八位不可能全部都为零,所以依旧可以表示信号量是否被占用。
pevent->OSEventPtr = (void *)OSTCBCur; //OSEventPtr指针指向任务控制块
if (OSTCBCur->OSTCBPrio <= pip) {
//申请任务优先级的数值小于继承优先级的数值。PIP的优先级必数值必须必所有申请任务的数值优先级要小,否则谈不上优先级继承。等于更不行。
OS_EXIT_CRITICAL();
*perr = OS_ERR_PIP_LOWER;
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
}
return; //申请完成,返回
}
申请互斥信号量是不可用得时候,也就是被占用的时候
mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); //取得互斥信号量任务的优先级(因为被占用,所以低8位为优先级)
ptcb = (OS_TCB *)(pevent->OSEventPtr); //获ECB取指向占用取得互斥信号量任务控制块的指针
if (ptcb->OSTCBPrio > pip)
{ //如果拥有信号量任务的优先级数值大于继承优先级的数值
if (mprio > OSTCBCur->OSTCBPrio)
{ //取得信号量任务的优先级数值大于现在申请的数值的时候(注意前面蓝色代码的那一段,当信号量没有被占用的时候,不管什么情况,继承优先级的大小,都会完成申请,所以这种情况是由可能出现的),若出现这种情况,需要对改继承优先级。若是小于现在的申请任务数值,所以就不需要进行进行优先级的提升,因为不会出现优先级反转的情况。
y = ptcb->OSTCBY;
if ((OSRdyTbl[y] & ptcb->OSTCBBitX) != 0u) { //看信号量的取得任务是否就绪
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; //如果是就绪,取消任务状态
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
}
rdy = OS_TRUE;
} else { //若是没有就绪
pevent2 = ptcb->OSTCBEventPtr; //指向拥有指向取得信号量的任务控制块指向ECB指针
if (pevent2 != (OS_EVENT *)0) { //若是指针不为空
y = ptcb->OSTCBY;
pevent2->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;//清除对应的等待表和等待组
if (pevent2->OSEventTbl[y] == 0u) {
pevent2->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
}
pevent2->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
}
rdy = OS_FALSE;
}
ptcb->OSTCBPrio = pip; //改变信号量取得任务的优先级
ptcb->OSTCBY = (INT8U)( ptcb->OSTCBPrio >> 3u); //根据新取得的优先级,更新任务控制块的相关数据。
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x07u);
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
if (rdy == OS_TRUE) { //如果取得信号量的任务为就绪状态的时候
OSRdyGrp |= ptcb->OSTCBBitY; //更新就绪数组和就绪表(任务的新优先级)
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
} else {
} else {
pevent2 = ptcb->OSTCBEventPtr; //如果不是就绪
if (pevent2 != (OS_EVENT *)0) { //将其更新ECB的等待数组和等待表
pevent2->OSEventGrp |= ptcb->OSTCBBitY;
pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
OSTCBPrioTbl[pip] = ptcb; //优先级数组指针指向取得的任务控制块
}
pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
OSTCBPrioTbl[pip] = ptcb; //优先级数组指针指向取得的任务控制块
}
}
OSTCBCur->OSTCBStat |= OS_STAT_MUTEX; //将当前任务的状态设为等待信号量
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; //等待时间
OS_EventTaskWait(pevent); //设置等待事件阻塞,取消申请任务的的就绪状态,直到时间超时或者是事件发生
OS_EXIT_CRITICAL();
OS_Sched(); //执行调度,运行最高优先级的就绪任务
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) {
case OS_STAT_PEND_OK: //事件发生
*perr = OS_ERR_NONE;
break;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; //异常终止
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; //时间超时
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; //将运行状态设置为等待
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; //等待事件状态设置无等待
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; //清除事件控制块指针
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OS_EXIT_CRITICAL();
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OS_EXIT_CRITICAL();
}
3:无等待请求互斥信号量OSMutexAccept
就是任务请求信号量,若是信号量可用就使用信号量,若是不可用就去执行其他代码,不阻塞等待。
若是有可用的,那么其操作跟请求互斥信号量OSMutexPend的信号量可用时执行代码部分是一样的。
4:改变任务的优先级并重新就绪OSMutex_RdyAtPrio
函数的原型如下:static void OSMutex_RdyAtPrio (OS_TCB *ptcb,INT8U prio)
该函数的功能是将ptcb指向的任务控制块的任务的优先级改为prio并且就绪 。
其代码如下:
y = ptcb->OSTCBY; //首先将任务从就绪表和就绪数组中清除
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBPrio = prio; //将任务控制块的任务优先级赋值为prio
OSPrioCur = prio; //将OSPrioCur改为prio
ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 3u) & 0x07u); //更新TCB任务控制块的跟优先级相关的数据成员
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
OSRdyGrp |= ptcb->OSTCBBitY; //更新就绪表
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; //更新就绪数数组
OSTCBPrioTbl[prio] = ptcb; //将对应优先级的数组指正指向该任务控制块
5:互斥信号量的删除OSMutexDel
互斥信号量的删除跟信号量的删除函数都一样,函数都差不多都一样。除了优先级继承那一部分之外,该部分代码如下。
pip = (INT8U)(pevent->OSEventCnt >> 8u); //取继承优先级的数值
prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); //获取取得信号量优先级的任务
ptcb = (OS_TCB *)pevent->OSEventPtr;
if (ptcb != (OS_TCB *)0) { //看是否ECB的任务控制块指针是否为空
if (ptcb->OSTCBPrio == pip) { //如果不空
OSMutex_RdyAtPrio(ptcb, prio); //恢复之前任务的优先级
}
}
6:提交互斥信号量OSMutexPost
当任务使用完互斥资源之后,需要提交信号量对信号量进行释放,同时将等待该信号量的任务中的最高优先级的任务就绪。
该函数原型如下:INT8U OSMutexPost (OS_EVENT *pevent)。
除了前面的参数之外
pip = (INT8U)(pevent->OSEventCnt >> 8u); //取得优先级的继承优先级数值
prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); //取得信号量任务的优先级
if (OSTCBCur != (OS_TCB *)pevent->OSEventPtr) { //判断是提交信号量的任务是不是取得信号量的任务。没有取得该信号量的任务去释放该信号量肯定不科学
OS_EXIT_CRITICAL();
return (OS_ERR_NOT_MUTEX_OWNER);
return (OS_ERR_NOT_MUTEX_OWNER);
}
if (OSTCBCur->OSTCBPrio == pip) { //如果取得信号量的任务的优先级等于继承优先级的数值
OSMutex_RdyAtPrio(OSTCBCur, prio); //恢复取得信号量之前的优先级
}
OSTCBPrioTbl[pip] = OS_TCB_RESERVED; //将pip优先级对应的任务优先级数组指针赋值为OS_TCB_RESERVED
if (pevent->OSEventGrp != 0u) { //是否有等待任务
/* Yes, Make HPT waiting for mutex ready */
prio = OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX, OS_STAT_PEND_OK); //将等待信号量最高优先级任务的就绪
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8; //高8位保存 pip(继承优先级)
pevent->OSEventCnt |= prio; //低八位保存刚刚转为就绪任务的优先级
pevent->OSEventPtr = OSTCBPrioTbl[prio]; //对应ECB的TCB指针赋值为对应prio优先级的优先级控制块指针(即该对应的任务控制块地址)
if (prio <= pip) { //刚刚转为就绪任务的优先级小于继承优先级
OS_EXIT_CRITICAL(); /* ... than current task! */
OS_Sched(); //执行调度
return (OS_ERR_PIP_LOWER);
} else {
OS_EXIT_CRITICAL();
} else {
OS_EXIT_CRITICAL();
OS_Sched();
return (OS_ERR_NONE);
}
}
}
pevent->OSEventCnt |= OS_MUTEX_AVAILABLE; //如果没有任务等待信号量,则将低8位赋值为OS_MUTEX_AVAILABLE,即信号量的状态为可用
pevent->OSEventPtr = (void *)0;
7:查询互斥信号量状态OSMutexQuery
INT8U OSMutexQuery (OS_EVENT *pevent,OS_MUTEX_DATA *p_mutex_data);
该函数的功能为查询pevent指向的信号量的信息,并且将信息存储在p_mutex_data指向的结构体中。
除了前面的参数检查之外,功能实现代码如下:
OS_ENTER_CRITICAL();
p_mutex_data->OSMutexPIP = (INT8U)(pevent->OSEventCnt >> 8u); //高8位的数据,即继承优先级的数值
p_mutex_data->OSOwnerPrio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); //信号量拥有者的优先级
if (p_mutex_data->OSOwnerPrio == 0xFFu) { //若是低8位全为1
p_mutex_data->OSValue = OS_TRUE; //则表示信号量可用
} else {
p_mutex_data->OSValue = OS_FALSE;//否则信号量不可用
} else {
p_mutex_data->OSValue = OS_FALSE;//否则信号量不可用
}
p_mutex_data->OSEventGrp = pevent->OSEventGrp; //复制任务等待信号量表
psrc = &pevent->OSEventTbl[0];
pdest = &p_mutex_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++; //复制任务等待信号量数组
}
OS_EXIT_CRITICAL();