1. 程式人生 > >ucos訊號量的理解2

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().在使用時需要特別注意.