1. 程式人生 > >uc/os-ii互斥型訊號量

uc/os-ii互斥型訊號量

互斥訊號量(Mutual Exclusion Semaphores)也稱為Mutex,用於實現對共享資源的獨佔處理,互斥訊號量也是一個二值訊號量,利用它可以降解優先順序反轉問題。
當高優先順序任務需要使用某個共享資源,而恰巧該共享資源又被一個低優先順序任務佔用時,優先順序反轉問題就會發生。為了降解優先順序反轉,核心就必須支援優先順序繼承,將低優先順序任務的優先順序提升到高於高優先順序任務的優先順序,直到低優先順序任務處理完畢共享資源。

  • 互斥訊號量只能提供給任務使用,因為互斥訊號量是用來處理共享資源的

  • µC/OS-Ⅱ的互斥訊號量由四個元素組成:1個標誌,指示Mutex是否可用(0xFF或其它);1個優先順序繼承優先順序PIP,一旦有高優先順序任務需要Mutex,核心就會將PIP賦給佔用Mutex的任務;1個指向佔有該Mutex的任務指標;1個Mutex等待任務列表。

  • µC/OS-Ⅱ提供了六種對互斥訊號量進行管理的函式,函式所屬檔案是OS_MUTEX.C。
    這裡寫圖片描述

  • OSMutexCreate()函式的主要工作過程是:首先進行執行條件檢查;其次檢查PIP優先順序是否被佔用;再次檢查是否有可用的事件控制塊;最後當上述條件都滿足後,進行Mutex的建立。此時,因為已經取走了一個空閒ECB,所以要調整空閒ECB連結串列指標,使其指向下一個空閒ECB。緊接著將ECB設定成Mutex型別,且儲存PIP並將Mutex設定為有效狀態。當等待任務列表初始化完畢後,返回ECB指標。其中,值得注意的是:這裡的事件計數器 OSEventCt與訊號量的不同,其高8位儲存PIP,低8位在沒有任務佔用時為0xFF,有任務佔用時,用於儲存佔用任務的優先順序。這樣做的目的是為了減少資料量,避免OS_EVENT資料結構的額外開銷。
    圖示為OSMutexCreate()函式返回前的ECB資料結構:
    這裡寫圖片描述

  • OSMutexDel()函式主要工作過程是:首先進行執行條件檢查,若不滿足,則返回空指標和錯誤程式碼;其次按刪除條件選擇引數opt進行刪除,刪除後還需要將Mutex的ECB指標交還給空閒ECB連結串列;如果佔有Mutex的任務被提升過優先順序,還需呼叫OSMutex_RdyAtPrio(ptcb, prio);恢復原來的優先順序。

