來自:http://blog.csdn.net/zhzht19861011/article/details/50835613
本文介紹信號量的基礎知識,詳細源碼分析見《FreeRTOS高級篇6---FreeRTOS信號量分析》
1.信號量簡介
? ? ? FreeRTOS的信號量包括二進制信號量、計數信號量、互斥信號量(以后簡稱互斥量)和遞歸互斥信號量(以后簡稱遞歸互斥量)。
? ? ? 我們可以把互斥量和遞歸互斥量看成特殊的信號量。互斥量和信號量在用法上不同:
- 信號量用于同步,任務間或者任務和中斷間同步;
- 互斥量用于互鎖,用于保護同時只能有一個任務訪問的資源,為資源上一把鎖。
- 信號量用于同步時,一般是一個任務(或中斷)給出信號,另一個任務獲取信號;互斥量必須在同一個任務中獲取信號、同一個任務給出信號。
- 互斥量具有優先級繼承,信號量沒有。
- 互斥量不能用在中斷服務程序中,信號量可以。
- 創建互斥量和創建信號量的API函數不同,但是共用獲取和給出信號API函數;
2.二進制信號量
? ? ? 二進制信號量既可以用于互斥功能也可以用于同步功能。
? ? ? 二進制信號量和互斥量非常相似,但是有一些細微差別:互斥量包含一個優先級繼承機制,二進制信號量則沒有這個機制。這使得二進制信號量更好的用于實現同步(任務間或任務和中斷間),互斥量更好的用于實現簡單互斥。本節僅描述用于同步的二進制信號量。
? ? ? 信號量API函數允許指定一個阻塞時間。當任務企圖獲取一個無效信號量時,任務進入阻塞狀態,阻塞時間用來確定任務進入阻塞的最大時間,阻塞時間單位為系統節拍周期時間。如果有多個任務阻塞在同一個信號量上,那么當信號量有效時,具有最高優先級別的任務最先解除阻塞。
? ? ? 可以將二進制信號量看作只有一個項目(item)的隊列,因此這個隊列只能為空或滿(因此稱為二進制)。任務和中斷使用隊列無需關注誰控制隊列---只需要知道隊列是空還是滿。利用這個機制可以在任務和中斷之間同步。
? ? ? 考慮這樣一種情況,一個任務用來維護外設。使用輪詢的方法會浪費CPU資源并且妨礙其它任務執行。更好的做法是任務的大部分時間處于阻塞狀態(允許其它任務執行),直到某些事件發生該任務才執行。可以使用二進制信號量實現這種應用:當任務取信號量時,因為此時尚未發生特定事件,信號量為空,任務會進入阻塞狀態;當外設需要維護時,觸發一個中斷服務例程,該中斷服務僅僅給出信號量(向隊列寫數據)。任務只是取信號,并不需要歸還,中斷服務只是給信號。
? ? ? 任務的優先級可以用于確保外設及時獲得維護。還可以使用隊列來代替二進制信號量。中斷例程可以捕獲與外設事件相關的數據并將它發往任務的隊列。任務發現隊列數據有效時解除阻塞,如果需要,則進行數據處理。第二種方案使得中斷執行盡可能的短,其它處理過程可以在任務中實現。
? ? ? 注:斷程序中決不可使用無“FromISR”結尾的API函數。
? ? ? 注:在大部分應用場合,任務通知都可以代替二進制信號量,并且速度更快、生成的代碼更少。
圖1-1:中斷和任務之間同步---使用信號量
? ? ? 如圖1-1所示,程序開始運行時,信號量無效,因此任務阻塞在這個信號量下。一段時間后,一個中斷發生,在中斷服務程序中使用API函數xSemaphoreGiveFromISR()給出了一個信號,信號量變得有效。當退出中斷服務程序后,執行上下文切換,任務解除阻塞,使用API函數xSemaphoreTake()取走信號量并執行任務。之后信號量變得無效,任務再次進入阻塞。
3.計數信號量
? ? ? 二進制信號量可以被認為是長度為1的隊列,計數信號量則可以被認為長度大于1的隊列。此外,信號量使用者不必關心存儲在隊列中的數據,只需關心隊列是否為空。
? ? ? 通常計數信號量用于下面兩種事件:
- 計數事件:在這種場合下,每當事件發生,事件處理程序將給出一個信號(信號量計數值增1),當處理事件時,處理程序會取走信號量(信號量計數值減1)。因此,計數值是事件發生的數量和事件處理的數量差值。在這種情況下,計數信號量在創建時其值為0。
- 資源管理:這種用法下,計數值表示有效的資源數目。任務必須先獲取信號量才能獲取資源控制權。當計數值減為零時表示沒有的資源。當任務完成后,它會返還信號量---信號量計數值增加。在這種情況下,信號量創建時,計數值等于最大資源數目。
? ? ? 注:中斷程序中決不可使用無“FromISR”結尾的API函數。
? ? ? 注:在大部分應用場合,任務通知都可以代替計數信號量,并且速度更快、生成的代碼更少。
4.互斥量
? ? ? 互斥量是一個包含優先級繼承機制的二進制信號量。用于實現同步(任務之間或者任務與中斷之間)的話,二進制信號量是更好的選擇,互斥量用于簡單的互鎖。
? ? ? 用于互鎖的互斥量可以充當保護資源的令牌。當一個任務希望訪問某個資源時,它必須先獲取令牌。當任務使用完資源后,必須還回令牌,以便其它任務可以訪問同一資源。
? ? ? 互斥量和信號量使用相同的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的阻塞時間變小了,這就是優先級繼承的優勢。
? ? ? 優先級繼承不能解決優先級反轉,只能將這種情況的影響降低到最小。硬實時系統在一開始設計時就要避免優先級反轉發生。
圖4-1 互斥量用于保護資源
? ? ? 如圖4-1所示,互斥量用來保護資源。為了訪問資源,任務必須先獲取互斥量。任務A想獲取資源,首先它使用API函數xSemaphoreTake()獲取信號量,成功獲取到信號量后,任務A就持有了互斥量,可以安全的訪問資源。期間任務B開始執行,它也想訪問資源,任務B也要先獲得信號量,但是信號量此時是無效的,任務B進入阻塞狀態。當任務A執行完成后,使用API函數xSemaphoreGive()釋放信號量。之后任務B解除阻塞,任務B使用API函數xSemaphoreTake()獲取并得到信號量,任務B可以訪問資源。
5.遞歸互斥量
? ? ? 已經獲取遞歸互斥量的任務可以重復獲取該遞歸互斥量。使用xSemaphoreTakeRecursive()?函數成功獲取幾次遞歸互斥量,就要使用xSemaphoreGiveRecursive()函數返還幾次,在此之前遞歸互斥量都處于無效狀態。比如,某個任務成功獲取5次遞歸互斥量,那么在它沒有返還5次該遞歸互斥量之前,這個互斥量對別的任務無效。
? ? ? 遞歸互斥量可以看成帶有優先級繼承機制的信號量,獲取遞歸互斥量的任務在用完后必須返還。
? ? ? 互斥量不可以用在中斷服務程序中,這是因為:
- 互斥量具有優先級繼承機制,只有在任務中獲取或給出互斥才有意義。
- 中斷不能因為等待互斥量而阻塞。