<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          FreeRTOS系列第24篇---FreeRTOS信號量分析

          共 24792字,需瀏覽 50分鐘

           ·

          2021-07-04 09:56



          關(guān)注、星標(biāo)公眾號,直達精彩內(nèi)容

          ID:技術(shù)讓夢想更偉大

          整理:李肖遙


          FreeRTOS的信號量包括二進制信號量、計數(shù)信號量、互斥信號量(以后簡稱互斥量)和遞歸互斥信號量(以后簡稱遞歸互斥量)。

          關(guān)于它們的區(qū)別可以參考《 FreeRTOS系列第19篇---FreeRTOS信號量》一文。

          信號量API函數(shù)實際上都是宏,它使用現(xiàn)有的隊列機制。這些宏定義在semphr.h文件中。如果使用信號量或者互斥量,需要包含semphr.h頭文件。

          二進制信號量、計數(shù)信號量和互斥量信號量的創(chuàng)建API函數(shù)是獨立的,但是獲取和釋放API函數(shù)都是相同的;

          遞歸互斥信號量的創(chuàng)建、獲取和釋放API函數(shù)都是獨立的。

          1.信號量創(chuàng)建

          在《FreeRTOS高級篇5---FreeRTOS隊列分析》中,我們分析了隊列的實現(xiàn)過程,包括隊列創(chuàng)建、入隊和出隊操作。

          在那篇文章中我們說過,創(chuàng)建隊列API函數(shù)實際是調(diào)用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()來實現(xiàn)的。

          其實,不但創(chuàng)建隊列實際調(diào)用通用隊列創(chuàng)建函數(shù),二進制信號量、計數(shù)信號量、互斥量和遞歸互斥量也都直接或間接使用這個函數(shù),如表1-1所示。

          表1-1中紅色字體表示是間接調(diào)用xQueueGenericCreate()函數(shù)。

          表1-1:隊列、信號量和互斥量創(chuàng)建宏與直接(間接)執(zhí)行函數(shù)

          1.1.創(chuàng)建二進制信號量

          二進制信號量創(chuàng)建實際上是直接使用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建二進制信號量API接口實際上是一個宏,定義如下:

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

          通過這個宏定義我們知道創(chuàng)建二進制信號量實際上是創(chuàng)建了一個隊列,隊列項有1個,但是隊列項的大小為0(宏semSEMAPHORE_QUEUE_ITEM_LENGTH定義為0)。

          有了隊列創(chuàng)建的知識,我們可以很容易的畫出初始化后的二進制信號量內(nèi)存,如圖1-1所示。

          圖1-1:初始化后的二進制信號量對象內(nèi)存

          或許不止一人像我一樣奇怪,創(chuàng)建一個沒有隊列項存儲空間的隊列,「信號量用什么表示?」

          其實二進制信號量的釋放和獲取都是通過操作隊列結(jié)構(gòu)體成員uxMessageWaiting來實現(xiàn)的(圖1-1紅色部分,uxMessageWaiting表示隊列中當(dāng)前隊列項的個數(shù))。

          經(jīng)過初始化后,變量uxMessageWaiting為0,這說明隊列為空,也就是信號量處于無效狀態(tài)。

          在使用API函數(shù)xSemaphoreTake()獲取信號之前,需要先釋放一個信號量。后面講到二進制信號量釋放和獲取時還會詳細介紹。

          1.2.創(chuàng)建計數(shù)信號量

          創(chuàng)建計數(shù)信號量間接使用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建計數(shù)信號量API接口同樣是個宏定義:

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

          創(chuàng)建計數(shù)信號量API接口有兩個參數(shù),含義如下:

          • 「uxMaxCount」:最大計數(shù)值,當(dāng)信號到達這個值后,就不再增長了。

          • 「uxInitialCount」:創(chuàng)建信號量時的初始值。

          我們來看一下函數(shù)xQueueCreateCountingSemaphore()如何實現(xiàn)的:

          QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_tuxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue )
          {
          QueueHandle_t xHandle;
           
            configASSERT( uxMaxCount != 0 );
            configASSERT( uxInitialCount <= uxMaxCount );

            /*調(diào)用通用隊列創(chuàng)建函數(shù)*/
            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;
          }

          從代碼可以看出,創(chuàng)建計數(shù)信號量仍然調(diào)用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()來創(chuàng)建一個隊列,隊列項的數(shù)目由參數(shù)uxMaxCount指定,每個隊列項的大小由宏queueSEMAPHORE_QUEUE_ITEM_LENGTH指出,我們找到這個宏定義發(fā)現(xiàn),這個宏被定義為0,也就是說創(chuàng)建的隊列只有隊列數(shù)據(jù)結(jié)構(gòu)存儲空間而沒有隊列項存儲空間。

          如果隊列創(chuàng)建成功,則將隊列結(jié)構(gòu)體成員uxMessageWaiting設(shè)置為初始計數(shù)信號量值。初始化后的計數(shù)信號量內(nèi)存如圖1-2所示。

          圖1-2:初始化后的計數(shù)信號量對象內(nèi)存

          1.3創(chuàng)建互斥量

          創(chuàng)建互斥量間接使用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建互斥量API接口同樣是個宏,定義如下:

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

          其中,宏queueQUEUE_TYPE_MUTEX用于通用隊列創(chuàng)建函數(shù),表示創(chuàng)建隊列的類型是互斥量,在文章《FreeRTOS高級篇5---FreeRTOS隊列分析》關(guān)于通用隊列創(chuàng)建函數(shù)參數(shù)說明中提到了這個宏。

          我們來看一下函數(shù)xQueueCreateMutex()是如何實現(xiàn)的:

          #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;
           
                  /* 防止編譯器產(chǎn)生警告信息 */
                  ( void ) ucQueueType;
                 
                  /*調(diào)用通用隊列創(chuàng)建函數(shù)*/
                  pxNewQueue = ( Queue_t * )xQueueGenericCreate( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );
           
                  /* 成功分配新的隊列結(jié)構(gòu)體? */
                  if( pxNewQueue != NULL )
                  {
                      /*xQueueGenericCreate()函數(shù)會按照通用隊列的方式設(shè)置所有隊列結(jié)構(gòu)體成員,但是我們是要創(chuàng)建互斥量.因此需要對一些結(jié)構(gòu)體成員重新賦值. */
                      pxNewQueue->pxMutexHolder = NULL;
                    
                      pxNewQueue->uxQueueType =queueQUEUE_IS_MUTEX;  //NULL
           
                      /* 用于遞歸互斥量創(chuàng)建 */
                      pxNewQueue->u.uxRecursiveCallCount = 0;
           
                      /* 使用一個預(yù)期狀態(tài)啟動信號量 */
                      ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK);
                  }
           
                  return pxNewQueue;
              }
          #endif /* configUSE_MUTEXES */

          這個函數(shù)是帶條件編譯的,只有將宏configUSE_MUTEXES定義為1才會編譯這個函數(shù)。

          函數(shù)首先調(diào)用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()來創(chuàng)建一個隊列,隊列項數(shù)目為1,隊列項大小為0,說明創(chuàng)建的隊列只有隊列數(shù)據(jù)結(jié)構(gòu)存儲空間而沒有隊列項存儲空間。

          如果隊列創(chuàng)建成功,通用隊列創(chuàng)建函數(shù)還會按照通用隊列的方式 初始化所有隊列結(jié)構(gòu)體成員。

          但是這里要創(chuàng)建的是互斥量,所以有一些結(jié)構(gòu)體成員必須重新賦值。

          在這段代碼中,可能你會疑惑,隊列結(jié)構(gòu)體成員中,并沒有pxMutexHolderuxQueueType

          其實這兩個標(biāo)識符只是宏定義,是專門為互斥量而定義的,如下所示:

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

          當(dāng)隊列結(jié)構(gòu)體用于互斥量時,成員pcHead和pcTail指針就不再需要,并且將pcHead指針設(shè)置為NULL,表示pcTail指針實際指向互斥量持有者任務(wù)TCB(如果有的話)。

          最后調(diào)用函數(shù)xQueueGenericSend()釋放一個互斥量,相當(dāng)于互斥量創(chuàng)建后是有效的,可以直接使用獲取信號量API函數(shù)來獲取這個互斥量。

          如果某資源同時只準(zhǔn)一個任務(wù)訪問,可以用互斥量保護這個資源。

          這個資源一定是存在的,所以創(chuàng)建互斥量時會先釋放一個互斥量,表示這個資源可以使用。

          任務(wù)想訪問資源時,先獲取互斥量,等使用完資源后,再釋放它。

          也就是說互斥量一旦創(chuàng)建好后,要先獲取,后釋放,要在同一個任務(wù)中獲取和釋放。

          這也是互斥量和二進制信號量的一個重要區(qū)別,二進制信號量可以在隨便一個任務(wù)中獲取或釋放,然后也可以在任意一個任務(wù)中釋放或獲取。

          「互斥量不同于二進制信號量的還有」:互斥量具有優(yōu)先級繼承機制,二進制信號量沒有,互斥量不可以用于中斷服務(wù)程序,二進制信號量可以。

          初始化后的互斥量內(nèi)存如圖1-3所示。

          圖1-3:初始化后的互斥量對象內(nèi)存

          1.4創(chuàng)建遞歸互斥量

          創(chuàng)建遞歸互斥量間接使用通用隊列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建遞歸互斥量API接口同樣是個宏,定義如下:

          #definexSemaphoreCreateRecursiveMutex()                 \
                    xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )

          其中,宏queueQUEUE_TYPE_RECURSIVE_MUTEX用于通用隊列創(chuàng)建函數(shù),表示創(chuàng)建隊列的類型是遞歸互斥量,在文章《FreeRTOS高級篇5---FreeRTOS隊列分析》關(guān)于通用隊列創(chuàng)建函數(shù)參數(shù)說明中提到了這個宏。

          創(chuàng)建互斥量和創(chuàng)建遞歸互斥量是調(diào)用的同一個函數(shù)xQueueCreateMutex();

          至于參數(shù)queueQUEUE_TYPE_RECURSIVE_MUTEX,我們在FreeRTOS一文中已經(jīng)知道,它只是用于可視化調(diào)試;

          因此創(chuàng)建互斥量和創(chuàng)建遞歸互斥量可以看作是一樣的,初始化后的遞歸互斥量對象內(nèi)存也和互斥量一樣,如圖1-3所示。

          2.釋放信號量

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

          2.1 xSemaphoreGive()

          用于釋放一個信號量,不帶中斷保護。被釋放的信號量可以是二進制信號量、計數(shù)信號量和互斥量。

          注意遞歸互斥量并不能使用這個API函數(shù)釋放。其實信號量釋放是一個宏,真正調(diào)用的函數(shù)是xQueueGenericSend(),宏定義如下:

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

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

          對于二進制信號量和計數(shù)信號量,根據(jù)上一章的內(nèi)容可以總結(jié)出,釋放一個信號量的過程實際上可以簡化為兩種情況:

          「第一」,如果隊列未滿,隊列結(jié)構(gòu)體成員uxMessageWaiting加1,判斷是否有阻塞的任務(wù),有的話解除阻塞,然后返回成功信息(pdPASS);

          「第二」,如果隊列滿,返回錯誤代碼(err_QUEUE_FULL),表示隊列滿。

          對于互斥量要復(fù)雜些,因為互斥量具有優(yōu)先級繼承機制。

          「優(yōu)先級繼承是個什么過程呢?」

          我們舉個例子。某個資源X同時只能有一個任務(wù)訪問,現(xiàn)在有任務(wù)A和任務(wù)C都要訪問這個資源,任務(wù)A的優(yōu)先級為1,任務(wù)C的優(yōu)先級為10,所以任務(wù)C的優(yōu)先級大于任務(wù)A的優(yōu)先級。

          我們用互斥量保護資源X,并且當(dāng)前任務(wù)A正在訪問資源X。

          在任務(wù)A訪問資源X的過程中,來了一個中斷,中斷事件使得任務(wù)C執(zhí)行。

          任務(wù)C執(zhí)行的過程中,也想訪問資源X,但是因為資源X還被任務(wù)A獨占著,所以任務(wù)C無法獲取互斥量,會進入阻塞狀態(tài)。

          此時,低優(yōu)先級任務(wù)A會繼承高優(yōu)先級任務(wù)C的優(yōu)先級,任務(wù)A的優(yōu)先級臨時的被提升,優(yōu)先級變成10。這個機制能夠?qū)⒁呀?jīng)發(fā)生的優(yōu)先級反轉(zhuǎn)影響降低到最小。

          「那么什么是優(yōu)先級反轉(zhuǎn)呢?」

          還是看上面的例子,任務(wù)C的優(yōu)先級高于任務(wù)A,但是任務(wù)C因為沒有獲得互斥量而進入阻塞,只能等待低優(yōu)先級的任務(wù)A釋放互斥量后才能運行,這種情況就是優(yōu)先級反轉(zhuǎn)。

          「那為什么優(yōu)先級繼承可以降低優(yōu)先級反轉(zhuǎn)的影響呢?」

          還是看上面的例子,不過我們再增加一個優(yōu)先級為5的任務(wù)B,這三個任務(wù)都處于就緒狀態(tài)。

          如果沒有優(yōu)先級繼承機制,三個任務(wù)的優(yōu)先級順序為任務(wù)C>任務(wù)B>任務(wù)A。

          當(dāng)任務(wù)C因為得不到互斥量而阻塞后,任務(wù)B會獲取CPU權(quán)限,等到任務(wù)B主動或被動讓出CPU后,任務(wù)A才會執(zhí)行,任務(wù)A釋放互斥量后,任務(wù)C才能得到運行。

          再看一下有優(yōu)先級繼承的情況,當(dāng)任務(wù)C因為得不到互斥量而阻塞后,任務(wù)A繼承任務(wù)C的優(yōu)先級,現(xiàn)在三個任務(wù)的優(yōu)先級順序為任務(wù)C=任務(wù)A>任務(wù)B。

          當(dāng)任務(wù)C因為得不到互斥量而阻塞后,任務(wù)A會獲得CPU權(quán)限,等到任務(wù)A釋放互斥量后,任務(wù)C就會得到運行。看,任務(wù)C等待的時間變短了。

          有了上面的基礎(chǔ)理論,我們就很好理解為什么釋放互斥量會比較復(fù)雜了。

          「還是可以簡化為兩種情況:」

          「第一」,如果隊列未滿,除了隊列結(jié)構(gòu)體成員uxMessageWaiting加1外,還要判斷獲取互斥量的任務(wù)是否有優(yōu)先級繼承,如果有的話,還要將任務(wù)的優(yōu)先級恢復(fù)到原始值。當(dāng)然,恢復(fù)到原來值也是有條件的,就是該任務(wù)必須在沒有使用其它互斥量的情況下,才能將繼承的優(yōu)先級恢復(fù)到原始值。然后判斷是否有阻塞的任務(wù),有的話解除阻塞,最后返回成功信息(pdPASS);

          「第二」,如果如果隊列滿,返回錯誤代碼(err_QUEUE_FULL),表示隊列滿。

          2.2xSemaphoreGiveFromISR()

          用于釋放一個信號量,帶中斷保護。被釋放的信號量可以是二進制信號量和計數(shù)信號量。

          和普通版本的釋放信號量API函數(shù)不同,它不能釋放互斥量,這是因為互斥量不可以在中斷中使用!

          互斥量的優(yōu)先級繼承機制只能在任務(wù)中起作用,在中斷中毫無意義。帶中斷保護的信號量釋放其實也是一個宏,真正調(diào)用的函數(shù)是xQueueGiveFromISR (),宏定義如下:

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

          我們看真正被調(diào)用的函數(shù)源碼(經(jīng)過整理后的):

          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();
            {
              /*當(dāng)隊列用于實現(xiàn)信號量時,永遠不會有數(shù)據(jù)出入隊列,但是任然要檢查隊列是否為空 */
              if( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
              {
                /* 一個任務(wù)可以獲取多個互斥量,但是只能有一個繼承優(yōu)先級,如果任務(wù)是互斥量的持有者,則互斥量不允許在中斷服務(wù)程序中釋放.因此這里不需要判斷是否要恢復(fù)任務(wù)的原始優(yōu)先級值,只是簡單更新隊列項計數(shù)器. */
                ++( pxQueue->uxMessagesWaiting );

                /* 如果列表上鎖,不能改變隊列的事件列表. */
                if( pxQueue->xTxLock == queueUNLOCKED )
                {
                  if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive) ) == pdFALSE )
                  {
                    if(xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive) ) != pdFALSE )
                    {
                      /* 解除阻塞的任務(wù)有更高優(yōu)先級,因此記錄上下文切換請求*/
                      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;
          }

          因為不涉及互斥量,不涉及阻塞,函數(shù)xQueueGiveFromISR()異常簡單,如果隊列滿,直接返回錯誤代碼(err_QUEUE_FULL);

          如果隊列未滿,則將隊列結(jié)構(gòu)體成員uxMessageWaiting加1,然后視隊列是否上鎖而決定是否解除任務(wù)阻塞(如果有得話)。

          如果你覺得難以理解,則需要先看看《FreeRTOS高級篇5---FreeRTOS隊列分析》。

          3.獲取信號量

          無論二進制信號量、計數(shù)信號量還是互斥量,它們都使用相同的獲取和釋放API函數(shù)。

          釋獲取信號量會消耗信號量,如果獲取信號量失敗,任務(wù)可能會阻塞,阻塞時間由函數(shù)參數(shù)xBlockTime指定,如果為0,則直接返回,不阻塞。

          獲取信號量分為不帶中斷保護和帶中斷保護兩個版本。

          3.1 xSemaphoreTake

          用于獲取信號量,不帶中斷保護。獲取的信號量可以是二進制信號量、計數(shù)信號量和互斥量。

          注意遞歸互斥量并不能使用這個API函數(shù)獲取。其實獲取信號量是一個宏,真正調(diào)用的函數(shù)是xQueueGenericReceive (),宏定義如下:

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

          通過上面的宏定義可以看出,獲取信號量實際上是執(zhí)行出隊操作。

          「對于二進制信號量和計數(shù)信號量,可以簡化為三種情況:」

          「第一」,如果隊列不為空,隊列結(jié)構(gòu)體成員uxMessageWaiting減1,判斷是否有因入隊而阻塞的任務(wù),有的話解除阻塞,然后返回成功信息(pdPASS);

          「第二」,如果隊列為空并且阻塞時間為0,則直接返回錯誤碼(errQUEUE_EMPTY),表示隊列為空;

          「第三」,如果隊列為空并且阻塞時間不為0,則任務(wù)會因為等待信號量而進入阻塞狀態(tài),任務(wù)會被掛接到延時列表中。

          「對于互斥量,也可以簡化為三種情況」,但是過程要復(fù)雜一些:

          「第一」,如果隊列不為空,隊列結(jié)構(gòu)體成員uxMessageWaiting減1、將當(dāng)前任務(wù)TCB結(jié)構(gòu)體成員uxMutexesHeld加1,表示任務(wù)獲取互斥量的個數(shù)、將隊列結(jié)構(gòu)體成員指針pxMutexHolder指向任務(wù)TCB、判斷是否有因入隊而阻塞的任務(wù),有的話解除阻塞,然后返回成功信息(pdPASS);

          「第二」,如果隊列為空并且阻塞時間為0,則直接返回錯誤碼(errQUEUE_EMPTY),表示隊列為空;

          「第三」,如果隊列為空并且阻塞時間不為0,則任務(wù)會因為等待信號量而進入阻塞狀態(tài),在將任務(wù)掛接到延時列表之前,會判斷當(dāng)前任務(wù)和擁有互斥量的任務(wù)優(yōu)先級哪個高,如果當(dāng)前任務(wù)優(yōu)先級高,則擁有互斥量的任務(wù)繼承當(dāng)前任務(wù)優(yōu)先級。

          3.2xSemaphoreTakeFromISR()

          用于獲取信號量,帶中斷保護。獲取的信號量可以是二進制信號量和計數(shù)信號量。

          和普通版本的獲取信號量API函數(shù)不同,它不能獲取互斥量,這是因為互斥量不可以在中斷中使用!

          互斥量的優(yōu)先級繼承機制只能在任務(wù)中起作用,在中斷中毫無意義。

          帶中斷保護的獲取信號量其實也是一個宏,真正調(diào)用的函數(shù)是xQueueReceiveFromISR (),宏定義如下:

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

          同樣因為不涉及互斥量,不涉及阻塞,函數(shù)xQueueReceiveFromISR ()同樣異常簡單:如果隊列為空,直接返回錯誤代碼(pdFAIL);

          如果隊列非空,則將隊列結(jié)構(gòu)體成員uxMessageWaiting減1,然后視隊列是否上鎖而決定是否解除任務(wù)阻塞(如果有得話)。

          4.釋放遞歸互斥量

          函數(shù)xSemaphoreGiveRecursive()用于釋放一個遞歸互斥量。

          已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量。

          使用xSemaphoreTakeRecursive() 函數(shù)成功獲取幾次遞歸互斥量,就要使用xSemaphoreGiveRecursive()函數(shù)返還幾次,在此之前遞歸互斥量都處于無效狀態(tài)。

          比如,某個任務(wù)成功獲取5次遞歸互斥量,那么在它沒有返還5次該遞歸互斥量之前,這個互斥量對別的任務(wù)無效。

          像其它信號量一樣,xSemaphoreGiveRecursive()也是一個宏定義,它最終使用現(xiàn)有的隊列機制,實際執(zhí)行的函數(shù)是xQueueGiveMutexRecursive(),這個宏定義如下所示:

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

          我們重點來看函數(shù)xQueueGiveMutexRecursive()的實現(xiàn)過程。經(jīng)過整理后(去除跟蹤調(diào)試語句)的源碼如下所示:

          #if ( configUSE_RECURSIVE_MUTEXES == 1 )
              BaseType_txQueueGiveMutexRecursive( QueueHandle_t xMutex )
              {
              BaseType_t xReturn;
              Queue_t * const pxMutex = ( Queue_t * ) xMutex;

             /* 互斥量和遞歸互斥量要在同一個任務(wù)中獲取和釋放,當(dāng)獲取互斥量或遞歸互斥量時,隊列結(jié)構(gòu)體成員指針pxMutexHolder指向獲取互斥量或遞歸互斥量的任務(wù)TCB,所以在釋放遞歸互斥量時需要檢查這個指針指向的TCB是否是和當(dāng)前任務(wù)TCB相同,如果不相同是不能釋放這個遞歸互斥量的! 注:釋放互斥量時,這個檢查不是必須的,FreeRTOS的作者將這個檢查放在了斷言中(configASSERT( pxTCB == pxCurrentTCB);).*/
                  if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
                  {
                      /* 每當(dāng)任務(wù)獲取遞歸互斥量時,隊列結(jié)構(gòu)體成員u.uxRecursiveCallCount會加1,互斥量不會使用這個變量,它用來保存遞歸次數(shù).所以,在釋放遞歸互斥量的時候要將它減1*/
                      ( pxMutex->u.uxRecursiveCallCount)--;
           
                      /* 遞歸計數(shù)器為0? */
                      if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
                      {
                          /* 調(diào)用入隊函數(shù)釋放一個互斥量,注意阻塞時間(由宏queueMUTEX_GIVE_BLOCK_TIME定義)為0 */
                          ( void ) xQueueGenericSend( pxMutex, NULL,queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
                      }
           
                      xReturn = pdPASS;
                  }
                  else
                  {
                      /* 如果不是本任務(wù)擁有這個互斥量,則直接返回錯誤碼 */
                      xReturn = pdFAIL;
                  }
           
                  return xReturn;
              }
          #endif /* configUSE_RECURSIVE_MUTEXES */

          這個函數(shù)是帶條件編譯的,只有將宏configUSE_RECURSIVE_MUTEXES定義為1才會編譯這個函數(shù)。

          互斥量和遞歸互斥量的最大區(qū)別在于一個遞歸互斥量可以被已經(jīng)獲取這個遞歸互斥量的任務(wù)重復(fù)獲取,這個遞歸調(diào)用功能是通過隊列結(jié)構(gòu)體成員u.uxRecursiveCallCount實現(xiàn)的。

          這個變量用于存儲遞歸調(diào)用的次數(shù),每次獲取遞歸互斥量后,這個變量加1,在釋放遞歸互斥量后,這個變量減1。

          只有這個變量減到0,即釋放和獲取的次數(shù)相等時,互斥量才能再次有效,使用入隊函數(shù)釋放一個遞歸互斥量。

          5.獲取遞歸互斥量

          函數(shù)xSemaphoreTakeRecursive()用于獲取一個遞歸互斥量。像其它信號量一樣,xSemaphoreTakeRecursive()也是一個宏定義,它最終使用現(xiàn)有的隊列機制,實際執(zhí)行的函數(shù)是xQueueTakeMutexRecursive(),這個宏定義如下所示:

          #definexSemaphoreTakeRecursive( xMutex, xBlockTime )       \
                         xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )

          獲取遞歸互斥量具有阻塞超時參數(shù),如果互斥量正被別的任務(wù)使用,可以阻塞設(shè)定的時間。

          我們重點來看函數(shù)xQueueTakeMutexRecursive()的實現(xiàn)過程。經(jīng)過整理后(去除跟蹤調(diào)試語句)的源碼如下所示:

          #if ( configUSE_RECURSIVE_MUTEXES == 1 )
              BaseType_txQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_txTicksToWait )
              {
              BaseType_t xReturn;
              Queue_t * const pxMutex = ( Queue_t * ) xMutex;
              /*互斥量和遞歸互斥量要在同一個任務(wù)中獲取和釋放,遞歸互斥量可以在一個任務(wù)中多次獲取,當(dāng)?shù)谝淮潍@取遞歸互斥量時,隊列結(jié)構(gòu)體成員指針pxMutexHolder指向獲取遞歸互斥量的任務(wù)TCB,在此獲取這個遞歸互斥量時,如果這個指針指向的TCB和當(dāng)前任務(wù)TCB相同,只需要將遞歸次數(shù)計數(shù)器u.uxRecursiveCallCount加1即可,不需要再操作隊列.*/
                  if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
                  {
                      ( pxMutex->u.uxRecursiveCallCount)++;
                      xReturn = pdPASS;
                  }
                  else
                  {
                      /*調(diào)用出隊函數(shù)*/
                      xReturn =xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
           
                      /* 成功獲取遞歸互斥量后,要將遞歸次數(shù)計數(shù)器加1*/
                      if( xReturn != pdFAIL )
                      {
                          ( pxMutex->u.uxRecursiveCallCount)++;
                      }
                  }
           
                  return xReturn;
              }
          #endif /* configUSE_RECURSIVE_MUTEXES */

          這個函數(shù)是帶條件編譯的,只有將宏configUSE_RECURSIVE_MUTEXES定義為1才會編譯這個函數(shù)。

          程序邏輯比較簡單,如果是第一次獲取這個遞歸互斥量,直接使用出隊函數(shù),成功后將遞歸次數(shù)計數(shù)器加1;

          如果是第二次或者更多次獲取這個遞歸互斥量,則只需要將遞歸次數(shù)計數(shù)器加1即可。

          ????????????????  END  ????????????????

          推薦閱讀:


          嵌入式編程專輯
          Linux 學(xué)習(xí)專輯
          C/C++編程專輯
          Qt進階學(xué)習(xí)專輯

          關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。


          點擊“閱讀原文”查看更多分享。

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九小视频 | 国产精品久久久久久亚洲毛片 | 黑人巨大精品A片一区二区七区 | 亚洲激情AV | 中文资源在线天堂的功能介绍 |