OS_EVENT  *OSMutexDel (OS_EVENT *pevent, INT8U opt, INT8U *perr)
{
    BOOLEAN    tasks_waiting;
    OS_EVENT  *pevent_return;
    INT8U
pip; /* Priority inheritance priority */ INT8U prio; OS_TCB *ptcb; #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 (perr == (INT8U *)0) { /* Validate 'perr' */ return (pevent); } if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */ *perr = OS_ERR_PEVENT_NULL; return (pevent); } #endif if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type */ *perr = OS_ERR_EVENT_TYPE; return (pevent); } if (OSIntNesting > 0) { /* See if called from ISR ... */ *perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */ return (pevent); } OS_ENTER_CRITICAL(); if (pevent->OSEventGrp != 0) { /* See if any tasks waiting on mutex */ tasks_waiting = OS_TRUE; /* Yes */ } else { tasks_waiting = OS_FALSE; /* No */ } switch (opt) { case OS_DEL_NO_PEND: /* DELETE MUTEX 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 pip = (INT8U)(pevent->OSEventCnt >> 8); OSTCBPrioTbl[pip] = (OS_TCB *)0; /* Free up the PIP */ pevent->OSEventType = OS_EVENT_TYPE_UNUSED; pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */ pevent->OSEventCnt = 0; OSEventFreeList = pevent; OS_EXIT_CRITICAL(); *perr = OS_ERR_NONE; pevent_return = (OS_EVENT *)0; /* Mutex has been deleted */ } else { OS_EXIT_CRITICAL(); *perr = OS_ERR_TASK_WAITING; pevent_return = pevent; } break; case OS_DEL_ALWAYS: /* ALWAYS DELETE THE MUTEX ---------------- */ pip = (INT8U)(pevent->OSEventCnt >> 8); /* Get PIP of mutex */ prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); /* Get owner's original prio */ ptcb = (OS_TCB *)pevent->OSEventPtr; if (ptcb != (OS_TCB *)0) { /* See if any task owns the mutex */ if (ptcb->OSTCBPrio == pip) { /* See if original prio was changed */ OSMutex_RdyAtPrio(ptcb, prio); /* Yes, Restore the task's original prio */ } } while (pevent->OSEventGrp != 0) { /* Ready ALL tasks waiting for mutex */ (void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX, OS_STAT_PEND_OK); } #if OS_EVENT_NAME_SIZE > 1 pevent->OSEventName[0] = '?'; /* Unknown name */ pevent->OSEventName[1] = OS_ASCII_NUL; #endif pip = (INT8U)(pevent->OSEventCnt >> 8); OSTCBPrioTbl[pip] = (OS_TCB *)0; /* Free up the PIP */ 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 */ } *perr = OS_ERR_NONE; pevent_return = (OS_EVENT *)0; /* Mutex has been deleted */ break; default: OS_EXIT_CRITICAL(); *perr = OS_ERR_INVALID_OPT; pevent_return = pevent; break; } return (pevent_return); }
  • OSMutexPend()函式的主要工作過程如下:① 進行執行條件檢查;② 滿足執行條件後,首先檢查是否有Mutex可用,如果有可用的Mutex,則佔用這個Mutex,呼叫者獲得共享資源的使用權;③ 如果沒有可用的Mutex,則需要檢查Mutex的佔用者是否需要提升優先順序;④ 對需要提升優先順序的佔用者進行一系列處理;⑤ 將Mutex佔用者的優先順序改為PIP;⑥ 掛起呼叫者,並呼叫排程函式切換任務;⑦ 當呼叫者再次執行時,需要檢查呼叫者是因為超時期滿還是得到Mutex而執行的,並作相應處理。
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 OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
    if (perr == (INT8U *)0) {                              /* Validate 'perr'                          */
        return;
    }
    if (pevent == (OS_EVENT *)0) {                         /* Validate 'pevent'                        */
        *perr = OS_ERR_PEVENT_NULL;
        return;
    }
#endif
    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;
    }
/*$PAGE*/
    OS_ENTER_CRITICAL();
    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;
    }
    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;
        }
    }
    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                          */
#if (OS_EVENT_MULTI_EN > 0)
    OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
    OS_EXIT_CRITICAL();
}
  • OSMutexPost()函式用於釋放Mutex。當高優先順序任務想得到Mutex時,如果Mutex佔用者的優先順序已經被升高,那麼該函式使優先順序升高了的任務恢復原來的優先順序。如果有多個任務在等待一個Mutex,那麼其中優先順序最高的任務獲得Mutex。此後,該函式將呼叫排程函式,進行任務切換。如果沒有任務在等待這個Mutex,則將Mutex的值設為0xFF,表示有mutx可用。
INT8U  OSMutexPost (OS_EVENT *pevent)
{
    INT8U      pip;                                   /* Priority inheritance priority                 */
    INT8U      prio;
#if OS_CRITICAL_METHOD == 3                           /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr = 0;
#endif

    if (OSIntNesting > 0) {                           /* See if called from ISR ...                    */
        return (OS_ERR_POST_ISR);                     /* ... can't POST mutex from an ISR              */
    }
#if OS_ARG_CHK_EN > 0
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        return (OS_ERR_PEVENT_NULL);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type                     */
        return (OS_ERR_EVENT_TYPE);
    }
    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                           */
    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);
}