【freertos】011-訊號量、互斥量及優先順序繼承機制原始碼分析

李柱明發表於2022-06-07


前言

原始碼實現主要參考訊息佇列章節,因為底層原始碼是一樣的,所以本章筆記側重點在訊號量、互斥量概念。
原始碼部分與訊息佇列重疊的函式不分析。

參考:李柱明部落格

11.1 任務同步

同步,執行完一個再到下一個,一條邏輯流。

非同步,執行者著這個的時候也可執行另外一個,不止一條互相獨立的邏輯流。

資源保護,控制資源的被訪問許可權。

在一個多工併發系統中,可能存在多工對共享資源的併發訪問,這樣可能會導致資料不可控、非預期。

所以我們需要對共享資源進行保護,而任務同步可以實現對共享資源訪問進行保護,維護資料一致性,產出結果達預期。

實現任務同步常見的元件有訊號量、互斥量、鎖等等。

同步和互斥的區別:

  • 同步:按序訪問,並非針對某個資源的保護,而是達到某個條件才繼續執行。
  • 互斥:指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排他性。但是互斥無法限制訪問者對資源的訪問順序,所以訪問是無序的。

11.2 訊號量概念

訊號量(Semaphore)也是實現任務間通訊的機制的一種。

可實現任務間同步、臨界資源的互斥訪問。

訊號量的核心其實就是一個非負值,表示當前資源數量:

  • 為0時,表示沒有資源,不能訪問。
  • 非0時,表示還有資源,可以訪問。

但是在freertos核心,一個訊號量除了核心的非負值外,還需要其他資料來維護當前訊號量的特性、運作。

如讀、寫阻塞連結串列,實現其阻塞機制,訊號量內部中斷鎖,實現其中斷訪問特性。

訊號量細分:

  • 二值訊號量。其資源最多隻有1個。
  • 計數訊號量。其資源最多不止1個。
  • 遞迴訊號量。同任務可重複獲取。獲取多少次就需要釋放多少次,這個任務才能真正釋放當前遞迴訊號量。

11.3 二值訊號量

11.3.1 二值訊號量概念

二值訊號量既可以用於臨界資源訪問也可以用於同步功能。

二值訊號量和互斥量其訊號量最大都是1,只有0和1兩種狀態,使用邏輯也類似,但是也有區別,主要在內部實現特性和上層應用場景方面:

  • 互斥量有優先順序繼承機制。(在互斥量章節詳細介紹)
  • 二值訊號量存在優先順序翻轉缺點。
  • 互斥量多用於資源保護。
  • 互斥量多用於任務同步。

11.3.2 優先順序翻轉

例子執行條件:

  • 建立3個任務Task1,Task2和Task3,優先順序分別為3,2,1。也就是Task1的優先順序最高。
  • 任務Task1和Task3互斥訪問串列埠列印printf,採用二值訊號實現互斥訪問。
  • 起初Task3通過二值訊號量正在呼叫printf,被任務Task1搶佔,開始執行任務Task1。

執行過程描述如下:

  • 任務Task1執行的過程需要呼叫函式printf,發現任務Task3正在呼叫,任務Task1會被掛起,等待Task3釋放函式printf。
  • 在排程器的作用下,任務Task3得到執行,Task3執行的過程中,由於任務Task2就緒,搶佔了Task3的執行。優先順序翻轉問題就出在這裡了,從任務執行的現象上看,任務Task1需要等待Task2執行完畢才有機會得到執行,這個與搶佔式排程正好反了,正常情況下應該是高優先順序任務搶佔低優先順序任務的執行,這裡成了高優先順序任務Task1等待低優先順序任務Task2完成。所以這種情況被稱之為優先順序翻轉問題。
  • 任務Task2執行完畢後,任務Task3恢復執行,Task3釋放互斥資源後,任務Task1得到互斥資源,從而可以繼續執行。

該圖源自安富萊

11.3.3 二值訊號量運作機制

建立訊號量:

  • 為其訊號量分為資源。
  • 初始化訊號量控制塊及其資源初始個數。

獲取訊號量:

  • 如果訊號量為1,獲取訊號量成功,當前訊號量改為0狀態,在釋放前,不能被獲取,當前任務可以繼續往下跑。
  • 如果訊號量為0,說明有任務已經佔用當前訊號量了,獲取失敗,要麼阻塞,要麼返回獲取失敗。

