以下轉載自安富萊電子: http://forum.armfly.com/forum.php
本章節講解 FreeRTOS 任務間的同步和資源共享機制,二值信號量。 二值信號量是計數信號量的一種特殊形式,即共享資源為 1 的情況。
FreeRTOS 分別提供了二值信號量和計數信號量,其中二值信號量可以理解成計數
信號量的一種特殊形式,即初始化為僅有一個資源可以使用,只不過 FreeRTOS 對這兩種都提供了 API
函數,而像 RTX,uCOS-II 和 III 是僅提供了一個信號量功能,設置不同的初始值就可以分別實現二值信
號量和計數信號量。 當然,FreeRTOS 使用計數信號量也能夠實現同樣的效果。 另外,為什么叫二值信號
量呢?因為信號量資源被獲取了,信號量值就是 0,信號量資源被釋放,信號量值就是 1,把這種只有 0
和 1 兩種情況的信號量稱之為二值信號量。
函數 xSemaphoreCreateBinary
函數原型:
SemaphoreHandle_t xSemaphoreCreateBinary(void)
函數描述:
函數 xSemaphoreCreateBinary 用於創建二值信號量。
返回值,如果創建成功會返回二值信號量的句柄,如果由於 FreeRTOSConfig.h 文件中 heap 大小不
足,無法為此二值信號量提供所需的空間會返回 NULL。
FreeRTOS 重要的資源共享機制---互斥信號量(Mutex,即 Mutual Exclusion 的縮寫)。
注意,建議初學者學習完前兩個信號量后再學習本章節的互斥信號量。
互斥信號量的主要作用是對資源實現互斥訪問,使用二值信號量也可以實現互斥訪問的功能,不過互
斥信號量與二值信號量有區別。 下面我們先舉一個通過二值信號量實現資源獨享,即互斥訪問的例子,讓
大家有一個形象的認識,進而引出要講解的互斥信號量。
運行條件:
讓兩個任務 Task1 和 Task2 都運行串口打印函數 printf,這里我們就通過二值信號量實現對函數
printf 的互斥訪問。 如果不對函數 printf 進行互斥訪問,串口打印容易出現亂碼。
用計數信號量實現二值信號量只需將計數信號量的初始值設置為 1 即可。
代碼實現:
有了上面二值信號量的認識之后,互斥信號量與二值信號量又有什么區別呢?互斥信號量可以防止優
先級翻轉,而二值信號量不支持,下面我們就講解一下優先級翻轉問題。
運行條件:
創建 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 得到互斥資源,
從而可以繼續執行。
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 中斷方式互斥信號量的實現
互斥信號量僅支持用在 FreeRTOS 的任務中,中斷函數中不可使用。
互斥信號量 API 函數
函數 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
互斥信號量, xSemaphoreTake 和 xSemaphoreGive 一定要成對的調用
應用舉例:
經過測試,互斥信號量是可以被其他任務釋放的,但是我們最好不要這么做,因為官方推薦的就是在同一個任務中接收和釋放。如果在其他任務釋放,不僅僅會讓代碼整體邏輯變得復雜,還會給使用和維護這套API的人帶來困難。遵守規范,總是好的。
裸機編程的時候,我經常想一個問題,就是怎么做到當一個標志位觸發的時候,立即執行某個操作,如同實現標志中斷一樣,在os編程之后,我們就可以讓一個優先級最高任務一直等待某個信號量,如果獲得信號量,就執行某個操作,實現類似標志位中斷的作用(當然,要想正真做到中斷效果,那就需要屏蔽所有可屏蔽中斷,而臨界區就可以做到)。
再說一下遞歸互斥信號量:遞歸互斥信號量,其實就是互斥信號量里面嵌套互斥信號量
eg:
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層被保護的資源,用戶可以在這里添加被保護資源\r\n"); /* 第1層被保護的資源里面嵌套被保護的資源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { /* ------------------------------------------------------------------------ */ //假如這里是被保護的資源,第2層被保護的資源,用戶可以在這里添加被保護資源 /* ------------------------------------------------------------------------ */ printf("任務vTaskMsgPro在運行,第2層被保護的資源,用戶可以在這里添加被保護資源\r\n"); /* 第2層被保護的資源里面嵌套被保護的資源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { printf("任務vTaskMsgPro在運行,第3層被保護的資源,用戶可以在這里添加被保護資源\r\n"); bsp_LedToggle(1); bsp_LedToggle(4); } xSemaphoreGiveRecursive(xRecursiveMutex); } xSemaphoreGiveRecursive(xRecursiveMutex); } xSemaphoreGiveRecursive(xRecursiveMutex); /* vTaskDelayUntil是絕對延遲,vTaskDelay是相對延遲。*/ vTaskDelayUntil(&xLastWakeTime, xFrequency); } }
可以前面的那個官方文檔那樣用if判斷傳遞共享量:
也可以用我們遞歸互斥信號量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE時,會一直等待信號量,直到有信號量來才執行后面的語句。