16 - FreeRTOS信號量

信號量簡介

  • FreeRTOS的信號量包括二進制信號量、計數信號量、互斥信號量(以後簡稱互斥量)和遞歸互斥信號量(以後簡稱遞歸互斥量)。
  • 我們可以把互斥量和遞歸互斥量看成特殊的信號量。互斥量和信號量在用法上不同:
  1. 信號量用於同步,任務間或者任務和中斷間同步;互斥量用於互鎖,用於保護同時只能有一個任務訪問的資源,爲資源上一把鎖。
  2. 信號量用於同步時,一般是一個任務(或中斷)給出信號,另一個任務獲取信號;互斥量必須在同一個任務中獲取信號、同一個任務給出信號。
  3. 互斥量具有優先級繼承,信號量沒有。
  4. 互斥量不能用在中斷服務程序中,信號量可以。
  5. 創建互斥量和創建信號量的API函數不同,但是共用獲取和給出信號API函數;

二進制信號量

  • 二進制信號量既可以用於互斥功能也可以用於同步功能。

  • 二進制信號量和互斥量非常相似,但是有一些細微差別:互斥量包含一個優先級繼承機制,二進制信號量則沒有這個機制。這使得二進制信號量更好的用於實現同步(任務間或任務和中斷間),互斥量更好的用於實現簡單互斥。本節僅描述用於同步的二進制信號量。

  • 信號量API函數允許指定一個阻塞時間。當任務企圖獲取一個無效信號量時,任務進入阻塞狀態,阻塞時間用來確定任務進入阻塞的最大時間,阻塞時間單位爲系統節拍週期時間。如果有多個任務阻塞在同一個信號量上,那麼當信號量有效時,具有最高優先級別的任務最先解除阻塞。

  • 可以將二進制信號量看作只有一個項目(item)的隊列,因此這個隊列只能爲空或滿(因此稱爲二進制)。任務和中斷使用隊列無需關注誰控制隊列—只需要知道隊列是空還是滿。利用這個機制可以在任務和中斷之間同步。

  • 考慮這樣一種情況,一個任務用來維護外設。使用輪詢的方法會浪費CPU資源並且妨礙其它任務執行。更好的做法是任務的大部分時間處於阻塞狀態(允許其它任務執行),直到某些事件發生該任務才執行。可以使用二進制信號量實現這種應用:當任務取信號量時,因爲此時尚未發生特定事件,信號量爲空,任務會進入阻塞狀態;當外設需要維護時,觸發一箇中斷服務例程,該中斷服務僅僅給出信號量(向隊列寫數據)。任務只是取信號,並不需要歸還,中斷服務只是給信號。

  • 任務的優先級可以用於確保外設及時獲得維護。還可以使用隊列來代替二進制信號量。中斷例程可以捕獲與外設事件相關的數據並將它發往任務的隊列。任務發現隊列數據有效時解除阻塞,如果需要,則進行數據處理。第二種方案使得中斷執行儘可能的短,其它處理過程可以在任務中實現。

    注:中斷程序中決不可使用無“FromISR”結尾的API函數。
    注:在大部分應用場合,任務通知都可以代替二進制信號量,並且速度更快、生成的代碼更少。
    

計數信號量

  • 二進制信號量可以被認爲是長度爲1的隊列,計數信號量則可以被認爲長度大於1的隊列。此外,信號量使用者不必關心存儲在隊列中的數據,只需關心隊列是否爲空。
  • 通常計數信號量用於下面兩種事件:
  1. 計數事件:在這種場合下,每當事件發生,事件處理程序將給出一個信號(信號量計數值增1),當處理事件時,處理程序會取走信號量(信號量計數值減1)。因此,計數值是事件發生的數量和事件處理的數量差值。在這種情況下,計數信號量在創建時其值爲0。

  2. 資源管理:這種用法下,計數值表示有效的資源數目。任務必須先獲取信號量才能獲取資源控制權。當計數值減爲零時表示沒有的資源。當任務完成後,它會返還信號量—信號量計數值增加。在這種情況下,信號量創建時,計數值等於最大資源數目。

    注:中斷程序中決不可使用無“FromISR”結尾的API函數。
    注:在大部分應用場合,任務通知都可以代替計數信號量,並且速度更快、生成的代碼更少。

