1. 程式人生 > >FreeRTOS(18)---FreeRTOS 訊號量分析

FreeRTOS(18)---FreeRTOS 訊號量分析

FreeRTOS 訊號量分析

FreeRTOS的訊號量包括二進位制訊號量、計數訊號量、互斥訊號量(以後簡稱互斥量)和遞迴互斥訊號量(以後簡稱遞迴互斥量)。關於它們的區別可以參考《 FreeRTOS系列第19篇—FreeRTOS訊號量》一文。

訊號量API函式實際上都是巨集,它使用現有的佇列機制。這些巨集定義在semphr.h檔案中。如果使用訊號量或者互斥量,需要包含semphr.h標頭檔案。

二進位制訊號量、計數訊號量和互斥量訊號量的建立API函式是獨立的,但是獲取和釋放API函式都是相同的;遞迴互斥訊號量的建立、獲取和釋放API函式都是獨立的。

訊號量建立

在《FreeRTOS高階篇5—FreeRTOS佇列分析》中,我們分析了佇列的實現過程,包括佇列建立、入隊和出隊操作。在那篇文章中我們說過,建立佇列API函式實際是呼叫通用佇列建立函式xQueueGenericCreate()來實現的。其實,不但建立佇列實際呼叫通用佇列建立函式,二進位制訊號量、計數訊號量、互斥量和遞迴互斥量也都直接或間接使用這個函式,如表1-1所示。表1-1中紅色字體表示是間接呼叫xQueueGenericCreate()函式。

表1-1:佇列、訊號量和互斥量建立巨集與直接(間接)執行函式 在這裡插入圖片描述

建立二進位制訊號量

二進位制訊號量建立實際上是直接使用通用佇列建立函式xQueueGenericCreate()。建立二進位制訊號量API介面實際上是一個巨集,定義如下:

#define xSemaphoreCreateBinary()         \
       xQueueGenericCreate(              \
                ( UBaseType_t ) 1,       \
                semSEMAPHORE_QUEUE_ITEM_LENGTH,  \
                NULL,              \
                NULL,              \
                queueQUEUE_TYPE_BINARY_SEMAPHORE\
                )

通過這個巨集定義我們知道建立二進位制訊號量實際上是建立了一個佇列,佇列項有1個,但是佇列項的大小為0(巨集semSEMAPHORE_QUEUE_ITEM_LENGTH定義為0)。

有了佇列建立的知識,我們可以很容易的畫出初始化後的二進位制訊號量記憶體,如圖1-1所示。 在這裡插入圖片描述 圖1-1:初始化後的二進位制訊號量物件記憶體

或許不止一人像我一樣奇怪,建立一個沒有佇列項儲存空間的佇列,訊號量用什麼表示?其實二進位制訊號量的釋放和獲取都是通過操作佇列結構體成員uxMessageWaiting來實現的(圖1-1紅色部分,uxMessageWaiting表示佇列中當前佇列項的個數)。經過初始化後,變數uxMessageWaiting為0,這說明佇列為空,也就是訊號量處於無效狀態。在使用API函式xSemaphoreTake()獲取訊號之前,需要先釋放一個訊號量。後面講到二進位制訊號量釋放和獲取時還會詳細介紹。

建立計數訊號量

建立計數訊號量間接使用通用佇列建立函式xQueueGenericCreate()。建立計數訊號量API介面同樣是個巨集定義:

#define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount )             \
       xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), (NULL ) )

建立計數訊號量API介面有兩個引數,含義如下:

  • uxMaxCount:最大計數值,當訊號到達這個值後,就不再增長了。
  • uxInitialCount:建立訊號量時的初始值。

我們來看一下函式xQueueCreateCountingSemaphore()如何實現的:

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_tuxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue )
{
QueueHandle_t xHandle;
 
    configASSERT( uxMaxCount != 0 );
    configASSERT( uxInitialCount <= uxMaxCount );
   
    /*呼叫通用佇列建立函式*/
    xHandle =xQueueGenericCreate(
          uxMaxCount,
          queueSEMAPHORE_QUEUE_ITEM_LENGTH,
          NULL,
          pxStaticQueue,
          queueQUEUE_TYPE_COUNTING_SEMAPHORE );
 
    if( xHandle != NULL )
    {
        ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
    }
    configASSERT( xHandle );
    return xHandle;
}