釋放訊號量:

  • 如果訊號量為1,說明當前訊號量沒有被其他任務佔用,直接呼叫釋放要麼阻塞semGIVE_BLOCK_TIME個節拍,要麼直接返回釋放失敗。
  • 如果訊號量為0,說明當前訊號量已經被佔用,通過釋放後,當前訊號量為1。

11.4 計數訊號量

11.4.1 計數訊號量概念

計數訊號量的最大資源大於1,主要用於計數。

獲取訊號量,訊號量值減1;釋放訊號量,訊號量值加1。

計數訊號通常用於兩種情況:

  1. 計算事件:

    1. 在事件發生時,獲取一個計數訊號量,計數訊號量的值減1。
    2. 當事件得到處理時,釋放一個計數訊號量,計數訊號量的值加1。
    3. 通過最大訊號量值和當前值得差值就知道當前還有多少個事件沒有被處理。
  2. 資源管理:

    1. 計數訊號量表示當前可用資源值。
    2. 獲取訊號量表示需要佔用一個資源,訊號量值減1。
    3. 當資源值為0時,表示沒有空閒的支援可用。
    4. 釋放訊號量表示資源不用了,當前訊號量加1.
      該圖片源自野火

11.4.2 計數訊號量運作

和二值訊號量類似,只是資源最大值不止1。

11.5 互斥量

11.5.1 互斥量概念

互斥量是包含優先順序繼承機制的二值訊號量。

而二值訊號量是實現同步(任務之間或任務與中斷之間)的更好選擇,互斥量是實現簡單互斥的更好選擇。

互斥量就像保護資源的令牌一樣。

當一個任務希望訪問該資源時,它必須首先獲得令牌。

當它使用完該資源時,它必須“歸還”令牌——允許其他任務訪問相同的資源。

互斥鎖不應該在中斷中使用,因為:

  1. 它們包括優先順序繼承機制,這種機制只在互斥鎖來自任務時才有意義,而不是在中斷時。
  2. 中斷不能阻塞以等待由互斥鎖保護的資源變為可用。

11.5.2 優先順序繼承機制概念

優先順序繼承:高優先順序任務TH在等待低優先順序的任務TL繼承佔用的競爭資源時,為了使TH能夠儘快獲得排程執行,由作業系統把TL的優先順序提高到TH的優先順序,從而讓TL以TH的優先順序參與排程,儘快讓TL執行並釋放調TH欲獲得的競爭資源,然後TL的優先順序調整到繼承前的水平,此時TH可獲得競爭資源而繼續執行。

在FreeRTOS作業系統中為了降低優先順序翻轉問題利用了優先順序繼承演算法。

不過優先順序繼承也不能解決優先順序反轉。

它只是在某些情況下將其影響降到最低。

舉個例子:

三個任務:任務A優先順序10,任務B優先順序5,任務C優先順序1。

在任務C佔用互斥量時,任務A就緒,也需要該互斥量,此時任務C的優先順序會繼承任務A的優先順序,從優先順序1躍升到優先順序10。就算當前任務B就緒了,也不能打斷任務C,因為優先順序比10底。

11.5.3 互斥量運作

和二值訊號量類似,比二值訊號量多個優先順序繼承機制。

11.6 遞迴互斥量

11.6.1 遞迴互斥量概念

就是互斥量具有遞迴性。

遞迴使用的互斥量可以被其所有者反覆“獲取”。

互斥量只有在所有者為每個成功的xSemaphoreTakeRecursive()請求呼叫xSemaphoreGiveRecursive()之後才會再次可用。

即是互斥量被同一個任務連續申請成功N次,就需要釋放N次才算真正釋放該互斥量。

互斥量型別的訊號量不能在中斷服務程式中使用。

因為:

  1. 互斥量包含了優先順序繼承機制,只有在互斥鎖來自任務而不是中斷時才有意義。
  2. 中斷不能阻塞以等待由互斥鎖保護的資源變為可用。

11.6.2 遞迴互斥量運作

參考互斥量運作機制,比互斥量多個遞迴性。

11.7 死鎖概念

