一、頭文件
#include "FreeRTOS.h"
#include "semphr.h"
二、互斥量
2.1 基本概念
互斥量又稱互斥信號(hào)量(本質(zhì)是信號(hào)量),是一種特殊的二值信號(hào)量,它和信號(hào)量不同的是,它支持互斥量所有權(quán)、遞歸訪問(wèn)以及防止優(yōu)先級(jí)翻轉(zhuǎn)的特性,用于實(shí)現(xiàn)對(duì)臨界資源的獨(dú)占式處理。任意時(shí)刻互斥量的狀態(tài)只有兩種,開(kāi)鎖或閉鎖。當(dāng)互斥量被任務(wù)持有時(shí),該互斥量處于閉鎖狀態(tài),這個(gè)任務(wù)獲得互斥量的所有權(quán)。當(dāng)該任務(wù)釋放這個(gè)互斥量時(shí),該互斥量處于開(kāi)鎖狀態(tài),任務(wù)失去該互斥量的所有權(quán)。當(dāng)一個(gè)任務(wù)持有互斥量時(shí),其他任務(wù)將不能再對(duì)該互斥量進(jìn)行開(kāi)鎖或持有。持有該互斥量的任務(wù)也能夠再次獲得這個(gè)鎖而不被掛起,這就是遞歸訪問(wèn),也就是遞歸互斥量的特性,這個(gè)特性與一般的信號(hào)量有很大的不同,在信號(hào)量中,由于已經(jīng)不存在可用的信號(hào)量,任務(wù)遞歸獲取信號(hào)量時(shí)會(huì)發(fā)生主動(dòng)掛起任務(wù)最終形成死鎖。
如果想要用于實(shí)現(xiàn)同步(任務(wù)之間或者任務(wù)與中斷之間),二值信號(hào)量或許是更好的選擇,雖然互斥量也可以用于任務(wù)與任務(wù)、任務(wù)與中斷的同步,但是互斥量更多的是用于保護(hù)資源的互鎖。
用于互鎖的互斥量可以充當(dāng)保護(hù)資源的令牌,當(dāng)一個(gè)任務(wù)希望訪問(wèn)某個(gè)資源時(shí),它必須先獲取令牌。當(dāng)任務(wù)使用完資源后,必須還回令牌,以便其它任務(wù)可以訪問(wèn)該資源。是不是很熟悉,在我們的二值信號(hào)量里面也是一樣的,用于保護(hù)臨界資源,保證多任務(wù)的訪問(wèn)井然有序。當(dāng)任務(wù)獲取到信號(hào)量的時(shí)候才能開(kāi)始使用被保護(hù)的資源,使用完就釋放信號(hào)量,下一個(gè)任務(wù)才能獲取到信號(hào)量從而可用使用被保護(hù)的資源。但是信號(hào)量會(huì)導(dǎo)致的另一個(gè)潛在問(wèn)題,那就是任務(wù)優(yōu)先級(jí)翻轉(zhuǎn)。而 FreeRTOS 提供的互斥量可以通過(guò)優(yōu)先級(jí)繼承算法,可以降低優(yōu)先級(jí)翻轉(zhuǎn)問(wèn)題產(chǎn)生的影響,所以,用于臨界資源的保護(hù)一般建議使用互斥量。
2.2 運(yùn)作機(jī)制

用互斥量處理不同任務(wù)對(duì)臨界資源的同步訪問(wèn)時(shí),任務(wù)想要獲得互斥量才能進(jìn)行資源訪問(wèn),如果一旦有任務(wù)成功獲得了互斥量,則互斥量立即變?yōu)殚]鎖狀態(tài),此時(shí)其他任務(wù)會(huì)因?yàn)楂@取不到互斥量而不能訪問(wèn)這個(gè)資源,任務(wù)會(huì)根據(jù)用戶自定義的等待時(shí)間進(jìn)行等待,直到互斥量被持有的任務(wù)釋放后,其他任務(wù)才能獲取互斥量從而得以訪問(wèn)該臨界資源,此時(shí)互斥量再次上鎖,如此一來(lái)就可以確保每個(gè)時(shí)刻只有一個(gè)任務(wù)正在訪問(wèn)這個(gè)臨界資源,保證了臨界資源操作的安全性。
2.3 互斥量與遞歸互斥量
- 互斥量更適合于可能會(huì)引起優(yōu)先級(jí)翻轉(zhuǎn)的情況。
- 遞歸互斥量更適用于任務(wù)可能會(huì)多次獲取互斥量的情況下。這樣可以避免同一任務(wù)多次遞歸持有而造成死鎖的問(wèn)題。
三、相關(guān)API說(shuō)明
3.1 xSemaphoreCreateMutex
用于創(chuàng)建一個(gè)互斥量,并返回一個(gè)互斥量句柄。
| 函數(shù) | #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX ) |
|---|---|
| 參數(shù) | 無(wú) |
| 返回值 | 互斥量句柄 |
要想使用該函數(shù)必須在 FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定義為 1 來(lái)使能。

