FreeRTOS Semaphore信號量-筆記
- **一、信號量與互斥量的核心區別**
- **二、二值信號量(Binary Semaphore)**
- **1. 功能與使用場景**
- **2. 示例:ADC中斷與任務同步**
- **三、計數信號量(Counting Semaphore)**
- **1. 功能與使用場景**
- **2. 示例:ADC雙緩沖區管理**
- **四、互斥量(Mutex)**
- **1. 功能與使用場景**
- **2. 示例:保護共享資源**
- **五、關鍵注意事項**
- **六、總結**
隊列的功能是將進程間需要傳遞的數據存在其中。所以在有的RTOS系統里,隊列也被稱為郵箱。
一、信號量與互斥量的核心區別
特性 | 信號量(Semaphore) | 互斥量(Mutex) |
---|---|---|
用途 | 進程間同步(如事件通知)或資源計數(如ADC雙緩沖區) | 互斥訪問共享資源(如保護全局變量或外設) |
所有權 | 無所有權(任何任務或ISR均可釋放) | 有所有權(僅持有者可釋放) |
優先級繼承機制 | 無(可能導致優先級翻轉) | 有(緩解優先級翻轉) |
是否可中斷使用 | 可(通過 xSemaphoreGiveFromISR ) | 不可(ISR中無法使用) |
初始值 | 二值信號量(0或1)或計數信號量(任意值) | 固定為1(表示資源可用) |
信號量和互斥量。信號量和互斥量的實現都是基于隊列的,信號量更適用于進程間同步,而互斥量更適用于共享資源的互斥性訪問。
二、二值信號量(Binary Semaphore)
如果不使用二值信號量,而是使用一個自定義標志變量來實現以上的同步過程,則任務需要不斷的查詢標志變量的值,而不是像使用二值信號那樣可以使任務進入阻塞動態狀態。所以使用二值信號量進行進程間同步的效率更高。
1. 功能與使用場景
- 功能:作為“標志”實現任務同步,例如:
- ADC中斷通知任務:當ADC中斷寫入緩沖區后,釋放二值信號量通知任務處理數據。
- 任務等待事件:任務阻塞等待信號量,避免忙等待。
- 創建函數:
// 動態分配xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );//這是一個宏函數。調用的函數類似于隊列(xQueueGenericCreate)創建隊列時,調用的也是這個函數。
參數解析
-
( UBaseType_t ) 1
- 隊列長度:表示隊列最多能容納的“消息”數量。
- 二進制信號量特性:二進制信號量只能處于“空”(0)或“滿”(1)狀態,因此隊列長度設為 1。
-
semSEMAPHORE_QUEUE_ITEM_LENGTH
- 隊列項大小:定義隊列中每個消息的字節長度。
- 二進制信號量特殊性:該宏被定義為 0(#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )),表示信號量不存儲實際數據,僅用隊列的空/滿狀態表示信號量狀態。
-
queueQUEUE_TYPE_BINARY_SEMAPHORE
- 隊列類型:指定此隊列用于二進制信號量,而非普通隊列或其他類型(如互斥鎖或計數信號量)。
// 靜態分配SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
- 操作函數:
- 釋放信號量(任務):
xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
參數解析
-
(QueueHandle_t)(xSemaphore)
- 信號量即隊列:FreeRTOS 的信號量句柄 xSemaphore 實際上是隊列句柄(QueueHandle_t)的別名。信號量通過隊列實現,其行為由隊列的配置參數控制(如長度、項大小等)。
-
NULL
- 無實際數據:信號量本身不傳遞數據,僅用隊列的空/滿狀態表示信號量狀態。因此,發送的“消息”無需有效數據,使用 NULL 即可。
-
semGIVE_BLOCK_TIME
- 阻塞時間:定義為 0(#define semGIVE_BLOCK_TIME (0)),表示調用 xSemaphoreGive 時不等待。如果隊列已滿(信號量已處于“可用”狀態),則直接返回錯誤 errQUEUE_FULL。
-
queueSEND_TO_BACK
- 入隊位置:將“消息”添加到隊列的尾部。信號量的順序無關緊要,因此使用尾部入隊是合理的。
-
返回類型為BaseType_t,pdFALSE或者pdTRUE。
- 釋放信號量(ISR):
xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
參數解析
- xSemaphore
- 信號量句柄:實際是一個隊列句柄(QueueHandle_t)的別名。信號量通過隊列實現,其行為由隊列的配置參數控制(如長度、項大小等)。
- pxHigherPriorityTaskWoken
- 優先級喚醒標志:
- 類型:BaseType_t *(指向布爾值的指針)。
- 功能:如果釋放信號量導致更高優先級的任務被喚醒,該指針會被設置為 pdTRUE,表示需要在退出中斷前請求上下文切換。
- 可選參數:從 FreeRTOS V7.3.0 開始,此參數可以設為 NULL。
- 注意:如果釋放信號量導致了一個任務解鎖,而解鎖的任務比當前任務的優先級高,這里就會返回pdTRUE。這就需要在退出ISR之前申請任務調度,以便及時的解鎖高優先級的任務。
- 獲取信號量(任務):
xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
參數解析
- xSemaphore
- 信號量句柄:實際是一個隊列句柄(QueueHandle_t)的別名。信號量通過隊列實現,其行為由隊列的配置參數控制(如長度、項大小等)。
- 類型:SemaphoreHandle_t(底層為 QueueHandle_t)。
● xBlockTime - 阻塞時間:以 Tick 為單位指定任務等待信號量的最長時間。
- portMAX_DELAY:無限期等待,直到信號量可用。
- 0:非阻塞模式,立即返回。
- 轉換:使用 pdMS_TO_TICKS(ms) 將毫秒轉換為 Tick(例如 pdMS_TO_TICKS(100))。
2. 示例:ADC中斷與任務同步
// 創建二值信號量
SemaphoreHandle_t xADCSemaphore = xSemaphoreCreateBinary();// ADC中斷服務程序
void ADC_IRQHandler(void) {// 寫入數據到緩沖區WriteDataToBuffer();// 釋放信號量通知任務xSemaphoreGiveFromISR(xADCSemaphore, NULL);
}// 數據處理任務
void vDataProcessingTask(void *pvParameters) {while (1) {// 等待信號量xSemaphoreTake(xADCSemaphore, portMAX_DELAY);// 處理緩沖區數據ProcessData();}
}
三、計數信號量(Counting Semaphore)
資源類比成一個餐館中的四個餐桌。
管理多個共享資源。例如ADC連續數據采集時,一般使用雙緩沖區,就可以使用計數信號量來進行管理。
1. 功能與使用場景
- 功能:管理多個同類型資源(如ADC雙緩沖區),允許同時訪問多個資源。
- 創建函數:
// 動態分配 SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount); // 靜態分配 SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer);
- 操作函數:
- 釋放資源:
xSemaphoreGive(xSemaphore); // 資源計數+1
- 獲取資源:
xSemaphoreTake(xSemaphore, xTicksToWait); // 資源計數-1
- 釋放資源:
2. 示例:ADC雙緩沖區管理
// 創建計數信號量(2個緩沖區)
SemaphoreHandle_t xADCBufferSemaphore = xSemaphoreCreateCounting(2, 2);// ADC中斷服務程序
void ADC_IRQHandler(void) {// 獲取一個緩沖區if (xSemaphoreTakeFromISR(xADCBufferSemaphore, NULL) == pdTRUE) {// 寫入數據到緩沖區WriteDataToBuffer();// 釋放信號量(資源可用)xSemaphoreGiveFromISR(xADCBufferSemaphore, NULL);}
}// 數據處理任務
void vDataProcessingTask(void *pvParameters) {while (1) {// 獲取緩沖區資源xSemaphoreTake(xADCBufferSemaphore, portMAX_DELAY);// 處理緩沖區數據ProcessData();// 釋放緩沖區資源xSemaphoreGive(xADCBufferSemaphore);}
}
四、互斥量(Mutex)
二值信號量更適用于進程間同步,而互斥量更適用于控制對互斥型資源的訪問。二值信號量沒有優先級繼承機制,將二值信號量用于互斥型資源訪問時,容易出現優先級翻轉問題。而互斥量有優先級繼承機制,可以減緩優先級翻轉問題。
1. 功能與使用場景
- 功能:保護共享資源的獨占訪問(如串口),避免數據競爭。
- 創建函數:
// 動態分配 SemaphoreHandle_t xSemaphoreCreateMutex(void); // 靜態分配 SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxSemaphoreBuffer);
- 操作函數:
- 釋放互斥量:(可以釋放二值信號量、計數信號量或者是互斥量。)
xSemaphoreGive(xSemaphore);
- 獲取互斥量:
xSemaphoreTake(xSemaphore, xTicksToWait);
- 遞歸互斥量(允許任務多次獲取同一互斥量):
// 創建 SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void); // 釋放 xSemaphoreGiveRecursive(xSemaphore); // 獲取 xSemaphoreTakeRecursive(xSemaphore, xTicksToWait);
- 釋放互斥量:(可以釋放二值信號量、計數信號量或者是互斥量。)
2. 示例:保護共享資源
// 創建互斥量
SemaphoreHandle_t xSharedResourceMutex = xSemaphoreCreateMutex();// 任務A
void vTaskA(void *pvParameters) {while (1) {xSemaphoreTake(xSharedResourceMutex, portMAX_DELAY);// 訪問共享資源SharedResource++;xSemaphoreGive(xSharedResourceMutex);}
}// 任務B
void vTaskB(void *pvParameters) {while (1) {xSemaphoreTake(xSharedResourceMutex, portMAX_DELAY);// 訪問共享資源SharedResource--;xSemaphoreGive(xSharedResourceMutex);}
}
五、關鍵注意事項
-
優先級翻轉問題:
- 二值信號量:無優先級繼承機制,可能導致低優先級任務阻塞高優先級任務。
- 互斥量:通過優先級繼承機制緩解問題,但仍需謹慎設計任務優先級。
-
ISR中的使用限制:
- 信號量:可在ISR中使用(
xSemaphoreGiveFromISR
)。 - 互斥量:禁止在ISR中使用(因其依賴優先級繼承)。
- 信號量:可在ISR中使用(
-
內存分配選擇:
- 動態分配:適合資源充足的系統,但可能產生內存碎片。
- 靜態分配:適合資源受限的嵌入式系統,需手動管理內存。
-
調試工具:
uxSemaphoreGetCount()
:獲取信號量當前值(適用于計數信號量)。xSemaphoreGetMutexHolder()
:檢查互斥量當前持有者。
六、總結
其他相關函數
● xSemaphoreGiveRecursive(用于遞歸互斥鎖的釋放)。
●xSemaphoreTakeRecursive()獲取二值信號量、計數信號量或者是互斥量。釋放遞歸互斥量依然有一個專用的函數。
●uxSemaphoreGetCount()返回傳進去的這個信號量的當前值。
●xSemaphoreGetMutexHolder()作用:返回當前持有指定互斥鎖(mutex)的任務的句柄(TaskHandle)。如果互斥鎖未被任何任務持有,則返回 NULL。
xSemaphoreTakeFromISR()
- 選擇信號量還是互斥量:
- 同步需求(如事件通知)→ 信號量。
- 資源互斥訪問(如保護共享變量)→ 互斥量。
- 避免常見錯誤:
- 不要在ISR中釋放互斥量。
- 確保每次獲取互斥量后最終釋放。
- 使用遞歸互斥量時,獲取與釋放需嚴格配對。
通過合理使用信號量和互斥量,可以高效實現FreeRTOS中的任務同步與資源共享,提升系統的實時性和穩定性。