ucos II 任務間 通訊之三:訊號量2
當我們建立好了訊號量之後,就可以使用訊號量了。使用訊號量需要調用OSSemPost()和OSSemPend()。關於具體怎麼使用訊號量,就得先看看這兩個系統函數的代碼。
先看等待訊號量OSSemPend()的代碼:
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) |
{ |
OS_ENTER_CRITICAL(); |
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (1) |
OS_EXIT_CRITICAL(); |
*err = OS_ERR_EVENT_TYPE; |
} |
if (pevent->OSEventCnt > 0) { (2) |
pevent->OSEventCnt--; (3) |
OS_EXIT_CRITICAL(); |
*err = OS_NO_ERR; |
} else if (OSIntNesting > 0) { (4) |
OS_EXIT_CRITICAL(); |
*err = OS_ERR_PEND_ISR; |
} else { |
OSTCBCur->OSTCBStat |= OS_STAT_SEM; (5) |
OSTCBCur->OSTCBDly = timeout; (6) |
OSEventTaskWait(pevent); (7) |
OS_EXIT_CRITICAL(); |
OSSched(); (8) |
OS_ENTER_CRITICAL(); |
if (OSTCBCur->OSTCBStat & OS_STAT_SEM) { (9) |
OSEventTO(pevent); (10) |
OS_EXIT_CRITICAL(); |
*err = OS_TIMEOUT; |
} else { |
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (11) |
OS_EXIT_CRITICAL(); |
*err = OS_NO_ERR; |
} |
} |
} |
在muc21看來,這麼多代碼就是做了一個重要的事情:就是將任務控制塊中的狀態標誌.OSTCBStat置1,也就是把任務置於睡眠狀態。這樣任務就處於等待訊號量來啟用任務的狀態了。可以這樣理解,當任務等待一個訊號量的時候,任務是被掛起了,需要等待發送訊號量來啟用。
下面具體分析下代碼的實現過程。
首先檢查指標pevent所指的任務控制塊是否是由OSSemCreate()建立的[(1)]。如果訊號量當前是可用的(訊號量的計數值大於0)[ (2)],將訊號量的計數值減1[(3)],然後函數將“無錯”錯誤碼返回給它的調用函數。顯然,如果正在等待訊號量,這時的輸出正是我們所希望的,也是運行OSSemPend()函數最快的路徑。
如果此時訊號量無效(計數器的值是0),OSSemPend()函數要進一步檢查它的調用函數是不是中斷服務子程式[ (4)]。在正常情況下,中斷服務子程式是不會調用OSSemPend()函數的。這裡加入這些代碼,只是為了以防萬一。當然,在訊號量有效情況下,即使是中斷服務子程式調用的OSSemPend(),函數也會成功返回,不會出任何錯誤。
OSSemPend()函數通過將任務控制塊中的狀態標誌.OSTCBStat置1,把任務置於睡眠狀態[ (5)],等待時間也同時置入任務控制塊中[ (6)],該值在OSTimeTick()函數中被逐次遞減。注意,OSTimeTick()函數對每個任務的任務控制塊的.OSTCBDly域做遞減操作(只要該域不為0)。真正將任務置入睡眠狀態的操作在OSEventTaskWait()函數中執行 [ (7)]。
因為當前任務已經不是就緒態了,所以任務調度函數將下一個最高優先順序的任務調入,準備運行[ (8)]。當訊號量有效或者等待時間到後,調用OSSemPend()函數的任務將再一次成為最高優先順序任務。這時OSSched()函數返回。這之後,OSSemPend()要檢查任務控制塊中的狀態標誌,看該任務是否仍處於等待訊號量的狀態[ (9)]。如果是,說明該任務還沒有被OSSemPost()函數發出的訊號量喚醒。事實上,該任務是因為等待逾時而由TimeTick()函數把它置為就緒狀態的。這種情況下,OSSemPend()函數調用OSEventTO()函數將任務從等待工作清單中刪除[
(10)],並返回給它的調用任務一個“逾時”的錯誤碼。如果任務的任務控制塊中的OS_STAT_SEM標誌位沒有置位,就認為調用OSSemPend()的任務已經得到了該訊號量,將指向訊號量ECB的指標從該任務的任務控制塊中刪除,並返回給調用函數一個“無錯”的錯誤碼[ (11)]。
接下來看下發送訊號量的代碼:
INT8U OSSemPost (OS_EVENT *pevent) |
{ |
OS_ENTER_CRITICAL(); |
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (1) |
OS_EXIT_CRITICAL(); |
return (OS_ERR_EVENT_TYPE); |
} |
if (pevent->OSEventGrp) { (2) |
OSEventTaskRdy(pevent, (void *)0, OS_STAT_SEM); (3) |
OS_EXIT_CRITICAL(); |
OSSched(); (4) |
return (OS_NO_ERR); |
} else { |
if (pevent->OSEventCnt < 65535) { |
pevent->OSEventCnt++; (5) |
OS_EXIT_CRITICAL(); |
return (OS_NO_ERR); |
} else { |
OS_EXIT_CRITICAL(); |
return (OS_SEM_OVF); |
} |
} |
} |
理解了等待訊號量函數的源碼後理解發送訊號量函數的源碼也就很容易了。
以上源碼的作用簡單來說就是尋找有沒有任務在等待這個訊號量,如果有,就把該任務從睡眠態拉回就緒態。
首先檢查參數指標pevent指向的任務控制塊是否是OSSemCreate()函數建立的[ (1)],接著檢查是否有任務在等待該訊號量[ (2)]。如果該任務控制塊中的.OSEventGrp域不是0,說明有任務正在等待該訊號量。這時,就要調用函數OSEventTaskRdy(),把其中的最高優先順序任務從等待工作清單中刪除[(3)]並使它進入就緒狀態。然後,調用OSSched()任務調度函數檢查該任務是否是系統中的最高優先順序的就緒任務[ (4)]。如果是,這時就要進行任務切換[當OSSemPost()函數是在任務中調用的],準備執行該就緒任務。如果不是,OSSched()直接返回,調用OSSemPost()的任務得以繼續執行。如果這時沒有任務在等待該訊號量,該訊號量的計數值就簡單地加1[
(5)]。