就是邏輯上獲取一個已經被佔用且邏輯上不可能被釋放的鎖而阻塞,永久阻塞。

避免死鎖需要遵循的規則

  • 對共享資源操作前一定要獲得鎖;
  • 完成操作後一定要釋放鎖;
  • 儘量短時間佔用鎖;
  • 如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。

11.8 建立訊號量

11.8.1 建立二值訊號量

xSemaphoreCreateBinary()

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xSemaphoreCreateBinary()    xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

就是建立一個型別queueQUEUE_TYPE_BINARY_SEMAPHORE、是佇列成員為1、不含資料區的佇列。

11.8.2 建立計數訊號量

xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )    xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif

#if ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
    QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
                                                 const UBaseType_t uxInitialCount )
    {
        QueueHandle_t xHandle = NULL;

        if( ( uxMaxCount != 0 ) && /* 最大訊號量不能少於1,這是常識 */
            ( uxInitialCount <= uxMaxCount ) ) /* 初始值也不能超過最大訊號量值 */
        {
            /* 建立一個型別為queueQUEUE_TYPE_COUNTING_SEMAPHORE、佇列成員為uxMaxCount、且不含資料區的佇列 */
            xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
            if( xHandle != NULL )
            {
                /* 初始化當前可用資源值 */
                ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
                traceCREATE_COUNTING_SEMAPHORE();
            }
            else
            {
                traceCREATE_COUNTING_SEMAPHORE_FAILED();
            }
        }
        else
        {
            configASSERT( xHandle );
            mtCOVERAGE_TEST_MARKER();
        }

        return xHandle;
    }
#endif

11.8.3 建立互斥量

xSemaphoreCreateMutex()

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xSemaphoreCreateMutex()    xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif

#if ( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
    QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
    {
        QueueHandle_t xNewQueue;
        const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
        /* 建立互斥量,不含資料區的佇列 */
        xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
        /* 初始化互斥量, */
        prvInitialiseMutex( ( Queue_t * ) xNewQueue );

        return xNewQueue;
    }
#endif

#if ( configUSE_MUTEXES == 1 )
    static void prvInitialiseMutex( Queue_t * pxNewQueue )
    {
        if( pxNewQueue != NULL )
        {
            /* 在呼叫xQueueGenericCreate()建立佇列的時候,預設都是佇列,聯合體初始化的是QueuePointers_t xQueue。
                所以需要在這裡初始化回SemaphoreData_t xSemaphore,這個成員非常重要,是實現優先順序繼承機制和遞迴互斥量的必要資料。
                用於互斥量。 */
            /* 互斥量持有者。初始化為NULL */
            pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
            /* 型別標記為互斥量 */
            pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

            /* 遞迴互斥量使用,初始為0 */
            pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;

            traceCREATE_MUTEX( pxNewQueue );

            /* 建立後,預設為開鎖狀態 */
            ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
        }
        else
        {
            traceCREATE_MUTEX_FAILED();
        }
    }
#endif /* configUSE_MUTEXES */

11.8.4 建立遞迴互斥量

#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) )
    #define xSemaphoreCreateRecursiveMutex()    xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
#endif

11.8.5 訊號量控制塊資料結構圖

11.8.6 互斥量控制塊資料結構圖

11.9 獲取訊號量

獲取和釋放訊號量都是區分任務和中斷呼叫的API的,其主要區別也是中斷呼叫是不能阻塞。

這裡主要分析任務呼叫。(中斷呼叫,按區別理解下就可以了)

二值訊號量、計數訊號量、互斥量都是使用xSemaphoreTake()獲取訊號量。

而遞迴互斥量使用xSemaphoreTakeRecursive()獲取互斥量。

11.9.1 xSemaphoreTake()