從程式碼可以看出,建立計數訊號量仍然呼叫通用佇列建立函式xQueueGenericCreate()來建立一個佇列,佇列項的數目由引數uxMaxCount指定,每個佇列項的大小由巨集queueSEMAPHORE_QUEUE_ITEM_LENGTH指出,我們找到這個巨集定義發現,這個巨集被定義為0,也就是說建立的佇列只有佇列資料結構儲存空間而沒有佇列項儲存空間。

如果佇列建立成功,則將佇列結構體成員uxMessageWaiting設定為初始計數訊號量值。初始化後的計數訊號量記憶體如圖1-2所示。 在這裡插入圖片描述 圖1-2:初始化後的計數訊號量物件記憶體

建立互斥量

建立互斥量間接使用通用佇列建立函式xQueueGenericCreate()。建立互斥量API介面同樣是個巨集,定義如下:

#define xSemaphoreCreateMutex()             \
          xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, NULL )

其中,巨集queueQUEUE_TYPE_MUTEX用於通用佇列建立函式,表示建立佇列的型別是互斥量,在文章《FreeRTOS高階篇5—FreeRTOS佇列分析》關於通用佇列建立函式引數說明中提到了這個巨集。

我們來看一下函式xQueueCreateMutex()是如何實現的:

#if ( configUSE_MUTEXES == 1 )
    QueueHandle_t xQueueCreateMutex( const uint8_tucQueueType, StaticQueue_t *pxStaticQueue )
    {
    Queue_t *pxNewQueue;
    const UBaseType_tuxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
 
        /* 防止編譯器產生警告資訊 */
        ( void ) ucQueueType;
       
        /*呼叫通用佇列建立函式*/
        pxNewQueue = ( Queue_t * )xQueueGenericCreate( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );
 
        /* 成功分配新的佇列結構體? */
        if( pxNewQueue != NULL )
        {
            /*xQueueGenericCreate()函式會按照通用佇列的方式設定所有佇列結構體成員,但是我們是要建立互斥量.因此需要對一些結構體成員重新賦值. */
            pxNewQueue->pxMutexHolder = NULL;
          
            pxNewQueue->uxQueueType =queueQUEUE_IS_MUTEX;  //NULL
 
            /* 用於遞迴互斥量建立 */
            pxNewQueue->u.uxRecursiveCallCount = 0;
 
            /* 使用一個預期狀態啟動訊號量 */
            ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK);
        }
 
        return pxNewQueue;
    }
#endif /* configUSE_MUTEXES */

這個函式是帶條件編譯的,只有將巨集configUSE_MUTEXES定義為1才會編譯這個函式。

函式首先呼叫通用佇列建立函式xQueueGenericCreate()來建立一個佇列,佇列項數目為1,佇列項大小為0,說明建立的佇列只有佇列資料結構儲存空間而沒有佇列項儲存空間。

如果佇列建立成功,通用佇列建立函式還會按照通用佇列的方式 初始化所有佇列結構體成員。但是這裡要建立的是互斥量,所以有一些結構體成員必須重新賦值。在這段程式碼中,可能你會疑惑,佇列結構體成員中,並沒有pxMutexHolder和uxQueueType!其實這兩個識別符號只是巨集定義,是專門為互斥量而定義的,如下所示:

#define pxMutexHolder                                     pcTail
#define uxQueueType                                       pcHead
#define queueQUEUE_IS_MUTEX                               NULL

當佇列結構體用於互斥量時,成員pcHead和pcTail指標就不再需要,並且將pcHead指標設定為NULL,表示pcTail指標實際指向互斥量持有者任務TCB(如果有的話)。

最後呼叫函式xQueueGenericSend()釋放一個互斥量,相當於互斥量建立後是有效的,可以直接使用獲取訊號量API函式來獲取這個互斥量。如果某資源同時只准一個任務訪問,可以用互斥量保護這個資源。這個資源一定是存在的,所以建立互斥量時會先釋放一個互斥量,表示這個資源可以使用。任務想訪問資源時,先獲取互斥量,等使用完資源後,再釋放它。也就是說互斥量一旦建立好後,要先獲取,後釋放,要在同一個任務中獲取和釋放。這也是互斥量和二進位制訊號量的一個重要區別,二進位制訊號量可以在隨便一個任務中獲取或釋放,然後也可以在任意一個任務中釋放或獲取。互斥量不同於二進位制訊號量的還有:互斥量具有優先順序繼承機制,二進位制訊號量沒有,互斥量不可以用於中斷服務程式,二進位制訊號量可以。

