ucos訊號量的理解2
ucos 對訊號量的支援由os_sem.c os_core.c支援,其中os_core.c提供OS_EVENT 資料結構的一些基本操作,os_sem.c則實現具體的訊號量,訊號量實現的分析,主要資料結構問題。
1.OS_EVENT結構的實現分析
typedef struct{
INT8U OSEventType //事件控制塊的型別
INT8U OSEventGrp //等待的任務組
INT16U OSEventCnt;//訊號量計數
void *OSEventPtr; //此處作用連結串列的連結指標
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//等待任務表
}OS_EVENT;
OS_EVENT 結構體中,訊號量主要涉及域為OSEventCnt OSEventGrp OSEventTbl 其中OSEventCnt 用於計數,OSEventGrp, OSEventTbl 用於
支援任務掛起,參照前面的分析 OSEventCnt相當於S
由該結構定義了以下變數用於支援多個訊號量的連結
OS_EXT OS_EVENT *OSEventFreeList;
OS_EXT OS_EVENT OSEventTbl[OS_MAX_EVENTS];
OSEventTbl構成由OSEventFreeList為頭指標,以OSEventPtr為連結指標的單鏈表,對於OS_EVENT項的獲取和收回都是基於連結串列,由連結串列OSStart呼叫
OS_InitEventList構成
對於OS_EVENT基本操作有:
static void OS_InitEventList(void)
功能:初始化訊號量列表,將OSEventTbl中的各項構成以OSEventFreeList 為頭指標的單鏈表,以進行各項的插入與操作。
void OS_EventWaitListInit(OS_EVENT *pevent);
功能:僅初始化OS_EVENT中的OSEventGrp OSEventTbl初始化等待的任務列表為空,這裡的等待列表和任務就緒表結構和功能類似。
INT8U OS_EventTaskRdy(OS_EVENT *pevent, void *msg,INT8U msk);
功能:將任務掛起,插入到OS_EVENT中等待任務列表中。
void OS_EventT0(OS_EVENT *pevent);
功能:將任務置為就緒,但原因是超時。
具體的程式碼分析可諮詢檢視原始檔和後面列出的參考書
分析這些程式碼,會發現程式碼的實現還是比較簡單的,主要涉及到一些任務的掛起和就緒的操作,此時,有可能有這樣的問題:
為什麼要單獨列出OS_EVENT 這樣的結構,實際上,不僅是訊號量,郵箱模組等也用到OS_EVENT結構,上面的操作時訊號量與郵箱共享的一組操作
,需要注意的是,程式碼涉及到一些任務管理相關操作,如任務的排程,掛起,就緒
2.基於OS_EVENT的訊號量實現
在os_sem.c中,實現了完整訊號量操作:
OS_EVENT *OSSemCreate(INT16U cnt);
功能:建立一個訊號量,從OSEventFreeList 中取出一空閒OS_EVENT,進行必要的初始化,對訊號量而言,主要的是初始化OSEventCnt域呼叫OS_EventWaitLIstInit()
初始化等待任務列表清空。
OS_EVENT *SSemDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
功能:刪除一個訊號量,收回OS_EVENT,如果OS_EVENT有等待的任務,則呼叫OS_EventTaskRdy()等待的任務列表任務為就緒。
void OSSemPend(OS_EVENT *pevent,INT16U timeout,INT8U *err);
功能:申請一個訊號量,任務在無法獲得訊號量時會呼叫OS_EventTaskWait()掛起,再呼叫OSSched()進行任務切換,超時返回呼叫OS_EventTo將任務之外就緒。
INT8U OSSemPost(OS_EVEENT *pevent)
功能:查詢訊號量,可以直接通過pevent 直接獲取訊號量的資訊,但是pevent 很多時候是被多個任務共享的,會隨時發生改變,使用額外的pdata可以快速的獲取當前訊號
量的副本。
INT8U OSSemQuery (OS_EVENT *pevent,OS_SEM_DATA *pdata);
功能:查詢訊號量,可以直接通過pevent直接獲取訊號量的資訊,但是pevent 很多時候是別多個任務共享的,隨時發生改變,
INT16U OSSemAccept(OS_EVENT *pevent);
功能:獲取訊號量,如果無法獲取,立即退出。
參考上面的說明,可以明確訊號量的實現與OS_EVENT結構操作函式呼叫關係,一旦立即這種呼叫關係,會發現訊號量的實現與很
容易理解,對OSEventCnt操作,是通過短暫的開關中斷實現,在關中斷期間,可以對OS_Event 結構進行操作,而不用擔心被中斷,
換句話說,通過短暫的開關中斷,實現核心內部理解操作。
三 訊號量的應用
以下將通過兩個例子,實現使用訊號量實現資源共享和任務鍵的同步
1.簡單的交通燈控制--訊號量進行任務同步的演示,
這裡交通燈由6個簡單的LED構成,由兩個任務分別控制紅 綠 黃 三個LED
task1: 綠--黃--紅--黃
task0:紅--黃--綠--黃--
void task1(void *pdata)
{
INT8U error;
while(1) {
OSSemPend(sem,0,&error);
LED1_ON(GREEN);
LED1_OFF(YELLOW);
OSSemPend(sem,0,&error);
LED1_ON(YELLOW);
LED1_OFF(GREEN);
OSSemPend(sem,0,&error);
LED1_ON(RED);
LED1_OFF(YELLOW);
OSSemPend(sem,0,&error);
LED1_ON(YELLOW);
LED1_OFF(RED);
}
void task0(void *pdata)
{
INT8U error;
t2init();
while(1)
{
LED0_ON(RED);
LED0_OFF(YELLOW);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY0,0);
LED0_ON(YELLOW);
LED0_OFF(RED);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY1,0);
LED0_ON(GREEN);
LED0_OFF(YELLOW);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY2,0);
LED0_ON(YELLOW);
LED0_OFF(GREEN);
OSSemPost(sem);
OSTimeDlyHMSM(0,0,DELAY3,0);
}
}
}
int main()
{
OSInit();
LED0_INIT();
LED1_INIT();
sem=OSSemCreate(0);
OSTaskCreate(task0,(void*)0,&stk0[99],3);
OSTaskCreate(task1,(void*)0,&stk0[99],2);
OSStart();
return 0;
}
如航母的程式碼,建立了task1,task0任務,用訊號量進行同步。
交通燈的控制,當然可以用一個任務進行控制,但就用不著訊號量了,如果用兩個任務,各個任務依次點亮燈,
然後延時,這樣兩個任務就各自點燈,沒有同步,此種方法存在問題是,在系統執行一些時間後,兩個任務中其中會執行
過快或過慢,兩邊的交通燈顯示的變換就不同時的。
task0每次在點燈後,向task1傳送訊號量,通知task1可以進行後續點燈操作,task1在點燈後,會繼續申請訊號量,這時會
被掛起,不能繼續執行,當task0在延時返回,更換led顯示,再向task1傳送訊號量,這樣task1又有哪些,更改其LED顯示,然後
再掛起,如此反覆,
因為task1的點燈操作都是在task0傳送訊號量進行,所以二者的冬至能做到同步
2)使用訊號量閃爍LED - 訊號量實現共離資源訪問
void task1( void * pdata )
{
INT8U error;
while(1)
{
OSSemPend( sem, 0, &error );
LED1_ON( RED ); // 點燈
OSTimeDlyHMSM( 0, 0, 1, 0 );
OSSemPost( sem );
}
}
void task0( void * pdata )
{
INT8U error;
t2init();
while(1)
{
OSSemPend( sem, 0, &error );
LED1_OFF( RED ); // 關燈
OSTimeDlyHMSM( 0, 0, 1, 0 );
OSSemPost( sem );
}
}
int main()
{
OSInit();
LED0_INIT();
LED1_INIT();
sem = OSSemCreate(1);
OSTaskCreate( task0, (void *)0, &stk0[99], 3 );
OSTaskCreate( task1, ( void *)0, &stk1[99], 2 );
OSStart();
return 0;
}
與前面的程式碼不同,上面的程式碼則使用兩個任務來控制LED閃爍。task1負責點燈,task0負責熄燈。
因為led只有一盞,在半個週期(1s)內只允許有點燈或者熄燈,即只有task0/task1執行。led此時作為臨界資源,一次只允許一個任務佔有,直至其放棄使用。
程式碼中的sem實際上是作為通行證,因為只有一盞led,所以通行證在建立時值1。task1,task0首行通過OSSemPend()申請通行證,當早請到時對LED操作,延時,然後
放棄通行證,此時另外一個任務可以獲取通行證再對led操作。可以看出,通過訊號量,不會出現多個任務在操作led時的衝突;同時task0和task1在操作LED時也實現了操作的同步.
三、關於訊號量的實現的體會
1、Ucos的訊號量實現還是比較簡單的。這裡的簡單指的是其程式碼易讀,易理解。簡單來說,需要是實現訊號量計數與任務掛起列表的實現。對於任務程式碼而言,訊號量提供了共享資源與同步的支援。而訊號量本身的實現,則是通過開斷中斷實現臨界區的訪問。
2、訊號量可以用於共享資源的訪問和任務間的同步,實際在應用中如果處理不當,可能會產生“死鎖”。死鎖問題,在ucos核心中沒有提供相應的解決方案,需要使用者程式碼支援。
3、並不是ucos相關的API可在所有情況下可用:ISR不可呼叫OSSemPend(),OSSemCreate().在使用時需要特別注意.