互斥量

  • 互斥量是一個包含優先級繼承機制的二進制信號量。用於實現同步(任務之間或者任務與中斷之間)的話,二進制信號量是更好的選擇,互斥量用於簡單的互鎖。
  • 用於互鎖的互斥量可以充當保護資源的令牌。當一個任務希望訪問某個資源時,它必須先獲取令牌。當任務使用完資源後,必須還回令牌,以便其它任務可以訪問同一資源。
  • 互斥量和信號量使用相同的API函數,因此互斥量也允許指定一個阻塞時間。阻塞時間單位爲系統節拍週期時間,數目表示獲取互斥量無效時最多處於阻塞狀態的系統節拍週期個數。
  • 互斥量與二進制信號量最大的不同是:互斥量具有優先級繼承機制。也就是說,如果一個互斥量(令牌)正在被一個低優先級任務使用,此時一個高優先級企圖獲取這個互斥量,高優先級任務會因爲得不到互斥量而進入阻塞狀態,正在使用互斥量的低優先級任務會臨時將自己的優先級提升,提升後的優先級與與進入阻塞狀態的高優先級任務相同。這個優先級提升的過程叫做優先級繼承。這個機制用於確保高優先級任務進入阻塞狀態的時間儘可能短,以及將已經出現的“優先級翻轉”影響降低到最小。
  • 在很多場合中,某個硬件資源只有一個,當低優先級任務佔用該資源的時候,即便高優先級任務也只能乖乖的等待低優先級任務釋放資源。這裏高優先級任務無法運行而低優先級任務可以運行的現象稱爲“優先級翻轉”。
  • 爲什麼優先級繼承能夠降低優先級翻轉的影響呢?舉個例子,現在有任務A、任務B和任務C,三個任務的優先級順序爲任務C>任務B>任務A。任務A和任務C都要使用某一個硬件資源,並且當前任務A佔有該資源。
    • 先看沒有優先級繼承的情況:任務C也要使用該資源,但是此時任務A正在使用這個資源,因此任務C進入阻塞,此時三個任務的優先級順序沒有發生變化。在任務C進入阻塞之後,某硬件產生了一次中斷,喚醒了一個事件,該事件可以解除任務B的阻塞狀態。在中斷結束後,因爲任務B的優先級是大於任務A的,所以任務B搶佔任務A的CPU權限。那麼任務C的阻塞時間就至少爲:中斷處理時間+任務B的運行時間+任務A的運行時間。
    • 再看有優先級繼承的情況:任務C也要使用該資源,但是此時任務A正在使用這個資源,因此任務C進入阻塞,此時由於優先級A會繼承任務C的優先級,三個任務的優先級順序發生了變化,新的優先級順序爲:任務C=任務A>任務B。在任務C進入阻塞之後,某硬件產生了一次中斷,喚醒了一個事件,該事件可以解除任務B的阻塞狀態。在中斷結束後,因爲任務A的優先級臨時被提高,大於任務B的優先級,所以任務A繼續獲得CPU權限。任務A完成後,處於高優先級的任務C會接管CPU。所以任務C的阻塞時間爲:中斷處理時間+任務A的運行時間。看,任務C的阻塞時間變小了,這就是優先級繼承的優勢。
  • 優先級繼承不能解決優先級反轉,只能將這種情況的影響降低到最小。硬實時系統在一開始設計時就要避免優先級反轉發生。

遞歸互斥量

  • 已經獲取遞歸互斥量的任務可以重複獲取該遞歸互斥量。使用xSemaphoreTakeRecursive() 函數成功獲取幾次遞歸互斥量,就要使用xSemaphoreGiveRecursive()函數返還幾次,在此之前遞歸互斥量都處於無效狀態。比如,某個任務成功獲取5次遞歸互斥量,那麼在它沒有返還5次該遞歸互斥量之前,這個互斥量對別的任務無效。
  • 遞歸互斥量可以看成帶有優先級繼承機制的信號量,獲取遞歸互斥量的任務在用完後必須返還。
  • 互斥量不可以用在中斷服務程序中,這是因爲:
  1. 互斥量具有優先級繼承機制,只有在任務中獲取或給出互斥纔有意義。
  2. 中斷不能因爲等待互斥量而阻塞
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章