初始化後的互斥量記憶體如圖1-3所示。 在這裡插入圖片描述 圖1-3:初始化後的互斥量物件記憶體

建立遞迴互斥量

建立遞迴互斥量間接使用通用佇列建立函式xQueueGenericCreate()。建立遞迴互斥量API介面同樣是個巨集,定義如下:

#definexSemaphoreCreateRecursiveMutex()                 \
          xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )

其中,巨集queueQUEUE_TYPE_RECURSIVE_MUTEX用於通用佇列建立函式,表示建立佇列的型別是遞迴互斥量,在文章《FreeRTOS高階篇5—FreeRTOS佇列分析》關於通用佇列建立函式引數說明中提到了這個巨集。

建立互斥量和建立遞迴互斥量是呼叫的同一個函式xQueueCreateMutex(),至於引數queueQUEUE_TYPE_RECURSIVE_MUTEX,我們在FreeRTOS一文中已經知道,它只是用於視覺化除錯,因此建立互斥量和建立遞迴互斥量可以看作是一樣的,初始化後的遞迴互斥量物件記憶體也和互斥量一樣,如圖1-3所示。

釋放訊號量

無論二進位制訊號量、計數訊號量還是互斥量,它們都使用相同的獲取和釋放API函式。釋放訊號量用於使訊號量有效,分為不帶中斷保護和帶中斷保護兩個版本。

xSemaphoreGive()

用於釋放一個訊號量,不帶中斷保護。被釋放的訊號量可以是二進位制訊號量、計數訊號量和互斥量。注意遞迴互斥量並不能使用這個API函式釋放。其實訊號量釋放是一個巨集,真正呼叫的函式是xQueueGenericSend(),巨集定義如下:

#definexSemaphoreGive( xSemaphore )                    \
              xQueueGenericSend(                       \
                     ( QueueHandle_t ) ( xSemaphore ), \
                     NULL,                \
                     semGIVE_BLOCK_TIME,  \
                     queueSEND_TO_BACK )

可以看出釋放訊號量實際上是一次入隊操作,並且阻塞時間為0(由巨集semGIVE_BLOCK_TIME定義)。

對於二進位制訊號量和計數訊號量,根據上一章的內容可以總結出,釋放一個訊號量的過程實際上可以簡化為兩種情況:第一,如果佇列未滿,佇列結構體成員uxMessageWaiting加1,判斷是否有阻塞的任務,有的話解除阻塞,然後返回成功資訊(pdPASS);第二,如果佇列滿,返回錯誤程式碼(err_QUEUE_FULL),表示佇列滿。

對於互斥量要複雜些,因為互斥量具有優先順序繼承機制。

優先順序繼承是個什麼過程呢?我們舉個例子。某個資源X同時只能有一個任務訪問,現在有任務A和任務C都要訪問這個資源,任務A的優先順序為1,任務C的優先順序為10,所以任務C的優先順序大於任務A的優先順序。我們用互斥量保護資源X,並且當前任務A正在訪問資源X。在任務A訪問資源X的過程中,來了一箇中斷,中斷事件使得任務C執行。任務C執行的過程中,也想訪問資源X,但是因為資源X還被任務A獨佔著,所以任務C無法獲取互斥量,會進入阻塞狀態。此時,低優先順序任務A會繼承高優先順序任務C的優先順序,任務A的優先順序臨時的被提升,優先順序變成10。這個機制能夠將已經發生的優先順序反轉影響降低到最小。

那麼什麼是優先順序反轉呢?還是看上面的例子,任務C的優先順序高於任務A,但是任務C因為沒有獲得互斥量而進入阻塞,只能等待低優先順序的任務A釋放互斥量後才能執行,這種情況就是優先順序反轉。

那為什麼優先順序繼承可以降低優先順序反轉的影響呢?還是看上面的例子,不過我們再增加一個優先順序為5的任務B,這三個任務都處於就緒狀態。如果沒有優先順序繼承機制,三個任務的優先順序順序為任務C>任務B>任務A。當任務C因為得不到互斥量而阻塞後,任務B會獲取CPU許可權,等到任務B主動或被動讓出CPU後,任務A才會執行,任務A釋放互斥量後,任務C才能得到執行。再看一下有優先順序繼承的情況,當任務C因為得不到互斥量而阻塞後,任務A繼承任務C的優先順序,現在三個任務的優先順序順序為任務C=任務A>任務B。當任務C因為得不到互斥量而阻塞後,任務A會獲得CPU許可權,等到任務A釋放互斥量後,任務C就會得到執行。看,任務C等待的時間變短了。

