想講一下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 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)==================================================================================================== }
本文將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行操作。
