If you want to talk about mutex in ucos task-to-task communication, you will find that it is cleverly designed. Like sem, it is implemented by event mechanism. The code is not analyzed in every line, because Mr. Shao Bei is very clear about how mutex's kernel is implemented.Understand that mutex is a special case when the semaphore value is set to 1. The difference is that mutex uses priority inheritance mechanism to avoid priority inversion. This paper mainly talks about the creation of mutex, pend and post. The corresponding functions are OSMutexCreate, OSMutexPend, OSMutexPost. Of course, functions will not be all extended functions, just one thing.The code posted below is also a simplified code.
You will talk about the code in blocks, and in the process, you will talk about the mechanism, function, implementation and code interpolation of mutually exclusive locks.
First of all, from the point of mutex creation, mutex is implemented using event mechanism as well, but unlike sem, mutex passes a higher priority to event when it is created, and is used for priority inheritance when low priority tasks get mutex, while SEM passes a parameter of semaphore value.The OSMutexCreate creation code is as follows:
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 first checks whether the task TCB passed in priority has been used. If the task TCB passed in priority has already been used, the creation fails and an error is returned. If the priority TCB passed in by the parameter is not occupied, it is set to reserved to ensure that the TCB will not be used by other tasks; then it is the same as the semaphore.To take an idle event structure and determine if there is no idle event, an error is returned; after obtaining the event, it is initialized, OSEventType is OS_EVENT_TYPE_MUTEX, OSEventCnt has a different value from sem, setting the high 8 bits as the priority to inherit (the priority of parameter passed in) and the low 8 bits as OS_MUTEX_AVAILABLE, which is 0x00FF, respectively.People understand that this value is equivalent to semaphore bit 1 in sem. When the lower 8 bits are detected in pend, it means that the task itself can be prioritized at the lower 8 bits. If the lower 8 bits already have a priority, it means that the task that wants to acquire the lock needs to be suspended.Later, in pend; OSEventPtr will be cleared to zero for unclear purposes, as if this parameter was not used for mutex; then zeroing initialization for event group s and tables indicates that there are currently no tasks waiting for mutex in groups and tables.
The pend and post operations on mutexes exist in pairs after the mutex is created, and the two functions are OSMutexPend and OSMutexPost.Next, I'll start with OSMutexPend's code to analyze how it works. Some of it is the same as sem, and the code is as follows:
void OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr) { 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)======================================================================================================== 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->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 { 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)==================================================================================================== }
The OSMutexPend function is divided into four parts as shown above (separated by'='). The first and last parts work the same as sem. The first part mainly checks whether the current event type is mutex and whether the current operation is in the interrupt handler.The last part is about task suspension, task scheduling and preparation when the task regains the mutex; unlike sem, the second and third parts are not actually run in one operation at the same time. When the second part runs, it indicates that no task uses mutex, and the task running this function willObtain the mutex and exit, and the third part represents the operation of a new task when it tries to acquire the mutex and the mutex is already occupied.
The second part runs as follows. It was previously said that the lower 8 bits of OSEventCnt store OS_MUTEX_AVAILABLE, if the check result is still OS_MUTEX_AVAILABLE.This means that the mutex is not occupied, at which point the priority of the currently running task is saved to the lower 8 bits of the OSEventCnt, and then a comparison is made to see if the priority of the current task is lower than the inheritance priority at creation (higher priority value), if the priority of the current task is higher than the inheritance priority (lower), an error is returned because the inheritance priority is to prevent priorityFlip it over and set it to return the correct value if it has a lower priority, which is returned by the parameter address, and then save the TCB of the task currently obtaining the mutex to the OSEventPtr pointer.
The flow of the third part is to first get the priority of the task occupying the mutex and the TCB, then compare the priority of the task that you want to acquire the mutex (1) with the priority of the task occupying the mutex (2). If (1) has a higher priority than (2), then do nothing, execute the fourth part directly, and suspend the current task; if (1) has a higher priority than (2)If the priority of (1) is low, the priority of (1) needs to be raised, and (2) needs to be suspended as well.For cases where (1) priority is lower than (2), first check whether the current task is running, then raise the priority of the mutex-occupying task to the inherited priority level, and then decide whether to continue the task in the ready table or the task's event waiting list based on the running status of the check task.Save the TCB holding the mutex task to the prio table of the system.
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)==================================================================================================== }
The OSMutexPost is also described in sections. The first part is to check the type of event and whether it is in the interrupt, which is the same as the sem. The second part is to restore the mutex lock, first to get the inheritance priority through the event and the priority of the task that now holds the mutex. Previously, the pend and post of the mutex lock exist in pairs, after the task pend acquires the mutexThe corresponding task posts are also required to release mutexes, so in the second part, there will be a judgment whether the current post releases mutexes is a task that owns mutexes, if not, an error will be made, if a task that owns mutexes is releasing mutexes, it will be judged whether the task has elevated the priority of the task to the level of inheritance priority when pending, if any.The priority of the current task needs to be restored to its original priority level through OSMutex_RdyAtPrio.
The third part of this section determines whether a task is waiting for the current event mutex, if any, obtains the priority of the waiting task through OS_EventTaskRdy, removes the acquired task from the event waiting list, finds the code from OS_EventTaskRdy, and sets mutex-related parameters as in pend If there is no event mutex waiting task, the clear 0 operation of OSEventCnt bit OS_MUTEX_AVAILABLE and OSEventPtr will be set directly.
In the third section, a clever point is that when there is a task in the event's waiting list, the mutex is handed over directly to the waiting task, the post release operation is done while waiting for the task to be used to complete, if there are multiple tasks waiting, the mutex is released one by one, and when all tasks are released, the mutex is completely released when returning to the current taskReturn to OS_ERR_NONE. If you don't wait for a task to be in the event's waiting list, you need the current task to release itself, which is the last four lines of the third section.
https://www.cnblogs.com/MyLove-Summer/p/5188383.html