有了上面的基礎理論,我們就很好理解為什麼釋放互斥量會比較複雜了。還是可以簡化為兩種情況:第一,如果佇列未滿,除了佇列結構體成員uxMessageWaiting加1外,還要判斷獲取互斥量的任務是否有優先順序繼承,如果有的話,還要將任務的優先順序恢復到原始值。當然,恢復到原來值也是有條件的,就是該任務必須在沒有使用其它互斥量的情況下,才能將繼承的優先順序恢復到原始值。然後判斷是否有阻塞的任務,有的話解除阻塞,最後返回成功資訊(pdPASS);第二,如果如果佇列滿,返回錯誤程式碼(err_QUEUE_FULL),表示佇列滿。

xSemaphoreGiveFromISR()

用於釋放一個訊號量,帶中斷保護。被釋放的訊號量可以是二進位制訊號量和計數訊號量。和普通版本的釋放訊號量API函式不同,它不能釋放互斥量,這是因為互斥量不可以在中斷中使用!互斥量的優先順序繼承機制只能在任務中起作用,在中斷中毫無意義。帶中斷保護的訊號量釋放其實也是一個巨集,真正呼叫的函式是xQueueGiveFromISR (),巨集定義如下:

#definexSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )     \
            xQueueGiveFromISR(                     \
                ( QueueHandle_t ) ( xSemaphore),  \
                ( pxHigherPriorityTaskWoken ) )

我們看真正被呼叫的函式原始碼(經過整理後的):

BaseType_t xQueueGiveFromISR(
            QueueHandle_t xQueue,
            BaseType_t * constpxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
    uxSavedInterruptStatus =portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /*當佇列用於實現訊號量時,永遠不會有資料出入佇列,但是任然要檢查佇列是否為空 */
        if( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
        {
            /* 一個任務可以獲取多個互斥量,但是隻能有一個繼承優先順序,如果任務是互斥量的持有者,則互斥量不允許在中斷服務程式中釋放.因此這裡不需要判斷是否要恢復任務的原始優先順序值,只是簡單更新佇列項計數器. */
            ++( pxQueue->uxMessagesWaiting );
 
            /* 如果列表上鎖,不能改變佇列的事件列表. */
            if( pxQueue->xTxLock == queueUNLOCKED )
            {
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive) ) == pdFALSE )
                {
                    if(xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive) ) != pdFALSE )
                    {
                        /* 解除阻塞的任務有更高優先順序,因此記錄上下文切換請求*/
                        if(pxHigherPriorityTaskWoken != NULL )
                        {
                            *pxHigherPriorityTaskWoken= pdTRUE;
                        }
                    }
                }
            }
            else
            {
                /* Increment thelock count so the task that unlocks the queue
                knows that data wasposted while it was locked. */
                ++( pxQueue->xTxLock );
            }
 
            xReturn = pdPASS;
        }
        else
        {
            xReturn = errQUEUE_FULL;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus );
 
    return xReturn;
}

因為不涉及互斥量,不涉及阻塞,函式xQueueGiveFromISR()異常簡單,如果佇列滿,直接返回錯誤程式碼(err_QUEUE_FULL);如果佇列未滿,則將佇列結構體成員uxMessageWaiting加1,然後視佇列是否上鎖而決定是否解除任務阻塞(如果有得話)。如果你覺得難以理解,則需要先看看《FreeRTOS高階篇5—FreeRTOS佇列分析》。

獲取訊號量

無論二進位制訊號量、計數訊號量還是互斥量,它們都使用相同的獲取和釋放API函式。釋獲取訊號量會消耗訊號量,如果獲取訊號量失敗,任務可能會阻塞,阻塞時間由函式引數xBlockTime指定,如果為0,則直接返回,不阻塞。獲取訊號量分為不帶中斷保護和帶中斷保護兩個版本。

xSemaphoreTake

用於獲取訊號量,不帶中斷保護。獲取的訊號量可以是二進位制訊號量、計數訊號量和互斥量。注意遞迴互斥量並不能使用這個API函式獲取。其實獲取訊號量是一個巨集,真正呼叫的函式是xQueueGenericReceive (),巨集定義如下:

#definexSemaphoreTake( xSemaphore, xBlockTime )        \
              xQueueGenericReceive(                    \
             ( QueueHandle_t ) ( xSemaphore ),         \
              NULL,                                    \
             ( xBlockTime ),                           \
              pdFALSE )

通過上面的巨集定義可以看出,獲取訊號量實際上是執行出隊操作。

對於二進位制訊號量和計數訊號量,可以簡化為三種情況:第一,如果佇列不為空,佇列結構體成員uxMessageWaiting減1,判斷是否有因入隊而阻塞的任務,有的話解除阻塞,然後返回成功資訊(pdPASS);第二,如果佇列為空並且阻塞時間為0,則直接返回錯誤碼(errQUEUE_EMPTY),表示佇列為空;第三,如果佇列為空並且阻塞時間不為0,則任務會因為等待訊號量而進入阻塞狀態,任務會被掛接到延時列表中。

對於互斥量,也可以簡化為三種情況,但是過程要複雜一些:第一,如果佇列不為空,佇列結構體成員uxMessageWaiting減1、將當前任務TCB結構體成員uxMutexesHeld加1,表示任務獲取互斥量的個數、將佇列結構體成員指標pxMutexHolder指向任務TCB、判斷是否有因入隊而阻塞的任務,有的話解除阻塞,然後返回成功資訊(pdPASS);第二,如果佇列為空並且阻塞時間為0,則直接返回錯誤碼(errQUEUE_EMPTY),表示佇列為空;第三,如果佇列為空並且阻塞時間不為0,則任務會因為等待訊號量而進入阻塞狀態,在將任務掛接到延時列表之前,會判斷當前任務和擁有互斥量的任務優先順序哪個高,如果當前任務優先順序高,則擁有互斥量的任務繼承當前任務優先順序。

xSemaphoreTakeFromISR()

用於獲取訊號量,帶中斷保護。獲取的訊號量可以是二進位制訊號量和計數訊號量。和普通版本的獲取訊號量API函式不同,它不能獲取互斥量,這是因為互斥量不可以在中斷中使用!互斥量的優先順序繼承機制只能在任務中起作用,在中斷中毫無意義。帶中斷保護的獲取訊號量其實也是一個巨集,真正呼叫的函式是xQueueReceiveFromISR (),巨集定義如下:

#definexSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )   \
              xQueueReceiveFromISR(              \
             ( QueueHandle_t ) ( xSemaphore ),   \
              NULL,                              \
             ( pxHigherPriorityTaskWoken ) )

同樣因為不涉及互斥量,不涉及阻塞,函式xQueueReceiveFromISR ()同樣異常簡單:如果佇列為空,直接返回錯誤程式碼(pdFAIL);如果佇列非空,則將佇列結構體成員uxMessageWaiting減1,然後視佇列是否上鎖而決定是否解除任務阻塞(如果有得話)。

釋放遞迴互斥量

函式xSemaphoreGiveRecursive()用於釋放一個遞迴互斥量。已經獲取遞迴互斥量的任務可以重複獲取該遞迴互斥量。使用xSemaphoreTakeRecursive() 函式成功獲取幾次遞迴互斥量,就要使用xSemaphoreGiveRecursive()函式返還幾次,在此之前遞迴互斥量都處於無效狀態。比如,某個任務成功獲取5次遞迴互斥量,那麼在它沒有返還5次該遞迴互斥量之前,這個互斥量對別的任務無效。

像其它訊號量一樣,xSemaphoreGiveRecursive()也是一個巨集定義,它最終使用現有的佇列機制,實際執行的函式是xQueueGiveMutexRecursive(),這個巨集定義如下所示:

#definexSemaphoreGiveRecursive( xMutex )            \
                xQueueGiveMutexRecursive( (xMutex ) )     