#define xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
                                TickType_t xTicksToWait )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    #if ( configUSE_MUTEXES == 1 )
        BaseType_t xInheritanceOccurred = pdFALSE;
    #endif

    /* 引數校驗 */
    configASSERT( ( pxQueue ) );

    /* 訊號量是不帶資料區的 */
    configASSERT( pxQueue->uxItemSize == 0 );

    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            /* 排程器掛起是不能以阻塞式呼叫 */
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    /* 迴圈方式。是實現阻塞機制的邏輯方式 */
    for( ; ; )
    {
        taskENTER_CRITICAL(); /* 進入臨界 */
        {
            /* 備份下當前訊號量值 */
            const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;

            /* 訊號量大於0,說明還有資源,可以被獲取佔用 */
            if( uxSemaphoreCount > ( UBaseType_t ) 0 )
            {
                traceQUEUE_RECEIVE( pxQueue );
                /* 訊號量減1 */
                pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;

                #if ( configUSE_MUTEXES == 1 )
                    {
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) /* 互斥量型別 */
                        {
                            /* 儲存當前互斥量持有者。且持有者也儲存佔用互斥量數。 */
                            pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configUSE_MUTEXES */

                /* 如果有任務阻塞在當前訊號量的獲取阻塞連結串列中,就解鎖一個,讓其寫入。 */
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    /* 把這個解除阻塞的任務從當前佇列的寫阻塞連結串列中解除,
                        並把該任務從延時連結串列或掛起連結串列中恢復到就緒連結串列或掛起的就緒連結串列中 */
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        /* 解鎖的任務比當前任務優先順序更加高,需要觸發任務排程。 */
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                /* 退出臨界 */
                taskEXIT_CRITICAL();
                return pdPASS; /* 返回獲取成功 */
            }
            else /* 如果訊號量為空,沒有可用資源。互斥量的話需要處理優先順序繼承機制。 */
            {
                if( xTicksToWait == ( TickType_t ) 0 ) /* 不阻塞 */
                {
                    #if ( configUSE_MUTEXES == 1 )
                        {
                            /* 引數校驗。不阻塞是不會因當前獲取而發生優先順序繼承的。 */
                            configASSERT( xInheritanceOccurred == pdFALSE );
                        }
                    #endif /* configUSE_MUTEXES */
                    /* 退出臨界,返回獲取失敗。 */
                    taskEXIT_CRITICAL();
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE ) /* 首次迴圈,需要開始計時阻塞 */
                {
                    /* 獲取當前系統節拍 */
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE; /* 標記已經開始計時 */
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        taskEXIT_CRITICAL();

        /* 退出臨界後系統會先處理在臨界期觸發的被遮蔽的中斷服務,如任務切換的中斷服務、其它中斷服務等等。 */

        vTaskSuspendAll(); /* 又回到了當前任務。掛起排程器 */
        prvLockQueue( pxQueue ); /* 佇列上鎖 */

        /* 檢查阻塞是否已經超時。 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) /* 阻塞未超時 */
        {
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) /* 如果訊號量中還沒有資源,需要繼續阻塞 */
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
                #if ( configUSE_MUTEXES == 1 )
                    {
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) /* 互斥量型別 */
                        {
                            taskENTER_CRITICAL(); /* 進入臨界 */
                            {
                                /* 處理優先順序繼承 */
                                xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
                            }
                            taskEXIT_CRITICAL(); /* 退出臨界 */
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* if ( configUSE_MUTEXES == 1 ) */
                /* 當前任務進入阻塞,從就緒連結串列中抽離,插入到延時連結串列中,並把該任務插入當前訊號量的獲取阻塞連結串列中 */
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
                prvUnlockQueue( pxQueue ); /* 就鎖佇列 */

                if( xTaskResumeAll() == pdFALSE ) /* 恢復排程器 */
                {
                    /* 如果在恢復排程器時沒有排程過,這裡必須手動觸發一次排程。 */
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else /* 訊號量中有資源了 */
            {
                /* 需要解鎖當前訊號量並恢復排程器,進入下一個迴圈處理 */
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else /* 阻塞超時 */
        {
            /* 解鎖當前訊號量 */
            prvUnlockQueue( pxQueue );
            /* 恢復排程器 */
            ( void ) xTaskResumeAll();

            /* 再次判斷下是否真的沒有資源,現在有資源還來得及 */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )  /* 確實沒有資源,解除阻塞處理 */
            {
                #if ( configUSE_MUTEXES == 1 )
                    {
                        if( xInheritanceOccurred != pdFALSE ) /* 發生過優先順序繼承 */
                        {
                            taskENTER_CRITICAL(); /* 進入臨界 */
                            {
                                UBaseType_t uxHighestWaitingPriority;

                                /* 重置優先順序繼承 */
                                /* 先解除當前互斥量的優先順序繼承 */
                                uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
                                /* 再設定新的優先順序繼承 */
                                vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
                            }
                            taskEXIT_CRITICAL(); /* 退出臨界 */
                        }
                    }
                #endif /* configUSE_MUTEXES */
                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY; /* 返回獲取失敗 */
            }
            else /* 在超時了,退出前發現有資源,可以進入下一個迴圈獲取 */
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    } /*lint -restore */
}

11.9.2 xSemaphoreTakeRecursive()

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    #define xSemaphoreTakeRecursive( xMutex, xBlockTime )    xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )

    BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex,
                                         TickType_t xTicksToWait )
    {
        BaseType_t xReturn;
        Queue_t * const pxMutex = ( Queue_t * ) xMutex;
        /* 引數校驗 */
        configASSERT( pxMutex );

        traceTAKE_MUTEX_RECURSIVE( pxMutex );
    
        if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() ) /* 如果遞迴互斥量已經被佔用了,持有者是當前任務的話,可以獲取遞迴互斥量成功 */
        {
            /* 遞迴深度加1 */
            ( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
            xReturn = pdPASS; /* 返回獲取成功 */
        }
        else /* 遞迴互斥量沒有被佔用,或者遞迴互斥量已經被佔用,但是持有者不是當前任務 */
        {
            /* 獲取互斥量處理 */
            xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );

            if( xReturn != pdFAIL ) /* 獲取成功 */
            {
                 /* 遞迴深度加1 */
                ( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
            }
            else
            {
                traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
            }
        }

        return xReturn;
    }
#endif

11.10 釋放訊號量

獲取和釋放訊號量都是區分任務和中斷呼叫的API的,其主要區別也是中斷呼叫是不能阻塞。

這裡主要分析任務呼叫。(中斷呼叫,按區別理解下就可以了)

二值訊號量、計數訊號量、互斥量都是使用xSemaphoreGive()獲取訊號量。

而遞迴互斥量使用xSemaphoreGiveRecursive()獲取互斥量。

注意:互斥量和遞迴互斥量只有持有者才有許可權釋放。

11.10.1 xSemaphoreGive()

參考訊息佇列章節。

#define xSemaphoreGive( xSemaphore )    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

11.10.2 xSemaphoreGiveRecursive()

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    #define xSemaphoreGiveRecursive( xMutex )    xQueueGiveMutexRecursive( ( xMutex ) )
#endif

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
    {
        BaseType_t xReturn;
        Queue_t * const pxMutex = ( Queue_t * ) xMutex;
        /* 引數校驗 */
        configASSERT( pxMutex );

        if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() ) /* 如果遞迴互斥量已經被佔用了,持有者是當前任務的話,可以被釋放 */
        {
            traceGIVE_MUTEX_RECURSIVE( pxMutex );
            /* 遞迴深度建1 */
            ( pxMutex->u.xSemaphore.uxRecursiveCallCount )--;

            /* 遞迴深度為0,說明已經被完全釋放了 */
            if( pxMutex->u.xSemaphore.uxRecursiveCallCount == ( UBaseType_t ) 0 )
            {
                /* 需要真正釋放這個遞迴互斥量 */
                ( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
            /* 返回成功 */
            xReturn = pdPASS;
        }
        else /* 遞迴互斥量沒有被佔用,或者遞迴互斥量已經被佔用,但是持有者不是當前任務 */
        {
            /* 返回釋放失敗 */
            xReturn = pdFAIL;
            traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
        }
        return xReturn;
    }
#endif

11.11 刪除訊號量

vSemaphoreDelete()用於刪除一個訊號量,包括二值訊號量,計數訊號量,互斥量和遞迴互斥量。

#define vSemaphoreDelete( xSemaphore )                   vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )

11.12 優先順序繼承機制主要原始碼

注意:當持有者持有多個互斥量時,不能通過單個互斥量來解除或者重置優先順序繼承的優先順序,只能選擇忽略。這種情況也會存在優先順序翻轉。

優先順序繼承機制主要資料:Queue_t->u->xQueue

typedef struct SemaphoreData
{
    TaskHandle_t xMutexHolder;        /* 當前互斥量的持有者 */
    UBaseType_t uxRecursiveCallCount; /* 當前互斥量被遞迴呼叫的深度 */
} SemaphoreData_t;

11.12.1 優先順序繼承

在互斥量被其它任務佔用,當前高優先順序任務因為該互斥量而進入阻塞時,會發生優先繼承,互斥量持有者的任務優先順序會躍升到阻塞在當前接收阻塞連結串列中最高,且比持有者高的任務優先順序。

xTaskPriorityInherit()

#if ( configUSE_MUTEXES == 1 )
    BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
    {
        TCB_t * const pxMutexHolderTCB = pxMutexHolder;
        BaseType_t xReturn = pdFALSE;

        /* 互斥鎖已被佔用 */
        if( pxMutexHolder != NULL )
        {
            /* 持有者優先順序比當前任務優先順序低,需要更新優先順序繼承 */
            if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
            {
                /* 持有者的事件節點值沒有被其它IPC佔用(如事件組元件),方可設定為優先順序相關的值 */
                if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
                {
                    /* 重置持有者事件節點值,優先順序升級 */
                    listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                /* 如果持有者處於就緒態,則需要將其移到新的就緒連結串列中 */
                if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
                {
                    if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) /* 解除任務狀態 */
                    {
                        /* 更新任務優先順序點陣圖 */
                        portRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority, uxTopReadyPriority );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 優先順序繼承:更新優先順序 */
                    pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
                    /* 重新插入對應就緒連結串列 */
                    prvAddTaskToReadyList( pxMutexHolderTCB );
                }
                else /* 持有者不在就緒態 */
                {
                    /* 直接更新優先順序即可 */
                    pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
                }
                traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );
                /* 繼承成功 */
                xReturn = pdTRUE;
            }
            else /*  */
            {
                if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
                {
                    /* 持有者當前優先順序比當前任務高,但是持有者基優先順序比當前任務優先順序低,也是算是繼承成功過 */
                    xReturn = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        return xReturn;
    }
#endif /* configUSE_MUTEXES */

11.12.2 解除優先順序繼承

互斥量持有者真正釋放互斥量時,方可解除優先順序繼承

正常由持有者解除:xTaskPriorityDisinherit()

#if ( configUSE_MUTEXES == 1 )
    BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
    {
        TCB_t * const pxTCB = pxMutexHolder;
        BaseType_t xReturn = pdFALSE;

        if( pxMutexHolder != NULL )
        {
            /* 持有者才有許可權解除繼承 */
            configASSERT( pxTCB == pxCurrentTCB );
            /* 繼承過才能解除繼承 */
            configASSERT( pxTCB->uxMutexesHeld );
            ( pxTCB->uxMutexesHeld )--;

            /* 繼承判斷 */
            if( pxTCB->uxPriority != pxTCB->uxBasePriority ) /* 繼承過 */
            {
                /* 只有當持有者這個任務不再持有任何互斥量時,才能解除優先順序繼承。
                    因為只解除當前的互斥量,但是當前優先順序繼承可能繼承的是其它互斥量的,所以不能直接解除。 */
                if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ) /* 持有者不再佔有任何互斥量 */
                {
                    /* 解除任務狀態 */
                    if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
                    {
                        /* 更新優先順序點陣圖 */
                        portRESET_READY_PRIORITY( pxTCB->uxPriority, uxTopReadyPriority );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
                    /* 重置優先順序 */
                    pxTCB->uxPriority = pxTCB->uxBasePriority;

                    /* 重置任務事件值 */
                    listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );
                    /* 把任務重新新增到就緒連結串列中 */
                    prvAddTaskToReadyList( pxTCB );

                    /* 優先順序解除成功 */
                    xReturn = pdTRUE;
                }
            }
        }
        return xReturn;
    }
#endif /* configUSE_MUTEXES */

11.12.3 重置優先順序繼承

獲取互斥量阻塞阻塞超時時會檢查重置優先順序繼承。

高優先順序任務阻塞超時而解除:

  • 這種情況不是解除阻塞,而是重置阻塞。
  • 獲取獲取互斥量阻塞連結串列任務中的最高優先順序:prvGetDisinheritPriorityAfterTimeout()
  • 重置優先順序繼承:vTaskPriorityDisinheritAfterTimeout()

prvGetDisinheritPriorityAfterTimeout()

#if ( configUSE_MUTEXES == 1 )
    static UBaseType_t prvGetDisinheritPriorityAfterTimeout( const Queue_t * const pxQueue )
    {
        UBaseType_t uxHighestPriorityOfWaitingTasks;
        /* 當前互斥量中有任務阻塞在獲取互斥量阻塞連結串列中 */
        if( listCURRENT_LIST_LENGTH( &( pxQueue->xTasksWaitingToReceive ) ) > 0U )
        {
            /* 獲取獲取互斥量阻塞連結串列任務中的最高優先順序 */
            uxHighestPriorityOfWaitingTasks = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) listGET_ITEM_VALUE_OF_HEAD_ENTRY( &( pxQueue->xTasksWaitingToReceive ) );
        }
        else /* 當前互斥量沒有阻塞獲取的任務 */
        {
            uxHighestPriorityOfWaitingTasks = tskIDLE_PRIORITY; /* 最低 */
        }
        return uxHighestPriorityOfWaitingTasks;
    }
#endif /* configUSE_MUTEXES */

vTaskPriorityDisinheritAfterTimeout()

  • 只有持有者只持有當前互斥量才能重置優先順序繼承,因為如果持有者持有多個互斥量時,並不能只參考當前互斥量來重置優先順序。
#if ( configUSE_MUTEXES == 1 )

    void vTaskPriorityDisinheritAfterTimeout( TaskHandle_t const pxMutexHolder,
                                              UBaseType_t uxHighestPriorityWaitingTask )
    {
        TCB_t * const pxTCB = pxMutexHolder;
        UBaseType_t uxPriorityUsedOnEntry, uxPriorityToUse;
        const UBaseType_t uxOnlyOneMutexHeld = ( UBaseType_t ) 1;

        if( pxMutexHolder != NULL )
        {
            /* 引數校驗,持有者必須持有互斥量 */
            configASSERT( pxTCB->uxMutexesHeld );

            /* 當前互斥量持有者基優先順序低於當前互斥量中阻塞獲取連結串列任務中的最高優先順序 */
            if( pxTCB->uxBasePriority < uxHighestPriorityWaitingTask )
            {
                uxPriorityToUse = uxHighestPriorityWaitingTask; /* 下一個繼承的優先順序 */
            }
            else
            {
                uxPriorityToUse = pxTCB->uxBasePriority; /* 需要解除繼承 */
            }

            /* 目標優先順序和當前優先順序不一致,需要重置優先順序 */
            if( pxTCB->uxPriority != uxPriorityToUse )
            {
                /* 只有持有者只持有當前互斥量才能重置優先順序繼承,因為如果持有者持有多個互斥量時,並不能只參考當前互斥量來重置優先順序 */
                if( pxTCB->uxMutexesHeld == uxOnlyOneMutexHeld )
                {
                    /* 確保持有者不是當前任務。否則可能就是一個死鎖的邏輯。斷言得了 */
                    configASSERT( pxTCB != pxCurrentTCB );

                    traceTASK_PRIORITY_DISINHERIT( pxTCB, uxPriorityToUse );
                    /* 備份持有者優先順序 */
                    uxPriorityUsedOnEntry = pxTCB->uxPriority;
                    /* 更新持有者優先順序 */
                    pxTCB->uxPriority = uxPriorityToUse;

                    /* 持有者的事件節點值沒有被其它IPC佔用(如事件組元件),方可設定為優先順序相關的值 */
                    if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
                    {
                        /* 重置持有者事件節點值,優先順序更新 */
                        listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriorityToUse );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 如果持有者處於就緒態,就需要更新就緒連結串列 */
                    if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
                    {
                        if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) /* 解除任務狀態 */
                        {
                            /* 更新就緒任務點陣圖 */
                            portRESET_READY_PRIORITY( pxTCB->uxPriority, uxTopReadyPriority );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                        /* 重新插入就緒連結串列 */
                        prvAddTaskToReadyList( pxTCB );
                    }
                    else /* 不是就緒態就不用管 */
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                } 
                else /* 持有者持有多個互斥量,也不需要重置優先順序繼承 */
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
#endif /* configUSE_MUTEXES */

相關文章