在FreeRTOS中,信號量是一種非常重要的同步機制,用于實現任務間的互斥訪問和同步操作。通過信號量,不同的任務可以安全地共享資源,避免競爭和沖突,從而確保系統的穩定性和可靠性。本篇博客將介紹FreeRTOS中信號量的基本概念、使用方法和實際應用,幫助讀者深入理解信號量的原理和在實際項目中的應用場景。我們將從信號量的創建、獲取和釋放等方面進行詳細講解,并結合實際的示例代碼,幫助讀者更好地掌握FreeRTOS中信號量的使用技巧和注意事項。通過本篇博客的學習,讀者將能夠更加靈活地運用信號量來實現任務間的同步和資源共享,提高系統的可靠性和效率。
文章目錄
- 1.信號量簡介
- 1.1 二值信號量
- 1.2 計數信號量
- 1.3 互斥信號量
- 1.4 遞歸信號量
- 1.5 信號量控制塊
- 2.常用信號量API函數
- 2.1 創建信號量函數
- 2.1.1 創建二值信號量 xSemaphoreCreateBinary()
- 2.1.2 創建計數信號量 xSemaphoreCreateCounting()
- 2.2 信號量刪除函數 vSemaphoreDelete()
- 2.3 信號量釋放函數
- 2.3.1 xSemaphoreGive()
- 2.3.2 xSemaphoreGiveFromISR()
- 2.4 信號量獲取函數
- 2.4.1 xSemaphoreTake()
- 2.4.2 xSemaphoreTakeFromISR()
- 3.例子說明
1.信號量簡介
信號量(Semaphore)是一種實現任務間通信的機制,可以實現任務之間同步或臨界資源的互斥訪問,常用于協助一組相互競爭的任務來訪問臨界資源。在多任務系統中,各任務之間需要同步或互斥實現臨界資源的保護,信號量功能可以為用戶提供這方面的支持。
1.1 二值信號量
二值信號量既可以用于臨界資源訪問也可以用于同步功能。
只能取兩個值:0和1。這種信號量通常用于實現互斥訪問,即只有一個進程或線程可以訪問共享資源。當一個進程或線程占用資源時,二值信號量的值為1,其他進程或線程需要等待,直到信號量的值變為0才能訪問資源。
二值信號量通常用于解決臨界區問題,即多個進程或線程需要訪問共享資源時可能會導致數據不一致或競爭條件的問題。通過使用二值信號量,可以有效地控制對共享資源的訪問,避免出現這些問題。
在實際應用中,二值信號量通常與互斥鎖(mutex
)結合使用,以實現對共享資源的互斥訪問。當一個進程或線程需要訪問共享資源時,首先嘗試對二值信號量進行加操作,如果成功則可以訪問資源,否則需要等待。在訪問完成后,再對二值信號量進行減操作,釋放資源。
1.2 計數信號量
二進制信號量可以被認為是長度為 1 的隊列,而計數信號量則可以被認為長度大于 1的隊列,信號量使用者依然不必關心存儲在隊列中的消息,只需關心隊列是否有消息即可。
一種可以取多個值的信號量,它用于控制對一組資源的訪問數量。計數信號量的值可以大于等于0,表示可用的資源數量。當一個進程或線程需要訪問資源時,它會嘗試對計數信號量進行減操作,如果計數信號量的值大于0,則可以訪問資源,同時計數信號量的值會減少;如果計數信號量的值為0,則需要等待,直到有其他進程或線程釋放資源,使計數信號量的值大于0。
計數信號量通常用于實現對一組資源的并發訪問控制,例如限制同時訪問某個資源的進程或線程的數量。這種機制可以有效地控制資源的并發訪問,避免資源被過度占用,提高系統的性能和穩定性。
在實際應用中,計數信號量可以用于實現線程池、連接池等并發控制的場景,以及限制對其他有限資源的并發訪問。通過合理地管理計數信號量的值,可以有效地控制對資源的并發訪問,避免出現競爭條件和數據不一致的問題。
1.3 互斥信號量
互斥信號量其實是特殊的二值信號量,由于其特有的優先級繼承機制從而使它更適用于簡單互鎖,也就是保護臨界資源。
只能取兩個值:0和1。它用于實現對共享資源的互斥訪問,即只有一個進程或線程可以訪問共享資源。當一個進程或線程占用資源時,互斥信號量的值為1,其他進程或線程需要等待,直到信號量的值變為0才能訪問資源。
互斥信號量通常與互斥鎖(mutex)等同步機制結合使用,以實現對共享資源的互斥訪問。在訪問共享資源之前,進程或線程會嘗試對互斥信號量進行減操作,如果成功則可以訪問資源,否則需要等待。在訪問完成后,再對互斥信號量進行加操作,釋放資源。
互斥信號量是實現進程同步和互斥的重要工具,它能夠有效地避免競爭條件和數據不一致的問題。在并發編程中,互斥信號量通常用于保護臨界區,即一段代碼在同一時間只能被一個線程執行,以確保數據的一致性和正確性。
1.4 遞歸信號量
允許同一線程多次對信號量進行加操作,而不會導致死鎖。通常情況下,普通的信號量在同一線程內多次對信號量進行加操作可能會導致死鎖,因為信號量的值會被多次減少,但只有一次加操作來釋放資源。
遞歸信號量則允許同一線程多次對信號量進行加操作,每次加操作都會增加信號量的值,從而避免了死鎖的情況。這種機制通常用于需要在遞歸函數中對共享資源進行加鎖的情況,以及其他需要在同一線程內多次對資源進行加鎖的情況。
1.5 信號量控制塊
2.常用信號量API函數
2.1 創建信號量函數
2.1.1 創建二值信號量 xSemaphoreCreateBinary()
- 原型:
SemaphoreHandle_t xSemaphoreCreateBinary( void );
- 作用:用于創建一個二值信號量,用于實現對共享資源的互斥訪問。
- 參數:無參數。
- 返回值:返回一個
SemaphoreHandle_t
類型的句柄,表示創建的二值信號量。
xSemaphoreCreateBinary()
函數的作用是創建一個二值信號量,二值信號量的初始值為0,用于實現對共享資源的互斥訪問。它返回一個 SemaphoreHandle_t
類型的句柄,可以用于后續對該二值信號量進行操作。
2.1.2 創建計數信號量 xSemaphoreCreateCounting()
- 原型:
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount );
- 作用:用于創建一個計數信號量,用于控制對一組資源的并發訪問數量。
- 參數:
uxMaxCount
:計數信號量的最大計數值。uxInitialCount
:計數信號量的初始計數值。
- 返回值:返回一個
SemaphoreHandle_t
類型的句柄,表示創建的計數信號量。
xSemaphoreCreateCounting()
函數用于創建一個計數信號量,計數信號量的初始值為 uxInitialCount
,最大計數值為 uxMaxCount
。計數信號量通常用于限制對一組資源的并發訪問數量,通過合理地管理計數信號量的值,可以有效地控制對資源的并發訪問,避免出現競爭條件和數據不一致的問題。
2.2 信號量刪除函數 vSemaphoreDelete()
- 原型:
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
- 作用:用于刪除一個已經創建的信號量。
- 參數:
xSemaphore
:要刪除的信號量的句柄。
- 返回值:無。
vSemaphoreDelete()
函數用于刪除一個已經創建的信號量,通過傳入要刪除的信號量的句柄 xSemaphore
來實現。一旦刪除了信號量,其句柄將不再有效,并且不能再對該信號量進行操作。
2.3 信號量釋放函數
2.3.1 xSemaphoreGive()
- 原型:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
- 作用:用于釋放一個二值信號量或增加一個計數信號量的計數值。
- 參數:
xSemaphore
:要釋放的信號量的句柄。
- 返回值:如果釋放成功,則返回 pdTRUE,否則返回 pdFALSE。
xSemaphoreGive()
函數用于釋放一個二值信號量或增加一個計數信號量的計數值。對于二值信號量,調用該函數會將信號量的計數值從 0 增加到 1;對于計數信號量,調用該函數會增加信號量的計數值。如果有任務因等待該信號量而被喚醒,則調用 xSemaphoreGive()
會使其中一個等待的任務得以繼續執行。
2.3.2 xSemaphoreGiveFromISR()
- 原型:
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
- 作用:用于從中斷服務程序中釋放一個二值信號量或增加一個計數信號量的計數值。
- 參數:
xSemaphore
:要釋放的信號量的句柄。pxHigherPriorityTaskWoken
:一個指向 BaseType_t 類型的指針,用于指示是否有一個高優先級任務被喚醒。
- 返回值:如果釋放成功,則返回 pdTRUE,否則返回 pdFALSE。
xSemaphoreGiveFromISR()
函數與 xSemaphoreGive()
函數類似,用于從中斷服務程序中釋放信號量。它可以用于在中斷服務程序中釋放信號量,以喚醒等待該信號量的任務。與 xSemaphoreGive()
不同的是,xSemaphoreGiveFromISR()
可以喚醒一個等待該信號量的高優先級任務,并通過 pxHigherPriorityTaskWoken
參數指示是否有高優先級任務被喚醒。
2.4 信號量獲取函數
2.4.1 xSemaphoreTake()
- 原型:
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
- 作用:用于獲取一個二值信號量或減少一個計數信號量的計數值。
- 參數:
xSemaphore
:要獲取的信號量的句柄。xTicksToWait
:等待獲取信號量的最長時間,以時鐘節拍數(tick)表示。
- 返回值:如果成功獲取信號量,則返回 pdTRUE,否則返回 pdFALSE。
xSemaphoreTake()
函數用于獲取一個二值信號量或減少一個計數信號量的計數值。如果信號量的計數值大于 0,則獲取成功,計數值減一;如果信號量的計數值為 0,則任務將進入阻塞狀態,直到信號量可用或超時。
xTicksToWait
參數用于指定任務在獲取信號量時的最長等待時間。如果設置為 portMAX_DELAY,任務將一直等待直到信號量可用;如果設置為 0,則任務將立即返回獲取結果而不進行等待;如果設置為其他數值,則任務將等待指定的時鐘節拍數后返回獲取結果。
2.4.2 xSemaphoreTakeFromISR()
- 原型:
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
- 作用:用于從中斷服務程序中獲取一個二值信號量或減少一個計數信號量的計數值。
- 參數:
xSemaphore
:要獲取的信號量的句柄。pxHigherPriorityTaskWoken
:一個指向 BaseType_t 類型的指針,用于指示是否有一個高優先級任務被喚醒。
- 返回值:如果成功獲取信號量,則返回 pdTRUE,否則返回 pdFALSE。
xSemaphoreTakeFromISR()
函數與 xSemaphoreTake()
函數類似,用于從中斷服務程序中獲取信號量。它可以用于在中斷服務程序中獲取信號量,以等待信號量可用或減少信號量的計數值。與 xSemaphoreTake()
不同的是,xSemaphoreTakeFromISR()
可以喚醒一個等待該信號量的高優先級任務,并通過 pxHigherPriorityTaskWoken
參數指示是否有高優先級任務被喚醒。
3.例子說明
當在STM32上使用FreeRTOS時,可以使用信號量來實現任務間的同步和互斥訪問。以下是一個簡單的示例,演示了如何在STM32上使用FreeRTOS中的信號量。
假設我們有兩個任務:Task1 和 Task2,它們需要共享一個資源,我們可以使用信號量來進行同步。Task1 將獲取資源并執行一些操作,然后釋放資源;Task2 將等待資源可用,然后獲取資源并執行操作。
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"// 定義一個全局的信號量句柄
SemaphoreHandle_t xSemaphore;void Task1(void *pvParameters) {while (1) {// 嘗試獲取信號量,等待最長100個時鐘節拍if (xSemaphoreTake(xSemaphore, 100) == pdTRUE) {// 信號量獲取成功,執行任務1的操作// ...// 釋放信號量xSemaphoreGive(xSemaphore);} else {// 等待超時或者獲取失敗的處理// ...}}
}void Task2(void *pvParameters) {while (1) {// 嘗試獲取信號量,等待最長100個時鐘節拍if (xSemaphoreTake(xSemaphore, 100) == pdTRUE) {// 信號量獲取成功,執行任務2的操作// ...// 釋放信號量xSemaphoreGive(xSemaphore);} else {// 等待超時或者獲取失敗的處理// ...}}
}int main(void) {// 創建一個二值信號量xSemaphore = xSemaphoreCreateBinary();// 創建任務 Task1xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);// 創建任務 Task2xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);// 啟動調度器vTaskStartScheduler();while (1) {// 如果調度器啟動失敗,進行錯誤處理// ...}
}
在上面的示例中,我們首先創建了一個二值信號量 xSemaphore
,然后在 Task1
和 Task2
中使用 xSemaphoreTake()
和 xSemaphoreGive()
來獲取和釋放信號量。
在每個任務中,我們使用 xSemaphoreTake()
函數來嘗試獲取信號量。如果獲取成功,任務就可以執行相應的操作;如果獲取失敗,任務將等待一段時間(這里是100個時鐘節拍)然后再次嘗試獲取。獲取成功后,任務執行完操作后使用 xSemaphoreGive()
來釋放信號量。
這樣,通過信號量的獲取和釋放,我們可以實現 Task1 和 Task2 之間的資源共享和同步。這樣的設計可以確保兩個任務之間的操作不會相互干擾,從而實現了任務間的同步和互斥訪問。