同時(shí)必須在
FreeRTOSConfig.h 中把 configUSE_MUTEXES 定義為 1 來(lái)使能。
3.2 xSemaphoreCreateRecursiveMutex
用于創(chuàng)建一個(gè)遞歸互斥量,不是遞歸的互斥量由函數(shù) xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()創(chuàng)建,且只能被同一個(gè)任務(wù)獲取一次,如果同一個(gè)任務(wù)想再次獲取則會(huì)失敗。遞歸信號(hào)量則相反,它可以被同一個(gè)任務(wù)獲取很多次,獲取多少次就需要釋放多少次。遞歸信號(hào)量與互斥量一樣,都實(shí)現(xiàn)了優(yōu)先級(jí)繼承機(jī)制,可以減少優(yōu)先級(jí)反轉(zhuǎn)的反生。
| 函數(shù) | #define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX ) |
|---|---|
| 參數(shù) | 無(wú) |
| 返回值 | 遞歸互斥量句柄 |
要想使用該函數(shù)必須在 FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定義為 1 來(lái)使能。

同時(shí)必須在
FreeRTOSConfig.h 中把 configUSE_RECURSIVE_MUTEXES 定義為 1 來(lái)使能。
3.3 vSemaphoreDelete
用于刪除一個(gè)信號(hào)量,包括二值信號(hào)量,計(jì)數(shù)信號(hào)量,互斥量和遞歸互斥量。如果有任務(wù)阻塞在該信號(hào)量上,那么不要?jiǎng)h除該信號(hào)量。
| 函數(shù) | void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ) |
|---|---|
| 參數(shù) | xSemaphore: 信號(hào)量句柄 |
| 返回值 | 無(wú) |
3.4 xSemaphoreTake
用于獲取信號(hào)量,不帶中斷保護(hù)。獲取的信號(hào)量對(duì)象可以是二值信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量,但是遞歸互斥量并不能使用這個(gè) API 函數(shù)獲取。
| 函數(shù) | xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime ) |
|---|---|
| 參數(shù) |
xSemaphore: 信號(hào)量句柄 xBlockTime: 等待信號(hào)量可用的最大超時(shí)時(shí)間,單位為 tick(即系統(tǒng)節(jié)拍周期)。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為 portMAX_DELAY ,則任務(wù)將一直阻塞在該信號(hào)量上(即沒(méi)有超時(shí)時(shí)間) |
| 返回值 | 成功返回 pdTRUE,否則返回 errQUEUE_EMPTY |
3.5 xSemaphoreTakeRecursive
用于獲取遞歸互斥量的宏,與互斥量的獲取函數(shù)一樣,xSemaphoreTakeRecursive()也是一個(gè)宏定義,它最終使用現(xiàn)有的隊(duì)列機(jī)制,實(shí)際執(zhí)行的函數(shù)是 xQueueTakeMutexRecursive() 。 獲取遞歸互斥量之前必須由 xSemaphoreCreateRecursiveMutex() 這個(gè)函數(shù)創(chuàng)建。要注意的是該函數(shù)不能用于獲取由函數(shù) xSemaphoreCreateMutex() 創(chuàng)建的互斥量。
| 函數(shù) | #define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) ) |
|---|---|
| 參數(shù) |
xMutex: 信號(hào)量句柄 xBlockTime: 如果不是持有互斥量的任務(wù)去獲取無(wú)效的互斥量,那么任務(wù)將進(jìn)行等待用戶指定超時(shí)時(shí)間,單位為 tick(即系統(tǒng)節(jié)拍周期)。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為portMAX_DELAY ,則任務(wù)將一直阻塞在該遞歸互斥量上(即沒(méi)有超時(shí)時(shí)間) |
| 返回值 | 成功返回 pdTRUE,否則返回 errQUEUE_EMPTY |
要想使用該函數(shù)必須在 FreeRTOSConfig.h 中把configUSE_RECURSIVE_MUTEXES 定義為 1 來(lái)使能。

