上一节我们引入了信号量的概念,这一讲我们将揭晓互斥信号量的奥秘。
互斥信号量和信号量虽然都带了信号量的帽子,但是二者却有着不同的运用场合,互斥信号量相比而言经常用于一些资源的互斥访问,比如打印机、厕所等,这里的厕所指的是单厕,哈哈哈。这样有的人就要问了,那信号量设置起始cnt为1不也可以实现资源的互斥访问吗,这样的话我们直接使用信号量的实现不就可以了吗?答案当然是否认的,作为OS的开发者,哪些大咖们怎么可能没有想到这些问题。-----为了解决在信号量使用过程中一些任务的优先级反转问题(就是低优先级抢占CPU在高优先级任务前得到运行的现象),于是乎给信号量来了一个优先级提升,但凡低优先级任务抢占CPU后,如果后面来了一个高优先级任务申请该"信号量",那么低优先级任务将被提升到所有任务中最高优先级来保证"信号量"可以尽早的得到释放。然后就成了我们今天要谈的互斥信号量。
首先来看看互斥信号量在事件控制块的基础上使用了哪些资源。
从图中 可以看出,OSEventType用于指示资源的类型,而对于,其实OSEventCnt的高八位用于存放提升后的优先级,而前面则用于指示资源的状态,以及在优先级提升后短暂的存放开始的任务优先级,后两个OSEventGrp和OSEventTbl[]在任何一种类型的等待事件过程中都会使用,我们不讲,那我们就来说说OSEventPtr,很多人都任务在互斥里边他没有被使用,直接指向NULL,其实不然,他其实指向了任务的TCB。
互斥信号量的操作一共6个函数
OS_EVENT *OSMutexCreate(INT8U prio, INT8U *perr); //互斥信号量的创建
void OSMutexPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr);//挂起
INT8U OSMutexPost (OS_EVENT *pevent);//释放
BOOLEAN OSMutexAccept (OS_EVENT *pevent, INT8U *perr);//无等待挂起
OS_EVENT *OSMutexDel (OS_EVENT *pevent, INT8U opt, INT8U *perr);//删除互斥量
INT8U OSMutexQuery (OS_EVENT *pevent, OS_MUTEX_DATA *p_mutex_data);//查询
一个内部函数
static void OSMutex_RdyAtPrio (OS_TCB *ptcb, INT8U prio);
其通讯机理如下图
好了 相信大家对互斥信号量有了一个初步的认识。那我们继续
与以往相同,我们来分析分析这些函数的内部实现
- OS_EVENT *OSMutexCreate (INT8U prio,
- INT8U *perr)
- {
- OS_EVENT *pevent;
- #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
- OS_CPU_SR cpu_sr = 0u;
- #endif
-
- #ifdef OS_SAFETY_CRITICAL
- if (perr == (INT8U *)0) {
- OS_SAFETY_CRITICAL_EXCEPTION();
- return ((OS_EVENT *)0);
- }
- #endif
-
- #ifdef OS_SAFETY_CRITICAL_IEC61508
- if (OSSafetyCriticalStartFlag == OS_TRUE) {
- OS_SAFETY_CRITICAL_EXCEPTION();
- return ((OS_EVENT *)0);
- }
- #endif
-
- #if OS_ARG_CHK_EN > 0u
- if (prio != OS_PRIO_MUTEX_CEIL_DIS) {
- if (prio >= OS_LOWEST_PRIO) {
- *perr = OS_ERR_PRIO_INVALID;
- return ((OS_EVENT *)0);
- }
- }
- #endif
- if (OSIntNesting > 0u) {
- *perr = OS_ERR_CREATE_ISR;
- return ((OS_EVENT *)0);
- }
- OS_ENTER_CRITICAL();
- if (prio != OS_PRIO_MUTEX_CEIL_DIS) {
- if (OSTCBPrioTbl[prio] != (OS_TCB *)0) {
- OS_EXIT_CRITICAL();
- *perr = OS_ERR_PRIO_EXIST;
- return ((OS_EVENT *)0);
- }
- OSTCBPrioTbl[prio] = OS_TCB_RESERVED;
- }
-
- pevent = OSEventFreeList;
- if (pevent == (OS_EVENT *)0) {
- if (prio != OS_PRIO_MUTEX_CEIL_DIS) {
- OSTCBPrioTbl[prio] = (OS_TCB *)0;
- }
- OS_EXIT_CRITICAL();
- *perr = OS_ERR_PEVENT_NULL;
- return (pevent);
- }
- OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
- OS_EXIT_CRITICAL();
- pevent->OSEventType = OS_EVENT_TYPE_MUTEX;
- pevent->OSEventCnt = (INT16U)((INT16U)prio << 8u) | OS_MUTEX_AVAILABLE;
- pevent->OSEventPtr = (void *)0;
- #if OS_EVENT_NAME_EN > 0u
- pevent->OSEventName = (INT8U *)(void *)"?";
- #endif
- OS_EventWaitListInit(pevent);
- *perr = OS_ERR_NONE;
- return (pevent);
- }
该函数的目的是初始化事件控制块为互斥信号量,其中,当prio输入为OS_PRIO_MUTEX_CEIL_DIS,任务将不进行优先级提升,此时和单一的信号量的作用是相同的。还有一点也是需要特别留意,如果任务的优先级提升了,那么任务在使用完指定资源过后,怎么重新恢复他原有的优先级呢?想必看过源码的都已经了解了,这也是UCOS设计者的另外一个高明之处---变量的复用,通过将提升后的任务优先级放到OSEventCnt的高八位,从而保存该优先级而不需要重新定义变量,节省了系统对RAM的要求。