1 、互 斥 信 號 量
1.1 互斥信號量的概念及其作用
互斥信號量的主要作用是對資源實現互斥訪問,使用二值信號量也可以實現互斥訪問的功能,不過互斥信號量與二值信號量有區別。下面我們先舉一個通過二值信號量實現資源獨享,即互斥訪問的例子,讓大家有一個形象的認識,進而引出要講解的互斥信號量。
運行條件:
讓兩個任務 Task1 和 Task2 都運行串口打印函數 printf,這里我們就通過二值信號量實現對函數printf 的互斥訪問。如果不對函數 printf 進行互斥訪問,串口打印容易出現亂碼。
用計數信號量實現二值信號量只需將計數信號量的初始值設置為 1 即可。
代碼實現:
創建二值信號量
static SemaphoreHandle_t xSemaphore = NULL; * 函 數 名: AppObjCreate * 功能說明: 創建任務通信機制 * 形 參: 無 * 返 回 值: 無 static void AppObjCreate (void) {/* 創建二值信號量,首次創建信號量計數值是 0 */xSemaphore = xSemaphoreCreateBinary();if(xSemaphore == NULL) {/* 沒有創建成功,用戶可以在這里加入創建失敗的處理機制 */}/* 先釋放一次,將初始值改為 1,利用二值信號量實現互斥功能 */xSemaphoreGive(xSemaphore); }
? 通過二值信號量實現對 printf 函數互斥訪問的兩個任務
static void vTaskLED(void *pvParameters) { TickType_t xLastWakeTime;const TickType_t xFrequency = 300;/* 獲取當前的系統時間 */xLastWakeTime = xTaskGetTickCount();while(1) {/* 通過二值信號量實現資源互斥訪問,永久等待直到資源可用 */xSemaphoreTake(xSemaphore, portMAX_DELAY); printf("任務 vTaskLED 在運行 "); bsp_LedToggle(1); bsp_LedToggle(4); xSemaphoreGive(xSemaphore);/* vTaskDelayUntil 是絕對延遲,vTaskDelay 是相對延遲。*/vTaskDelayUntil(&xLastWakeTime, xFrequency); } } * 函 數 名: vTaskMsgPro * 功能說明: 實現對串口的互斥訪問 * 形 參: pvParameters 是在創建該任務時傳遞的形參 * 返 回 值: 無 * 優 先 級: 3 static void vTaskMsgPro(void *pvParameters) { TickType_t xLastWakeTime; const TickType_t xFrequency = 300; /* 獲取當前的系統時間 */xLastWakeTime = xTaskGetTickCount(); while(1) { /* 通過二值信號量實現資源互斥訪問,永久等待直到資源可用 */ xSemaphoreTake(xSemaphore, portMAX_DELAY); printf("任務 vTaskMsgPro 在運行 "); bsp_LedToggle(2); bsp_LedToggle(3); xSemaphoreGive(xSemaphore); /* vTaskDelayUntil 是絕對延遲,vTaskDelay 是相對延遲。*/ vTaskDelayUntil(&xLastWakeTime, xFrequency); } }
有了上面二值信號量的認識之后,互斥信號量與二值信號量又有什么區別呢?互斥信號量可以防止優先級翻轉,而二值信號量不支持,下面我們就講解一下優先級翻轉問題。
1.2 優先級翻轉問題
下面我們通過如下的框圖來說明一下優先級翻轉的問題,讓大家有一個形象的認識。
運行條件:
創建 3 個任務 Task1,Task2 和 Task3,優先級分別為 3,2,1。也就是 Task1 的優先級最高。
任務 Task1 和 Task3 互斥訪問串口打印 printf,采用二值信號實現互斥訪問。
起初 Task3 通過二值信號量正在調用 printf,被任務 Task1 搶占,開始執行任務 Task1,也就是上圖的起始位置。
運行過程描述如下:
任務 Task1 運行的過程需要調用函數 printf,發現任務 Task3 正在調用,任務 Task1 會被掛起,等待 Task3 釋放函數 printf。
在調度器的作用下,任務 Task3 得到運行,Task3 運行的過程中,由于任務 Task2 就緒,搶占了 Task3的運行。優先級翻轉問題就出在這里了,從任務執行的現象上看,任務 Task1 需要等待 Task2 執行完畢才有機會得到執行,這個與搶占式調度正好反了,正常情況下應該是高優先級任務搶占低優先級任務的執行,這里成了高優先級任務 Task1 等待低優先級任務 Task2 完成。所以這種情況被稱之為優先級翻轉問題。
任務 Task2 執行完畢后,任務 Task3 恢復執行,Task3 釋放互斥資源后,任務 Task1 得到互斥資源,從而可以繼續執行。
上面就是一個產生優先級翻轉問題的現象。
1.3 FreeRTOS 互斥信號量的實現
FreeRTOS 互斥信號量是怎么實現的呢?其實相對于二值信號量,互斥信號量就是解決了一下優先級翻轉的問題。下面我們通過如下的框圖來說明一下 FreeRTOS 互斥信號量的實現,讓大家有一個形象的認識。
運行條件:
創建 2 個任務 Task1 和 Task2,優先級分別為 1 和 3,也就是任務 Task2 的優先級最高。
任務 Task1 和 Task2 互斥訪問串口打印 printf。
使用 FreeRTOS 的互斥信號量實現串口打印 printf 的互斥訪問。
運行過程描述如下:
低優先級任務 Task1 執行過程中先獲得互斥資源 printf 的執行。此時任務 Task2 搶占了任務 Task1的執行,任務 Task1 被掛起。任務 Task2 得到執行。
任務 Task2 執行過程中也需要調用互斥資源,但是發現任務 Task1 正在訪問,此時任務 Task1 的優先級會被提升到與 Task2 同一個優先級,也就是優先級 3,這個就是所謂的優先級繼承(Priority inheritance),這樣就有效地防止了優先級翻轉問題。任務 Task2 被掛起,任務 Task1 有新的優先級繼續執行。
任務 Task1 執行完畢并釋放互斥資源后,優先級恢復到原來的水平。由于互斥資源可以使用,任務Task2 獲得互斥資源后開始執行。
上面就是一個簡單的 FreeRTOS 互斥信號量的實現過程。
1.4 FreeRTOS 中斷方式互斥信號量的實現
互斥信號量僅支持用在 FreeRTOS 的任務中,中斷函數中不可使用。
2 互 斥 信 號 量 API 函 數
使用如下 18 個函數可以實現 FreeRTOS 的信號量(含計數信號量,二值信號量和互斥信號):
? xSemaphoreCreateBinary()
? xSemaphoreCreateBinaryStatic()
? vSemaphoreCreateBinary()
? xSemaphoreCreateCounting()
? xSemaphoreCreateCountingStatic()
? xSemaphoreCreateMutex()
? xSemaphoreCreateMutexStatic()
? xSem'CreateRecursiveMutex()
? xSem'CreateRecursiveMutexStatic()
? vSemaphoreDelete()
? xSemaphoreGetMutexHolder()
? uxSemaphoreGetCount()
? xSemaphoreTake()
? xSemaphoreTakeFromISR()
? xSemaphoreTakeRecursive()
? xSemaphoreGive()
? xSemaphoreGiveRecursive()
? xSemaphoreGiveFromISR()
關于這 18 個函數的講解及其使用方法可以看 FreeRTOS 在線版手冊:
2.1 函數 xSemaphoreCreateMutex
函數原型:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
函數描述:
函數 xSemaphoreCreateMutex 用于創建互斥信號量。
返回值,如果創建成功會返回互斥信號量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,無法為此互斥信號量提供所需的空間會返回 NULL。
使用這個函數要注意以下問題:
1. 此函數是基于函數 xQueueCreateMutex 實現的:
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
函數 xQueueCreateMutex 的實現是基于消息隊列函數 xQueueGenericCreate 實現的。
2. 使用此函數要在 FreeRTOSConfig.h 文件中使能宏定義:
#define configUSE_MUTEXES 1
使用舉例:
static SemaphoreHandle_t xMutex = NULL; * 函 數 名: AppObjCreate * 功能說明: 創建任務通信機制 * 形 參: 無 * 返 回 值: 無 static void AppObjCreate (void) {/* 創建互斥信號量 */xMutex = xSemaphoreCreateMutex(); if(xSemaphore == NULL) {/* 沒有創建成功,用戶可以在這里加入創建失敗的處理機制 */} }
2.2 函數 xSemaphoreGive
函數原型:
xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信號量句柄 */
函數描述:
函數 xSemaphoreGive 用于在任務代碼中釋放信號量。
第 1 個參數是信號量句柄。
返回值,如果信號量釋放成功返回 pdTRUE,否則返回 pdFALSE,因為信號量的實現是基于消息隊列,返回失敗的主要原因是消息隊列已經滿了。
使用這個函數要注意以下問題:
1. 此函數是基于消息隊列函數 xQueueGenericSend 實現的:
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME,queueSEND_TO_BACK )
2. 此函數是用于任務代碼中調用的,故不可以在中斷服務程序中調用此函數,中斷服務程序中使用的是xSemaphoreGiveFromISR。
3. 使用此函數前,一定要保證用函數 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()創建了信號量。
4. 此函數不支持使用 xSemaphoreCreateRecursiveMutex()創建的信號量。
2.3 函數 xSemaphoreTake
函數原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信號量句柄 */
TickType_t xTicksToWait ); /* 等待信號量可用的最大等待時間 */
函數描述:
函數 xSemaphoreTake 用于在任務代碼中獲取信號量。
第 1 個參數是信號量句柄。
第 2 個參數是沒有信號量可用時,等待信號量可用的最大等待時間,單位系統時鐘節拍。
返回值,如果創建成功會獲取信號量返回 pdTRUE,否則返回 pdFALSE。
使用這個函數要注意以下問題:
1. 此函數是用于任務代碼中調用的,故不可以在中斷服務程序中調用此函數,中斷服務程序使用的是xSemaphoreTakeFromISR。
2. 如果消息隊列為空且第 2 個參數為 0,那么此函數會立即返回。
3. 如果用戶將 FreeRTOSConfig.h 文件中的宏定義 INCLUDE_vTaskSuspend 配置為 1 且第 2 個參數配置為 portMAX_DELAY,那么此函數會永久等待直到信號量可用。
使用舉例:
static SemaphoreHandle_t xMutex = NULL; * 函 數 名: vTaskLED * 功能說明: 實現串口的互斥訪問,防止多個任務同時訪問造成串口打印亂碼 * 形 參: pvParameters 是在創建該任務時傳遞的形參 * 返 回 值: 無 * 優 先 級: 2 static void vTaskLED(void *pvParameters) { TickType_t xLastWakeTime;const TickType_t xFrequency = 200;/* 獲取當前的系統時間 */xLastWakeTime = xTaskGetTickCount();while(1) {/* 互斥信號量,xSemaphoreTake 和 xSemaphoreGive 一定要成對的調用 */xSemaphoreTake(xMutex, portMAX_DELAY); printf("任務 vTaskLED 在運行 "); bsp_LedToggle(2); bsp_LedToggle(3); xSemaphoreGive(xMutex);/* vTaskDelayUntil 是絕對延遲,vTaskDelay 是相對延遲。*/vTaskDelayUntil(&xLastWakeTime, xFrequency); } }
互斥信號量,xSemaphoreTake 和 xSemaphoreGive 一定要成對的調用
經過測試,互斥信號量是可以被其他任務釋放的,但是我們最好不要這么做,因為官方推薦的就是在同一個任務中接收和釋放。如果在其他任務釋放,不僅僅會讓代碼整體邏輯變得復雜,還會給使用和維護這套API的人帶來困難。遵守規范,總是好的。
裸機編程的時候,我經常想一個問題,就是怎么做到當一個標志位觸發的時候,立即執行某個操作,如同實現標志中斷一樣,在os編程之后,我們就可以讓一個優先級最高任務一直等待某個信號量,如果獲得信號量,就執行某個操作,實現類似標志位中斷的作用(當然,要想正真做到中斷效果,那就需要屏蔽所有可屏蔽中斷,而臨界區就可以做到)。
再說一下遞歸互斥信號量:遞歸互斥信號量,其實就是互斥信號量里面嵌套互斥信號量
使用舉例:
static void vTaskMsgPro(void *pvParameters) { TickType_t xLastWakeTime; const TickType_t xFrequency = 1500; /* 獲取當前的系統時間 */ xLastWakeTime = xTaskGetTickCount(); while(1) {/* 遞歸互斥信號量,其實就是互斥信號量里面嵌套互斥信號量 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); {//假如這里是被保護的資源,第1層被保護的資源,用戶可以在這里添加被保護資源 printf("任務vTaskMsgPro在運行,第1層被保護的資源,用戶可以在這里添加被保護資源 "); /* 第1層被保護的資源里面嵌套被保護的資源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { //假如這里是被保護的資源,第2層被保護的資源,用戶可以在這里添加被保護資源 printf("任務vTaskMsgPro在運行,第2層被保護的資源,用戶可以在這里添加被保護資源 "); /* 第2層被保護的資源里面嵌套被保護的資源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { printf("任務vTaskMsgPro在運行,第3層被保護的資源,用戶可以在這里添加被保護資源 "); bsp_LedToggle(1); bsp_LedToggle(4); } xSemaphoreGiveRecursive(xRecursiveMutex); } xSemaphoreGiveRecursive(xRecursiveMutex); } xSemaphoreGiveRecursive(xRecursiveMutex); /* vTaskDelayUntil是絕對延遲,vTaskDelay是相對延遲。*/ vTaskDelayUntil(&xLastWakeTime, xFrequency); } }
可以前面的那個官方文檔那樣用if判斷傳遞共享量:
也可以用我們遞歸互斥信號量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE時,會一直等待信號量,直到有信號量來才執行后面的語句。
責任編輯:PSY
原文標題:嵌入式系統FreeRTOS — 互斥信號量
文章出處:【微信公眾號:開源嵌入式】歡迎添加關注!文章轉載請注明出處。
-
嵌入式系統
+關注
關注
41文章
3570瀏覽量
129251 -
FreeRTOS
+關注
關注
12文章
483瀏覽量
62018 -
互斥信號量
+關注
關注
0文章
3瀏覽量
2018
發布評論請先 登錄
相關推薦
評論