3.6 xSemaphoreGive
用于釋放信號(hào)量的宏。釋放的信號(hào)量對(duì)象必須是已經(jīng)被創(chuàng)建的,可以用于二值信號(hào)量、計(jì)數(shù)信號(hào)量、互斥量的釋放,但不能釋放由函數(shù) xSemaphoreCreateRecursiveMutex() 創(chuàng)建的遞歸互斥量。此外該函數(shù)不能在中斷中使用。
| 函數(shù) | xSemaphoreGive( SemaphoreHandle_t xSemaphore ) |
|---|---|
| 參數(shù) | xSemaphore: 信號(hào)量句柄 |
| 返回值 | 成功返回 pdTRUE,否則返回 pdFALSE |
3.7 xSemaphoreGiveRecursive
用于釋放一個(gè)遞歸互斥量。已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量。使用 xSemaphoreTakeRecursive() 函數(shù)成功獲取幾次遞歸互斥量,就要使用 xSemaphoreGiveRecursive() 函數(shù)返還幾次,在此之前遞歸互斥量都處于無(wú)效狀態(tài),別的任務(wù)就無(wú)法獲取該遞歸互斥量。使用該函數(shù)接口時(shí),只有已持有互斥量所有權(quán)的任務(wù)才能釋放它,每釋放一該遞歸互斥量,它的計(jì)數(shù)值就減 1。當(dāng)該互斥量的計(jì)數(shù)值為 0 時(shí)(即持有任務(wù)已經(jīng)釋放所有的持有操作),互斥量則變?yōu)殚_(kāi)鎖狀態(tài),等待在該互斥量上的任務(wù)將被喚醒。如果任務(wù)的優(yōu)先級(jí)被互斥量的優(yōu)先級(jí)翻轉(zhuǎn)機(jī)制臨時(shí)提升,那么當(dāng)互斥量被釋放后,任務(wù)的優(yōu)先級(jí)將恢復(fù)為原本設(shè)定的優(yōu)先級(jí)。
| 函數(shù) | #define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) ) |
|---|---|
| 參數(shù) | xMutex : 信號(hào)量句柄 |
| 返回值 | 成功返回 pdTRUE,否則返回 pdFALSE |
要想使用該函數(shù)必須在 FreeRTOSConfig.h 中把configUSE_RECURSIVE_MUTEXES 定義為 1 來(lái)使能。

