從零開始學習UCOSII操作系統7--信號量

從零開始學習UCOSII操作系統7--信號量


參考博客:@ http://blog.csdn.net/gatiemehttps://blog.csdn.net/gatieme/article/details/21071379

前言:這裏一定要分析清楚,因爲信號量分析清楚後,後面的郵箱等其他的通信的東西都是大同小異的。

1、信號量的組成

(1)一部分是16位無符號的整型信號量計數值(0~65536)

(2)另一部分是由等待信號量的任務組成的等待任務表。

(3)信號量的6個基本函數:
OSSemAccept(), OSSemCreate(),OSSemDel()
OSSemPend(),OSSemPost()以及OSSemQuery()

其中要使用這些信號量的函數,必須在OS_CFG.h中將配置常數置爲1,這樣UCOSII才能支持信號量。

當OS_SEM_EN設爲0的時候,所有的信號量都不能使用。
需要使用這些函數的時候,需要把這些宏定義置爲1;

2、信號量使用的函數

(1)OSSemAccept(),OSSemPost()以及OSSemQuery()函數可以由任務或者中斷服務子程序調用。

(2)OSSemDel()和OSSemPend()函數只能由任務程序調用。

3、信號量的兩個關鍵問題

(1)任務怎麼得到一個信號量的問題?

想要得到一個信號量的任務,必須執行等待的操作(pend)
如果信號量有效(非0),則信號量減1,任務繼續運行。

如果此時信號量無效,則等待信號量的任務就被列入等待信號量的任務表中。此時可以設置超時時間,如果等待了多少時間後,仍然沒有信號發生。
則此任務進入就緒態,準備運行,並且顯示出錯的代碼---等待超時失敗。

(2)任務對信號量的釋放問題
任務執行發信號的POST操作來釋放信號量,如果沒有任務等待該信號量,那麼這個信號量的值就是僅僅簡單的+1.則此時信號量大於0有效。

如果有任務等待該信號量,那麼就會有另一個任務進入就緒態,此時信號量就不+1了,因爲剛剛的信號量就用出去了。

4、信號量的有效與無效問題

信號量有效:信號量的計算器非0,信號量有效表示任務對資源可用。

信號量無效:信號量的計算器爲0,信號量無效表示任務對目前的資源不可用,需要等待其他的另一個任務或者中斷服務子程序發出該信號量。

5信號量的值OSEventCnt大小表示什麼?

二值信號量Mutext,表示任務可以獨佔共享資源

計數型的信號量Semaphore,用於某資源可同時爲N個任務所用。

二值信號量與互斥型信號量的區別:

互斥型信號量必須是同一個任務申請,同一個任務釋放,其他的任務釋放無效。

二值信號量,一個任務申請成功後,可以由另一個任務釋放,也可以由本任務釋放。

6、信號量是如何實現任務之間的通信的?

(1)信號量的建立必須在任務級中建立。

(2)信號量類型爲OS_EVENT,信號量值可以爲1和0(二值信號量),0~65536計數型的信號量,不同的值代表不同的意義。

(3)對於互斥型信號量來說,就兩個操作,請求和釋放。

(4)一個任務請求信號量的時候,如果被其他的任務佔用,則任務等待,同時導致任務切換。 如果沒有被其他任務佔用,則獲得,繼續執行。

(5)釋放信號量的時候,如果其他高優先級任務正在請求並且等待該信號量的時候,則導致任務切換。

(6)OSSemAccept(信號量)起到查詢信號量的作用,返回信號量的值。

(7)OSSemPend(Sem,timeout,&err);
timeout代表等待timeout個信號量後還沒有得到信號量,恢復到就緒的狀態,如果timeout=0,標誌等待無限信號量。

7、信號量的三個關鍵函數的代碼分析

(1)OSSemCreate()創建一個信號量(由任務或者啓動代碼操作)
創建工作必須在任務級代碼中或者多任務啓動之前完成。功能只要是先獲取一個事件控制塊ECB,寫入一些參數,其中調用了OS_EventWaitListInit()函數,對事件控制塊的等待任務列表進行初始化。