我們重點來看函式xQueueGiveMutexRecursive()的實現過程。經過整理後(去除跟蹤除錯語句)的原始碼如下所示:

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    BaseType_txQueueGiveMutexRecursive( QueueHandle_t xMutex )
    {
    BaseType_t xReturn;
    Queue_t * const pxMutex = ( Queue_t * ) xMutex;
 
        /* 互斥量和遞迴互斥量要在同一個任務中獲取和釋放,當獲取互斥量或遞迴互斥量時,佇列結構體成員指標pxMutexHolder指向獲取互斥量或遞迴互斥量的任務TCB,所以在釋放遞迴互斥量時需要檢查這個指標指向的TCB是否是和當前任務TCB相同,如果不相同是不能釋放這個遞迴互斥量的! 注:釋放互斥量時,這個檢查不是必須的,FreeRTOS的作者將這個檢查放在了斷言中(configASSERT( pxTCB == pxCurrentTCB);).*/
        if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
        {
            /* 每當任務獲取遞迴互斥量時,佇列結構體成員u.uxRecursiveCallCount會加1,互斥量不會使用這個變數,它用來儲存遞迴次數.所以,在釋放遞迴互斥量的時候要將它減1*/
            ( pxMutex->u.uxRecursiveCallCount)--;
 
            /* 遞迴計數器為0? */
            if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
            {
                /* 呼叫入隊函式釋放一個互斥量,注意阻塞時間(由巨集queueMUTEX_GIVE_BLOCK_TIME定義)為0 */
                ( void ) xQueueGenericSend( pxMutex, NULL,queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
            }
 
            xReturn = pdPASS;
        }
        else
        {
            /* 如果不是本任務擁有這個互斥量,則直接返回錯誤碼 */
            xReturn = pdFAIL;
        }
 
        return xReturn;
    }
#endif /* configUSE_RECURSIVE_MUTEXES */

這個函式是帶條件編譯的,只有將巨集configUSE_RECURSIVE_MUTEXES定義為1才會編譯這個函式。

互斥量和遞迴互斥量的最大區別在於一個遞迴互斥量可以被已經獲取這個遞迴互斥量的任務重複獲取,這個遞迴呼叫功能是通過佇列結構體成員u.uxRecursiveCallCount實現的。這個變數用於儲存遞迴呼叫的次數,每次獲取遞迴互斥量後,這個變數加1,在釋放遞迴互斥量後,這個變數減1。只有這個變數減到0,即釋放和獲取的次數相等時,互斥量才能再次有效,使用入隊函式釋放一個遞迴互斥量。

獲取遞迴互斥量

函式xSemaphoreTakeRecursive()用於獲取一個遞迴互斥量。像其它訊號量一樣,xSemaphoreTakeRecursive()也是一個巨集定義,它最終使用現有的佇列機制,實際執行的函式是xQueueTakeMutexRecursive(),這個巨集定義如下所示:

#definexSemaphoreTakeRecursive( xMutex, xBlockTime )       \
               xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
        獲取遞迴互斥量具有阻塞超時引數,如果互斥量正被別的任務使用,可以阻塞設定的時間。我們重點來看函式xQueueTakeMutexRecursive()的實現過程。經過整理後(去除跟蹤除錯語句)的原始碼如下所示:

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    BaseType_txQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_txTicksToWait )
    {
    BaseType_t xReturn;
    Queue_t * const pxMutex = ( Queue_t * ) xMutex;
    /*互斥量和遞迴互斥量要在同一個任務中獲取和釋放,遞迴互斥量可以在一個任務中多次獲取,當第一次獲取遞迴互斥量時,佇列結構體成員指標pxMutexHolder指向獲取遞迴互斥量的任務TCB,在此獲取這個遞迴互斥量時,如果這個指標指向的TCB和當前任務TCB相同,只需要將遞迴次數計數器u.uxRecursiveCallCount加1即可,不需要再操作佇列.*/
        if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
        {
            ( pxMutex->u.uxRecursiveCallCount)++;
            xReturn = pdPASS;
        }
        else
        {
            /*調用出隊函式*/
            xReturn =xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
 
            /* 成功獲取遞迴互斥量後,要將遞迴次數計數器加1*/
            if( xReturn != pdFAIL )
            {
                ( pxMutex->u.uxRecursiveCallCount)++;
            }
        }
 
        return xReturn;
    }
#endif /* configUSE_RECURSIVE_MUTEXES */

這個函式是帶條件編譯的,只有將巨集configUSE_RECURSIVE_MUTEXES定義為1才會編譯這個函式。

程式邏輯比較簡單,如果是第一次獲取這個遞迴互斥量,直接使用出隊函式,成功後將遞迴次數計數器加1;如果是第二次或者更多次獲取這個遞迴互斥量,則只需要將遞迴次數計數器加1即可。