四、示例
/* FreeRTOS 頭文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 開(kāi)發(fā)板硬件 bsp 頭文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任務(wù)句柄 ********************************/
/*
* 任務(wù)句柄是一個(gè)指針,用于指向一個(gè)任務(wù),當(dāng)任務(wù)創(chuàng)建好之后,它就具有了一個(gè)任務(wù)句柄
* 以后我們要想操作這個(gè)任務(wù)都需要通過(guò)這個(gè)任務(wù)句柄,如果是自身的任務(wù)操作自己,那么
* 這個(gè)句柄可以為 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 創(chuàng)建任務(wù)句柄 */
static TaskHandle_t LowPriority_Task_Handle = NULL;/* LowPriority_Task 任務(wù)句柄 */
static TaskHandle_t MidPriority_Task_Handle = NULL;/* MidPriority_Task 任務(wù)句柄 */
static TaskHandle_t HighPriority_Task_Handle = NULL;/* HighPriority_Task 任務(wù)句柄 */
/***************************** 內(nèi)核對(duì)象句柄 *****************************/
/*
* 信號(hào)量,消息隊(duì)列,事件標(biāo)志組,軟件定時(shí)器這些都屬于內(nèi)核的對(duì)象,要想使用這些內(nèi)核
* 對(duì)象,必須先創(chuàng)建,創(chuàng)建成功之后會(huì)返回一個(gè)相應(yīng)的句柄。實(shí)際上就是一個(gè)指針,后續(xù)我
* 們就可以通過(guò)這個(gè)句柄操作這些內(nèi)核對(duì)象。
*
* 內(nèi)核對(duì)象說(shuō)白了就是一種全局的數(shù)據(jù)結(jié)構(gòu),通過(guò)這些數(shù)據(jù)結(jié)構(gòu)我們可以實(shí)現(xiàn)任務(wù)間的通信,
* 任務(wù)間的事件同步等各種功能。至于這些功能的實(shí)現(xiàn)我們是通過(guò)調(diào)用這些內(nèi)核對(duì)象的函數(shù)
* 來(lái)完成的
*
*/
SemaphoreHandle_t MuxSem_Handle = NULL;
static void AppTaskCreate(void);/* 用于創(chuàng)建任務(wù) */
static void LowPriority_Task(void* pvParameters);/* LowPriority_Task 任務(wù)實(shí)現(xiàn) */
static void MidPriority_Task(void* pvParameters);/* MidPriority_Task 任務(wù)實(shí)現(xiàn) */
static void HighPriority_Task(void* pvParameters);/* HighPriority_Task 任務(wù)實(shí)現(xiàn) */
static void BSP_Init(void);/* 用于初始化板載相關(guān)資源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定義一個(gè)創(chuàng)建信息返回值,默認(rèn)為 pdPASS */
/* 開(kāi)發(fā)板硬件初始化 */
BSP_Init();
/* 創(chuàng)建 AppTaskCreate 任務(wù) */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,/* 任務(wù)入口函數(shù) */
(const char* )"AppTaskCreate",/* 任務(wù)名字 */
(uint16_t )512, /* 任務(wù)棧大小 */
(void* )NULL,/* 任務(wù)入口函數(shù)參數(shù) */
(UBaseType_t )1, /* 任務(wù)的優(yōu)先級(jí) */
(TaskHandle_t*)&AppTaskCreate_Handle);/* 任務(wù)控制塊指針 */
/* 啟動(dòng)任務(wù)調(diào)度 */
if (pdPASS == xReturn)
{
vTaskStartScheduler(); /* 啟動(dòng)任務(wù),開(kāi)啟調(diào)度 */
}
else
{
return -1;
}
while (1); /* 正常不會(huì)執(zhí)行到這里 */
}
/***********************************************************************
* @ 函數(shù)名 : AppTaskCreate
* @ 功能說(shuō)明: 為了方便管理,所有的任務(wù)創(chuàng)建函數(shù)都放在這個(gè)函數(shù)里面
* @ 參數(shù) : 無(wú)
* @ 返回值 : 無(wú)
***************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定義一個(gè)創(chuàng)建信息返回值,默認(rèn)為 pdPASS */
taskENTER_CRITICAL(); //進(jìn)入臨界區(qū)
/* 創(chuàng)建 MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if (NULL != MuxSem_Handle)
{
printf("MuxSem_Handle 互斥量創(chuàng)建成功!\r\n");
}
xReturn = xSemaphoreGive( MuxSem_Handle );//給出互斥量
/* 創(chuàng)建 LowPriority_Task 任務(wù) */
xReturn = xTaskCreate((TaskFunction_t )LowPriority_Task,/* 任務(wù)入口函數(shù) */
(const char* )"LowPriority_Task",/* 任務(wù)名字 */
(uint16_t )512, /* 任務(wù)棧大小 */
(void* )NULL, /* 任務(wù)入口函數(shù)參數(shù) */
(UBaseType_t )2, /* 任務(wù)的優(yōu)先級(jí) */
(TaskHandle_t* )&LowPriority_Task_Handle);/* 任務(wù)控制塊指針 */
if (pdPASS == xReturn)
{
printf("創(chuàng)建 LowPriority_Task 任務(wù)成功!\r\n");
}
/* 創(chuàng)建 MidPriority_Task 任務(wù) */
xReturn = xTaskCreate((TaskFunction_t )MidPriority_Task,/* 任務(wù)入口函數(shù) */
(const char* )"MidPriority_Task",/* 任務(wù)名字 */
(uint16_t )512, /* 任務(wù)棧大小 */
(void* )NULL, /* 任務(wù)入口函數(shù)參數(shù) */
(UBaseType_t )2, /* 任務(wù)的優(yōu)先級(jí) */
(TaskHandle_t* )&MidPriority_Task_Handle);/* 任務(wù)控制塊指針 */
if (pdPASS == xReturn)
{
printf("創(chuàng)建 MidPriority_Task 任務(wù)成功!\r\n");
}
/* 創(chuàng)建 HighPriority_Task 任務(wù) */
xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task,/* 任務(wù)入口函數(shù) */
(const char* )"HighPriority_Task",/* 任務(wù)名字 */
(uint16_t )512, /* 任務(wù)棧大小 */
(void* )NULL, /* 任務(wù)入口函數(shù)參數(shù) */
(UBaseType_t )2, /* 任務(wù)的優(yōu)先級(jí) */
(TaskHandle_t* )&HighPriority_Task_Handle);/* 任務(wù)控制塊指針 */
if (pdPASS == xReturn)
{
printf("創(chuàng)建 HighPriority_Task 任務(wù)成功!\r\n");
}
vTaskDelete(AppTaskCreate_Handle); //刪除 AppTaskCreate 任務(wù)
taskEXIT_CRITICAL(); //退出臨界區(qū)
}
/**********************************************************************
* @ 函數(shù)名 : LowPriority_Task
* @ 功能說(shuō)明: LowPriority_Task 任務(wù)主體
* @ 參數(shù) :
* @ 返回值 : 無(wú)
********************************************************************/
static void LowPriority_Task(void* parameter)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;/* 定義一個(gè)創(chuàng)建信息返回值,默認(rèn)為 pdPASS */
while (1)
{
printf("LowPriority_Task 獲取信號(hào)量\n");
//獲取互斥量 MuxSem,沒(méi)獲取到則一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待時(shí)間 */
if (pdTRUE == xReturn)
{
printf("LowPriority_Task Runing\n\n");
}
for (i=0; i<2000000; i++)
{ //模擬低優(yōu)先級(jí)任務(wù)占用互斥量
taskYIELD();//發(fā)起任務(wù)調(diào)度
}
printf("LowPriority_Task 釋放信號(hào)量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//給出互斥量
LED1_TOGGLE;
vTaskDelay(1000);
}
}
/**********************************************************************
* @ 函數(shù)名 : MidPriority_Task
* @ 功能說(shuō)明: MidPriority_Task 任務(wù)主體
* @ 參數(shù) :
* @ 返回值 : 無(wú)
********************************************************************/
static void MidPriority_Task(void* parameter)
{
while (1)
{
printf("MidPriority_Task Runing\n");
vTaskDelay(1000);
}
}
/**********************************************************************
* @ 函數(shù)名 : HighPriority_Task
* @ 功能說(shuō)明: HighPriority_Task 任務(wù)主體
* @ 參數(shù) :
* @ 返回值 : 無(wú)
********************************************************************/
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定義一個(gè)創(chuàng)建信息返回值,默認(rèn)為 pdPASS */
while (1)
{
printf("HighPriority_Task 獲取信號(hào)量\n");
//獲取互斥量 MuxSem,沒(méi)獲取到則一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待時(shí)間 */
if (pdTRUE == xReturn)
{
printf("HighPriority_Task Runing\n");
}
LED1_TOGGLE;
printf("HighPriority_Task 釋放信號(hào)量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//給出互斥量
vTaskDelay(1000);
}
}
/***********************************************************************
* @ 函數(shù)名 : BSP_Init
* @ 功能說(shuō)明: 板級(jí)外設(shè)初始化,所有板子上的初始化均可放在這個(gè)函數(shù)里面
* @ 參數(shù) :
* @ 返回值 : 無(wú)
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32 中斷優(yōu)先級(jí)分組為 4,即 4bit 都用來(lái)表示搶占優(yōu)先級(jí),范圍為:0~15
* 優(yōu)先級(jí)分組只需要分組一次即可,以后如果有其他的任務(wù)需要用到中斷,
* 都統(tǒng)一用這個(gè)優(yōu)先級(jí)分組,千萬(wàn)不要再分組,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
/* 按鍵初始化 */
Key_GPIO_Config();
}

? 由 Leung 寫(xiě)于 2020 年 11 月 23 日
? 參考:野火FreeRTOS視頻與PDF教程