就是將事件控制塊定義爲信號量,然後把該填寫的量填寫。最後設置信號量的初始值

OS_EVENT  *OSSemCreate (INT16U cnt)
{
    OS_EVENT  *pevent;
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif



    if (OSIntNesting > 0) {                                /* 中斷服務不能創建信號量  */
        return ((OS_EVENT *)0);                           
    }
    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;                              /* 得到下一個空閒的事件空閒控制塊 */
    if (OSEventFreeList != (OS_EVENT *)0) {                /* See if pool of free ECB pool was empty   */
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {                         /* Get an event control block               */
        pevent->OSEventType    = OS_EVENT_TYPE_SEM;
        pevent->OSEventCnt     = cnt;                      /* 設置信號量的初始值                      */
        pevent->OSEventPtr     = (void *)0;                /* Unlink from ECB free list                */
#if OS_EVENT_NAME_SIZE > 1
        pevent->OSEventName[0] = '?';                      /* Unknown name                             */
        pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
        OS_EventWaitListInit(pevent);                      /* Initialize to 'nobody waiting' on sem.   */
    }
    return (pevent);
}

(2)刪除一個信號量
1、刪除一個信號量的函數OSSemDel()的源代碼,當文件OS_CFG.h中的OS_SEM_DEL_EN爲1的時候,該代碼才被編譯。

2、總之,在刪除信號量之前,必須首先刪除操作該信號量的所有的任務。

3、進入switch中,當OPT爲OS_DEL_NO_PEND,並且沒有任務在等待該信號量的時候,OSSemDel()函數將事件控制塊ECB標誌爲未使用,並將其退回到空閒事件控制鏈表中,此操作允許該事件用於創建另一個信號量。

當OPT爲OS_DEL_ALWAYS時候,所有等待該信號的任務都將進入就緒態,每個任務都得到了該信號量,當然這樣可能會導致致命的後果,因爲採用信號量就是爲了防止對資源的多重訪問。

OS_EVENT  *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
    BOOLEAN    tasks_waiting;
    OS_EVENT  *pevent_return;
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif

#if OS_ARG_CHK_EN > 0
    if (err == (INT8U *)0) {                               /* Validate 'err'                           */
        return (pevent);
    }
    if (pevent == (OS_EVENT *)0) {                         /* Validate 'pevent'                        */
        *err = OS_ERR_PEVENT_NULL;
        return (pevent);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* 是否是指向某一個用於信號量的一個函數             */
        *err = OS_ERR_EVENT_TYPE;
        return (pevent);
    }
    if (OSIntNesting > 0) {                                /* See if called from ISR ...               */
        *err = OS_ERR_DEL_ISR;                             /* ... can't DELETE from an ISR             */
        return (pevent);
    }
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0) {                         /* 看是否有任務在等待信號量  */
        tasks_waiting = OS_TRUE;                           /* 是的                                     */
    } else {
        tasks_waiting = OS_FALSE;                          /* No                                       */
    }
    switch (opt) {
        case OS_DEL_NO_PEND:                               /* Delete semaphore only if no task waiting */
             if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_SIZE > 1
                 pevent->OSEventName[0] = '?';             /* Unknown name                             */
                 pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
                 pevent->OSEventType    = OS_EVENT_TYPE_UNUSED;
                 pevent->OSEventPtr     = OSEventFreeList; /* Return Event Control Block to free list  */
                 pevent->OSEventCnt     = 0;
                 OSEventFreeList        = pevent;          /* Get next free event control block        */
                 OS_EXIT_CRITICAL();
                 *err                   = OS_NO_ERR;
                 pevent_return          = (OS_EVENT *)0;   /* Semaphore has been deleted               */
             } else {
                 OS_EXIT_CRITICAL();
                 *err                   = OS_ERR_TASK_WAITING;
                 pevent_return          = pevent;
             }
             break;

        case OS_DEL_ALWAYS:                                /* Always delete the semaphore              */
             while (pevent->OSEventGrp != 0) {             /* Ready ALL tasks waiting for semaphore    */
                 (void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
             }
#if OS_EVENT_NAME_SIZE > 1
             pevent->OSEventName[0] = '?';                 /* Unknown name                             */
             pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
             pevent->OSEventType    = OS_EVENT_TYPE_UNUSED;
             pevent->OSEventPtr     = OSEventFreeList;     /* Return Event Control Block to free list  */
             pevent->OSEventCnt     = 0;
             OSEventFreeList        = pevent;              /* Get next free event control block        */
             OS_EXIT_CRITICAL();
             if (tasks_waiting == OS_TRUE) {               /* Reschedule only if task(s) were waiting  */
                 OS_Sched();                               /* Find highest priority task ready to run  */
             }
             *err                   = OS_NO_ERR;
             pevent_return          = (OS_EVENT *)0;       /* Semaphore has been deleted               */
             break;

        default:
             OS_EXIT_CRITICAL();
             *err                   = OS_ERR_INVALID_OPT;
             pevent_return          = pevent;
             break;
    }
    return (pevent_return);
}
#endif

