ucos实时操作系统学习笔记——任务间通信(互斥锁) 想讲⼀下ucos任务间通信中的mutex,感觉其设计挺巧妙,同sem⼀样使⽤的是event机制实现的,代码不每⼀⾏都分析,因为讲的没邵贝贝⽼师清楚,主要讲⼀下mutex的内核是如何实现的。可以理解互斥锁是设置信号量值为1时候的特殊情况,与之不同的地⽅是互斥锁为了避免优先级反转采⽤了优先级继承机制,本⽂主要讲⼀下互斥锁的创建,pend和post,对应的函数是 OSMutexCreate,OSMutexPend,OSMutexPost,当然讲函数也不会所有的扩展功能都讲,只是讲⼀下主⼲部分,下⾯贴出来的代码也是简化的代码。 会通过分块的⽅式讲代码,在讲的过程中会把互斥锁的机制、功能以及具体实现与代码穿插讲⼀下。
⾸先,从互斥锁创建讲起,互斥锁同⼀样使⽤event机制来实现,不过与sem不同的是互斥锁在创建的时候会传递⼀个⾼⼀点的优先级给event,当低优先级任务获得互斥锁时⽤于优先级继承,⽽sem传递的参数是信号量的值。OSMutexCreate创建代码如下所⽰:
OS_EVENT *OSMutexCreate (INT8U prio,
INT8U *perr)
{
OS_EVENT *pevent;
if (OSIntNesting > 0u) { /* See if called from ISR ... */
*perr = OS_ERR_CREATE_ISR; /* ... can't CREATE mutex from an ISR */
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[prio] != (OS_TCB *)0) { /* Mutex priority must not already exist */
OS_EXIT_CRITICAL(); /* Task already exist at priority ... */
*perr = OS_ERR_PRIO_EXIST; /* ... inheritance priority */
return ((OS_EVENT *)0);
}
OSTCBPrioTbl[prio] = OS_TCB_RESERVED; /* Reserve the table entry */
pevent = OSEventFreeList; /* Get next free event control block */
if (pevent == (OS_EVENT *)0) { /* See if an ECB was available */
OSTCBPrioTbl[prio] = (OS_TCB *)0; /* No, Release the table entry */
OS_EXIT_CRITICAL();
*perr = OS_ERR_PEVENT_NULL; /* No more event control blocks */
return (pevent);
}
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; /* Adjust the free list */
OS_EXIT_CRITICAL();
pevent->OSEventType = OS_EVENT_TYPE_MUTEX;
pevent->OSEventCnt = (INT16U)((INT16U)prio << 8u) | OS_MUTEX_AVAILABLE; /* Resource is avail. */
pevent->OSEventPtr = (void *)0; /* No task owning the mutex */
OS_EventWaitListInit(pevent);
*perr = OS_ERR_NONE;
return (pevent);
}
互斥锁的创建同样不允许在中断中进⾏,mutex⾸先会检查传⼊优先级的任务TCB有没有已经被使⽤,如果传⼊优先级已经被使⽤,则创建失败,返回错误,如果参数传⼊的优先级的TCB没有被占⽤,则设置为reserved,保证该TCB不会被其他任务使⽤;然后是跟信号量⼀样,要取⼀个空闲的event结构体,判断如果没有空闲event则返回错误;取得event之后要对其进⾏初始化,OSEventType为
OS_EVENT_TYPE_MUTEX类型,OSEventCnt的数值跟sem不同,在mutex中将⾼8位设置为需要继
承的优先级(参数传⼊的优先级),低8位为OS_MUTEX_AVAILABLE,这个值为0x00FF,个⼈理解这个值相当于sem中信号量位1的作⽤,当pend中监测到低8位是这个值时,表⽰可以将任务本⾝的优先级放到低8位的位置上,如果低8位已经有优先级存在,则表⽰需要把想获得锁的任务挂起。之后pend中会讲到;然后会对OSEventPtr清0,具体⽬的不清楚,对mutex来说,好像没有⽤到这个参数;之后是对event group和table的清零初始化操作,表⽰当前没有等待互斥锁的任务在group和table中。 互斥锁创建完成之后就是有关互斥锁的pend和post操作了,这两个操作是成对存在的,两个函数分别是OSMutexPend和OSMutexPost。接下来从OSMutexPend的代码开始分析其主要的操作是怎么样的,其中有⼀部分的内容和sem是相同的,代码如下所⽰:
void OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr)
{
INT8U pip; /* Priority Inheritance Priority (PIP) */
INT8U pip; /* Priority Inheritance Priority (PIP) */
INT8U mprio; /* Mutex owner priority */
BOOLEAN rdy; /* Flag indicating task was ready */
OS_TCB *ptcb;
OS_EVENT *pevent2;
INT8U y;
if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type */
*perr = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0) { /* See if called from ISR ... */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return;
}
if (OSLockNesting > 0) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return;
}
OS_ENTER_CRITICAL();
(1)========================================================================================================= pip = (INT8U)(pevent->OSEventCnt >> 8); /* Get PIP from mutex */
/* Is Mutex available? */
if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE) {
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8; /* Yes, Acquire the resource */
pevent->OSEventCnt |= OSTCBCur->OSTCBPrio; /* Save priority of owning task */
pevent->OSEventPtr = (void *)OSTCBCur; /* Point to owning task's OS_TCB */
if (OSTCBCur->OSTCBPrio <= pip) { /* PIP 'must' have a SMALLER prio ... */
OS_EXIT_CRITICAL(); /* ... than current task! */
*perr = OS_ERR_PIP_LOWER;
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
}
return;
}
(2)========================================================================================================l型密封圈
mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); /* No, Get priority of mutex owner */
ptcb = (OS_TCB *)(pevent->OSEventPtr); /* Point to TCB of mutex owner */
if (ptcb->OSTCBPrio > pip) { /* Need to promote prio of owner?*/
if (mprio > OSTCBCur->OSTCBPrio) {
y = ptcb->OSTCBY;
if ((OSRdyTbl[y] & ptcb->OSTCBBitX) != 0) { /* See if mutex owner is ready */
无电沉镍OSRdyTbl[y] &= ~ptcb->OSTCBBitX; /* Yes, Remove owner from Rdy ...*/
if (OSRdyTbl[y] == 0) { /* ... list at current prio */
OSRdyGrp &= ~ptcb->OSTCBBitY;
}
rdy = OS_TRUE;
} else {
pevent2 = ptcb->OSTCBEventPtr;
if (pevent2 != (OS_EVENT *)0) { /* Remove from event wait list */
if ((pevent2->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {
pevent2->OSEventGrp &= ~ptcb->OSTCBBitY;
}
}
rdy = OS_FALSE; /* No */
}
ptcb->OSTCBPrio = pip; /* Change owner task prio to PIP */
#if OS_LOWEST_PRIO <= 63
ptcb->OSTCBY = (INT8U)( ptcb->OSTCBPrio >> 3);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x07);
ptcb->OSTCBBitY = (INT8U)(1 << ptcb->OSTCBY);
ptcb->OSTCBBitX = (INT8U)(1 << ptcb->OSTCBX);
#else
ptcb->OSTCBY = (INT8U)((ptcb->OSTCBPrio >> 4) & 0xFF);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x0F);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x0F);
ptcb->OSTCBBitY = (INT16U)(1 << ptcb->OSTCBY);
ptcb->OSTCBBitX = (INT16U)(1 << ptcb->OSTCBX);
#endif
if (rdy == OS_TRUE) { /* If task was ready at owner's priority ...*/
锚
OSRdyGrp |= ptcb->OSTCBBitY; /* ... make it ready at new priority. */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
} else {
DD LM0558
pevent2 = ptcb->OSTCBEventPtr;
if (pevent2 != (OS_EVENT *)0) { /* Add to event wait list */
pevent2->OSEventGrp |= ptcb->OSTCBBitY;
pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
OSTCBPrioTbl[pip] = ptcb;
}
}
(3)==========================================================================================================
OSTCBCur->OSTCBStat |= OS_STAT_MUTEX; /* Mutex not available, pend current task */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Store timeout in current task's TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
物联网实验设备
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted getting mutex */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get mutex within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
OS_EXIT_CRITICAL();
(4)====================================================================================================
}
本⽂将OSMutexPend函数分为了如上所⽰的4个部分(使⽤“=”隔开),第⼀部分和最后⼀部分和sem的操作相同,第⼀部分主要就是检查⼀下当前的event类型是不是mutex以及检查当前的操作是不是在中断处理程序中进⾏的, 最后⼀部分讲的的就是讲任务挂起然后进⾏任务调度以及当任务重新获得互斥锁时的准备⼯作;与sem不同的主要是第⼆和第三部分,其实这两部分的内容是不会同时在⼀次操作中运⾏的,当第⼆部分运⾏的时候表⽰没有任务使⽤互斥锁,这时候运⾏该函数的任务将获得互斥锁并退出,第三部分则是表⽰⼀个新的任务在尝试获得互斥锁,并且互斥锁已经被占⽤时的操作。
第⼆部分的运⾏流程是这样的,之前创建的时候也说过OSEventCnt的低8位存放的是OS_MUTEX_AVAILABLE,如果检查结果仍为OS_MUTEX_AVAILABLE,则说明互斥锁没有被占⽤,这时候就要保存当前运⾏任务的优先级到OSEventCnt的低8位中,然后⽐较⼀下当前任务的优先级是否⽐创建时的继承优先级低(优先级值⼤),如果当前任务的优先级⽐继承优先级⾼(⼩),则返回错误,因为继承优先级是为了防⽌优先级翻转⽽设定,如果其优先级低则实现不了这⼀功能,反之,则返回正确值,此处的返回值是通过参数地址返回的;然后将当前获取互斥锁的任务的TCB保存到OSEventPtr指针中。
第三部分的运⾏流程是,⾸先获得占⽤互斥锁任务的优先级以及TCB,然后将当前想获取互斥锁的任务的优先级(1)和占有互斥锁任务的优先级(2)做⽐较,如果(1)⽐(2)的优先级⾼,则不做任何操作,直接执⾏第4部分,将当前任务挂起;如果(1)⽐(2)的优先级低,则需要提升(1)的优
先级,同样要挂起(2)。对于(1)⽐(2)优先级低的情况,⾸先检查当前任务是不是处于运⾏状态,然后给占⽤互斥锁的任务提升优先级到继承优先级级别,然后根据检查任务的运⾏状态决定把任务继续放到ready table中还是放到任务的event等待列表中。将占有互斥锁的任务的TCB保存到系统的prio table中。
INT8U OSMutexPost (OS_EVENT *pevent)
{
INT8U pip; /* Priority inheritance priority */
INT8U prio;
if (OSIntNesting > 0) { /* See if called from ISR ... */
return (OS_ERR_POST_ISR); /* ... can't POST mutex from an ISR */
}
if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
(1)====================================================================================================
OS_ENTER_CRITICAL();
pip = (INT8U)(pevent->OSEventCnt >> 8); /* Get priority inheritance priority of mutex */
prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); /* Get owner's original priority */
if (OSTCBCur != (OS_TCB *)pevent->OSEventPtr) { /* See if posting task owns the MUTEX */
OS_EXIT_CRITICAL();整流罩
return (OS_ERR_NOT_MUTEX_OWNER);
}
if (OSTCBCur->OSTCBPrio == pip) { /* Did we have to raise current task's priority? */
OSMutex_RdyAtPrio(OSTCBCur, prio); /* Restore the task's original priority */
}
OSTCBPrioTbl[pip] = OS_TCB_RESERVED; /* Reserve table entry */
(2)=====================================================================================================
if (pevent->OSEventGrp != 0) { /* Any task waiting for the mutex? */
/* 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; /* Save priority of mutex's new owner */
pevent->OSEventCnt |= prio;
pevent->OSEventPtr = OSTCBPrioTbl[prio]; /* Link to new mutex owner's OS_TCB */
if (prio <= pip) { /* PIP 'must' have a SMALLER prio ... */
OS_EXIT_CRITICAL(); /* ... than current task! */
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_PIP_LOWER);
} else {
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
}
pevent->OSEventCnt |= OS_MUTEX_AVAILABLE; /* No, Mutex is now available */
pevent->OSEventPtr = (void *)0;
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
(3)====================================================================================================
}
对OSMutexPost同样进⾏分段描述,第⼀部分是检查event的type和是否在中断中,这跟sem是相同的;第⼆部分则是讲互斥锁的还原,⾸先是通过event获取继承优先级以及现在占有互斥锁的任务的优先级,之前讲过互斥锁的pend和post是成对存在的,当任务pend获取互斥锁之后,也需要相对应的任务post释放互斥锁,所以第⼆部分中会有⼀个判断当前post释放互斥锁的是否是占有互斥锁的任务,如果不是则会报错,如果是占有互斥锁的任务要释放互斥锁,则会判断任务在pend的时候有没有提升
任务的优先级到继承优先级的级别,如果有的话需要把当前任务的优先级通过OSMutex_RdyAtPrio还原到任务⾃⼰原来的优先级级别。
第三部分的内容这是会判断有没有任务在等待当前的event互斥锁,如果有的话就通过OS_EventTaskRdy获取等待任务的优先级,在获取优先级的同时会把获取的任务从event等待列表中删除,可以从OS_EventTaskRdy中到代码,然后设置event mutex相关参数如pend中的操作⼀样,同样需要判断任务的优先级是否⽐继承优先级⾼,如果⾼则报错,否则会任务调度到另外的任务中继续执⾏,相当于本任务的post过程完成,如果没有event mutex的等待任务,则会直接设置event的OSEventCnt位OS_MUTEX_AVAILABLE和OSEventPtr的清0操作。
在第三部分中⼀个⽐较巧妙的地⽅是,当有任务在event的等待列表时,会直接将互斥锁交给等待任务,等待任务使⽤完成时会做post 释放操作,如果有多个任务在等待,则会⼀⼀释放掉互斥锁,当所有任务释放掉之后,返回到当前任务时,则互斥锁已经完全释放了,这时返回OS_ERR_NONE,如果没有等待任务在event的等待列表中,则需要当前任务⾃⼰释放,也就是第三部分最后的4⾏操作。