(3)等待一個信號量OSSEMPend()

等待一個信號量函數的源代碼分析:

PS:設置該函數中的超時時間,真正實現的函數是在OSTimeTICK()函數中逐個的遞減。這個地方要注意下,對每個任務的任務控制塊TCB中的OSTCBDly做遞減操作。

最後的參數:perr就是指向一個錯誤的標誌。

void  OSSemPend (OS_EVENT  *pevent,
                 INT32U     timeout,
                 INT8U     *perr)
{

#ifdef OS_SAFETY_CRITICAL
    if (perr == (INT8U *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* 判斷這個指針是否爲空指針                            */
        *perr = OS_ERR_PEVENT_NULL;
        return;
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* 如果這個事件控制塊的類型不是信號量的話                 */
        *perr = OS_ERR_EVENT_TYPE;
        return;
    }
    if (OSIntNesting > 0u) {                          /* 不在中斷中操作此函數  */
        *perr = OS_ERR_PEND_ISR;                      /* ... can't PEND from an ISR                    */
        return;
    }
    if (OSLockNesting > 0u) {                         /* See if called with scheduler locked ...       */
        *perr = OS_ERR_PEND_LOCKED;                   /* ... can't PEND when locked                    */
        return;
    }
    OS_ENTER_CRITICAL();
    if (pevent->OSEventCnt > 0u) {                    /* If sem. is positive, resource available ...   */
        pevent->OSEventCnt--;                         /* ... decrement semaphore only if positive.     */
        OS_EXIT_CRITICAL();
        *perr = OS_ERR_NONE;
        return;
    }
                                                      /* Otherwise, must wait until event occurs       */
    OSTCBCur->OSTCBStat     |= OS_STAT_SEM;           /* 資源沒有異常的話,那麼就把資源定義爲信號量 */
    OSTCBCur->OSTCBStatPend  = OS_STAT_PEND_OK;
    OSTCBCur->OSTCBDly       = timeout;               /* 設置信號量的超時時間 */
    OS_EventTaskWait(pevent);                         /* 使一個任務進入等待某事件發生狀態 */
    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                      */
             break;

        case OS_STAT_PEND_TO:
        default:
             OS_EventTaskRemove(OSTCBCur, pevent);
             *perr = OS_ERR_TIMEOUT;                  /* Indicate that we didn't get event 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                          */
#if (OS_EVENT_MULTI_EN > 0u)
    OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
    OS_EXIT_CRITICAL();
}

(4)發出一個信號量:OSSEMPOST()

當任務發出一個等待的信號量之後,那麼它必須接收到任務或者中斷服務子程序給他發出相應的信號量,那麼他才能回到就緒態,不然的話,就會不斷的存在在事件控制等待列表中。

OS_EventTaskRdy()函數把優先級最高的任務從等待任務列表中去除,並使它進入就緒態,然後,調用OSSched()任務調用函數,因爲已經獲得信號量的任務,並且檢車一下是否是最高的優先級的就緒態任務,如果是優先級最高的就緒態任務,這時就要進行任務切換,準備執行該就緒態任務。如果得到這個信號量的任務不是最高優先級的任務,退回到這個函數,並繼續執行。

INT8U  OSSemPost (OS_EVENT *pevent)
{

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        return (OS_ERR_PEVENT_NULL);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0u) {                   /* 查看是否是存在信號量 */
                                                      /* 檢查一下是否有任務在等待該信號量                */
        (void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
        OS_EXIT_CRITICAL();
        OS_Sched();                                   /* Find HPT ready to run                         */
        return (OS_ERR_NONE);
    }
    if (pevent->OSEventCnt < 65535u) {                /* Make sure semaphore will not overflow         */
        pevent->OSEventCnt++;                         /* Increment semaphore count to register event   */
        OS_EXIT_CRITICAL();
        return (OS_ERR_NONE);
    }
    OS_EXIT_CRITICAL();                               /* Semaphore value has reached its maximum       */
    return (OS_ERR_SEM_OVF);
}

(5)無等待的請求一個信號量OSSemAccept()

有些書籍或者博主稱爲查詢信號量。

解釋:當一個任務請求一個信號量的時候,如果該信號量暫停無效,也可以讓該任務簡單的返回,而不是進入休眠的狀態,這種情況下面的操作是由OSSEMAccept()函數完成的。

也就是別人家稱爲很牛逼的詞語:非阻塞函數

有我就響應,沒有那麼我就繼續執行。

這個函數十分的簡單:從該信號量的事件控制塊中取出當前的計數值,並檢查該信號量是否有效(計數值是否爲非0值)

如果信號量有效的話,則信號量的計數值減1,
如果信號量無效的話,那麼就直接返回。

INT16U  OSSemAccept (OS_EVENT *pevent)
{
    INT16U     cnt;

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        return (0u);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        return (0u);
    }
    OS_ENTER_CRITICAL();
    cnt = pevent->OSEventCnt;
    if (cnt > 0u) {                                   /* See if resource is available                  */
        pevent->OSEventCnt--;                         /* Yes, decrement semaphore and notify caller    */
    }
    OS_EXIT_CRITICAL();
    return (cnt);                                     /* Return semaphore count                        */
}

(6)查詢一個信號量的當前的狀態OSSemQuery()

這裏面使用的,內存拷貝的方法,不斷把源地址的內容,也就是源數據結構的成員,賦值到現在地址的成員中。

這段代碼可能使用到了很多高級應用,但是我覺得是不好的,因爲我們可以設置一個結構體,來對應事件控制塊的成員。

然後一一賦值,可讀性大大的提高了,做過開發的都知道,代碼又醜又長,可讀性沒有的話,維護的成本會大大的提高。我們不是追求省那麼一丁點的內存。而是後面的人維護起來比較爽。

INT8U  OSSemQuery (OS_EVENT     *pevent,
                   OS_SEM_DATA  *p_sem_data)
{
    INT8U       i;
    OS_PRIO    *psrc;
    OS_PRIO    *pdest;
#if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */
    OS_CPU_SR   cpu_sr = 0u;
#endif



#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                         /* Validate 'pevent'                        */
        return (OS_ERR_PEVENT_NULL);
    }
    if (p_sem_data == (OS_SEM_DATA *)0) {                  /* Validate 'p_sem_data'                    */
        return (OS_ERR_PDATA_NULL);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* Validate event block type                */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
    p_sem_data->OSEventGrp = pevent->OSEventGrp;           /* Copy message mailbox wait list           */
    psrc                   = &pevent->OSEventTbl[0];
    pdest                  = &p_sem_data->OSEventTbl[0];
    for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
        *pdest++ = *psrc++;
    }
    p_sem_data->OSCnt = pevent->OSEventCnt;                /* Get semaphore count                      */
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}
發佈了89 篇原創文章 · 獲贊 86 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章