一、頭文件
#include "FreeRTOS.h"
#include "semphr.h"
二眷细、互斥量
2.1 基本概念
互斥量又稱互斥信號量(本質(zhì)是信號量),是一種特殊的二值信號量礼搁,它和信號量不同的是宙搬,它支持互斥量所有權(quán)笨腥、遞歸訪問以及防止優(yōu)先級翻轉(zhuǎn)的特性,用于實現(xiàn)對臨界資源的獨占式處理勇垛。任意時刻互斥量的狀態(tài)只有兩種脖母,開鎖或閉鎖。當(dāng)互斥量被任務(wù)持有時闲孤,該互斥量處于閉鎖狀態(tài)谆级,這個任務(wù)獲得互斥量的所有權(quán)。當(dāng)該任務(wù)釋放這個互斥量時讼积,該互斥量處于開鎖狀態(tài)肥照,任務(wù)失去該互斥量的所有權(quán)。當(dāng)一個任務(wù)持有互斥量時勤众,其他任務(wù)將不能再對該互斥量進行開鎖或持有建峭。持有該互斥量的任務(wù)也能夠再次獲得這個鎖而不被掛起,這就是遞歸訪問决摧,也就是遞歸互斥量的特性亿蒸,這個特性與一般的信號量有很大的不同,在信號量中掌桩,由于已經(jīng)不存在可用的信號量边锁,任務(wù)遞歸獲取信號量時會發(fā)生主動掛起任務(wù)最終形成死鎖。
如果想要用于實現(xiàn)同步(任務(wù)之間或者任務(wù)與中斷之間)波岛,二值信號量或許是更好的選擇茅坛,雖然互斥量也可以用于任務(wù)與任務(wù)、任務(wù)與中斷的同步,但是互斥量更多的是用于保護資源的互鎖贡蓖。
用于互鎖的互斥量可以充當(dāng)保護資源的令牌曹鸠,當(dāng)一個任務(wù)希望訪問某個資源時,它必須先獲取令牌斥铺。當(dāng)任務(wù)使用完資源后彻桃,必須還回令牌,以便其它任務(wù)可以訪問該資源晾蜘。是不是很熟悉邻眷,在我們的二值信號量里面也是一樣的,用于保護臨界資源剔交,保證多任務(wù)的訪問井然有序肆饶。當(dāng)任務(wù)獲取到信號量的時候才能開始使用被保護的資源,使用完就釋放信號量岖常,下一個任務(wù)才能獲取到信號量從而可用使用被保護的資源驯镊。但是信號量會導(dǎo)致的另一個潛在問題,那就是任務(wù)優(yōu)先級翻轉(zhuǎn)竭鞍。而 FreeRTOS 提供的互斥量可以通過優(yōu)先級繼承算法阿宅,可以降低優(yōu)先級翻轉(zhuǎn)問題產(chǎn)生的影響,所以笼蛛,用于臨界資源的保護一般建議使用互斥量洒放。
2.2 運作機制
用互斥量處理不同任務(wù)對臨界資源的同步訪問時,任務(wù)想要獲得互斥量才能進行資源訪問滨砍,如果一旦有任務(wù)成功獲得了互斥量往湿,則互斥量立即變?yōu)殚]鎖狀態(tài),此時其他任務(wù)會因為獲取不到互斥量而不能訪問這個資源惋戏,任務(wù)會根據(jù)用戶自定義的等待時間進行等待领追,直到互斥量被持有的任務(wù)釋放后,其他任務(wù)才能獲取互斥量從而得以訪問該臨界資源响逢,此時互斥量再次上鎖绒窑,如此一來就可以確保每個時刻只有一個任務(wù)正在訪問這個臨界資源,保證了臨界資源操作的安全性舔亭。
2.3 互斥量與遞歸互斥量
- 互斥量更適合于可能會引起優(yōu)先級翻轉(zhuǎn)的情況些膨。
- 遞歸互斥量更適用于任務(wù)可能會多次獲取互斥量的情況下。這樣可以避免同一任務(wù)多次遞歸持有而造成死鎖的問題钦铺。
三订雾、相關(guān)API說明
3.1 xSemaphoreCreateMutex
用于創(chuàng)建一個互斥量,并返回一個互斥量句柄矛洞。
函數(shù) | #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX ) |
---|---|
參數(shù) | 無 |
返回值 | 互斥量句柄 |
要想使用該函數(shù)必須在 FreeRTOSConfig.h
中把 configSUPPORT_DYNAMIC_ALLOCATION
定義為 1 來使能洼哎。
同時必須在
FreeRTOSConfig.h
中把 configUSE_MUTEXES
定義為 1 來使能。3.2 xSemaphoreCreateRecursiveMutex
用于創(chuàng)建一個遞歸互斥量,不是遞歸的互斥量由函數(shù) xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()創(chuàng)建噩峦,且只能被同一個任務(wù)獲取一次锭沟,如果同一個任務(wù)想再次獲取則會失敗。遞歸信號量則相反识补,它可以被同一個任務(wù)獲取很多次族淮,獲取多少次就需要釋放多少次。遞歸信號量與互斥量一樣李请,都實現(xiàn)了優(yōu)先級繼承機制,可以減少優(yōu)先級反轉(zhuǎn)的反生厉熟。
函數(shù) | #define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX ) |
---|---|
參數(shù) | 無 |
返回值 | 遞歸互斥量句柄 |
要想使用該函數(shù)必須在 FreeRTOSConfig.h
中把 configSUPPORT_DYNAMIC_ALLOCATION
定義為 1 來使能导盅。
同時必須在
FreeRTOSConfig.h
中把 configUSE_RECURSIVE_MUTEXES
定義為 1 來使能。3.3 vSemaphoreDelete
用于刪除一個信號量揍瑟,包括二值信號量白翻,計數(shù)信號量,互斥量和遞歸互斥量绢片。如果有任務(wù)阻塞在該信號量上滤馍,那么不要刪除該信號量。
函數(shù) | void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ) |
---|---|
參數(shù) | xSemaphore: 信號量句柄 |
返回值 | 無 |
3.4 xSemaphoreTake
用于獲取信號量底循,不帶中斷保護巢株。獲取的信號量對象可以是二值信號量、計數(shù)信號量和互斥量熙涤,但是遞歸互斥量并不能使用這個 API 函數(shù)獲取阁苞。
函數(shù) | xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime ) |
---|---|
參數(shù) |
xSemaphore: 信號量句柄 xBlockTime: 等待信號量可用的最大超時時間,單位為 tick(即系統(tǒng)節(jié)拍周期)祠挫。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為 portMAX_DELAY 那槽,則任務(wù)將一直阻塞在該信號量上(即沒有超時時間) |
返回值 | 成功返回 pdTRUE,否則返回 errQUEUE_EMPTY |
3.5 xSemaphoreTakeRecursive
用于獲取遞歸互斥量的宏等舔,與互斥量的獲取函數(shù)一樣骚灸,xSemaphoreTakeRecursive()也是一個宏定義,它最終使用現(xiàn)有的隊列機制慌植,實際執(zhí)行的函數(shù)是 xQueueTakeMutexRecursive() 甚牲。 獲取遞歸互斥量之前必須由 xSemaphoreCreateRecursiveMutex() 這個函數(shù)創(chuàng)建。要注意的是該函數(shù)不能用于獲取由函數(shù) xSemaphoreCreateMutex() 創(chuàng)建的互斥量蝶柿。
函數(shù) | #define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) ) |
---|---|
參數(shù) |
xMutex: 信號量句柄 xBlockTime: 如果不是持有互斥量的任務(wù)去獲取無效的互斥量鳖藕,那么任務(wù)將進行等待用戶指定超時時間,單位為 tick(即系統(tǒng)節(jié)拍周期)只锭。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設(shè)置為portMAX_DELAY 著恩,則任務(wù)將一直阻塞在該遞歸互斥量上(即沒有超時時間) |
返回值 | 成功返回 pdTRUE,否則返回 errQUEUE_EMPTY |
要想使用該函數(shù)必須在 FreeRTOSConfig.h
中把configUSE_RECURSIVE_MUTEXES
定義為 1 來使能。
3.6 xSemaphoreGive
用于釋放信號量的宏喉誊。釋放的信號量對象必須是已經(jīng)被創(chuàng)建的邀摆,可以用于二值信號量、計數(shù)信號量伍茄、互斥量的釋放栋盹,但不能釋放由函數(shù) xSemaphoreCreateRecursiveMutex() 創(chuàng)建的遞歸互斥量。此外該函數(shù)不能在中斷中使用敷矫。
函數(shù) | xSemaphoreGive( SemaphoreHandle_t xSemaphore ) |
---|---|
參數(shù) | xSemaphore: 信號量句柄 |
返回值 | 成功返回 pdTRUE例获,否則返回 pdFALSE |
3.7 xSemaphoreGiveRecursive
用于釋放一個遞歸互斥量。已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量曹仗。使用 xSemaphoreTakeRecursive() 函數(shù)成功獲取幾次遞歸互斥量榨汤,就要使用 xSemaphoreGiveRecursive() 函數(shù)返還幾次,在此之前遞歸互斥量都處于無效狀態(tài)怎茫,別的任務(wù)就無法獲取該遞歸互斥量收壕。使用該函數(shù)接口時,只有已持有互斥量所有權(quán)的任務(wù)才能釋放它轨蛤,每釋放一該遞歸互斥量蜜宪,它的計數(shù)值就減 1。當(dāng)該互斥量的計數(shù)值為 0 時(即持有任務(wù)已經(jīng)釋放所有的持有操作)祥山,互斥量則變?yōu)殚_鎖狀態(tài)圃验,等待在該互斥量上的任務(wù)將被喚醒。如果任務(wù)的優(yōu)先級被互斥量的優(yōu)先級翻轉(zhuǎn)機制臨時提升缝呕,那么當(dāng)互斥量被釋放后损谦,任務(wù)的優(yōu)先級將恢復(fù)為原本設(shè)定的優(yōu)先級。
函數(shù) | #define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) ) |
---|---|
參數(shù) | xMutex : 信號量句柄 |
返回值 | 成功返回 pdTRUE岳颇,否則返回 pdFALSE |
要想使用該函數(shù)必須在 FreeRTOSConfig.h
中把configUSE_RECURSIVE_MUTEXES
定義為 1 來使能照捡。
四、示例
/* FreeRTOS 頭文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 開發(fā)板硬件 bsp 頭文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任務(wù)句柄 ********************************/
/*
* 任務(wù)句柄是一個指針话侧,用于指向一個任務(wù)栗精,當(dāng)任務(wù)創(chuàng)建好之后,它就具有了一個任務(wù)句柄
* 以后我們要想操作這個任務(wù)都需要通過這個任務(wù)句柄瞻鹏,如果是自身的任務(wù)操作自己悲立,那么
* 這個句柄可以為 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)核對象句柄 *****************************/
/*
* 信號量新博,消息隊列薪夕,事件標(biāo)志組,軟件定時器這些都屬于內(nèi)核的對象赫悄,要想使用這些內(nèi)核
* 對象原献,必須先創(chuàng)建馏慨,創(chuàng)建成功之后會返回一個相應(yīng)的句柄。實際上就是一個指針姑隅,后續(xù)我
* 們就可以通過這個句柄操作這些內(nèi)核對象写隶。
*
* 內(nèi)核對象說白了就是一種全局的數(shù)據(jù)結(jié)構(gòu),通過這些數(shù)據(jù)結(jié)構(gòu)我們可以實現(xiàn)任務(wù)間的通信讲仰,
* 任務(wù)間的事件同步等各種功能慕趴。至于這些功能的實現(xiàn)我們是通過調(diào)用這些內(nèi)核對象的函數(shù)
* 來完成的
*
*/
SemaphoreHandle_t MuxSem_Handle = NULL;
static void AppTaskCreate(void);/* 用于創(chuàng)建任務(wù) */
static void LowPriority_Task(void* pvParameters);/* LowPriority_Task 任務(wù)實現(xiàn) */
static void MidPriority_Task(void* pvParameters);/* MidPriority_Task 任務(wù)實現(xiàn) */
static void HighPriority_Task(void* pvParameters);/* HighPriority_Task 任務(wù)實現(xiàn) */
static void BSP_Init(void);/* 用于初始化板載相關(guān)資源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定義一個創(chuàng)建信息返回值,默認(rèn)為 pdPASS */
/* 開發(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)先級 */
(TaskHandle_t*)&AppTaskCreate_Handle);/* 任務(wù)控制塊指針 */
/* 啟動任務(wù)調(diào)度 */
if (pdPASS == xReturn)
{
vTaskStartScheduler(); /* 啟動任務(wù)鄙陡,開啟調(diào)度 */
}
else
{
return -1;
}
while (1); /* 正常不會執(zhí)行到這里 */
}
/***********************************************************************
* @ 函數(shù)名 : AppTaskCreate
* @ 功能說明: 為了方便管理冕房,所有的任務(wù)創(chuàng)建函數(shù)都放在這個函數(shù)里面
* @ 參數(shù) : 無
* @ 返回值 : 無
***************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定義一個創(chuàng)建信息返回值,默認(rèn)為 pdPASS */
taskENTER_CRITICAL(); //進入臨界區(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)先級 */
(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)先級 */
(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)先級 */
(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
* @ 功能說明: LowPriority_Task 任務(wù)主體
* @ 參數(shù) :
* @ 返回值 : 無
********************************************************************/
static void LowPriority_Task(void* parameter)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;/* 定義一個創(chuàng)建信息返回值趁矾,默認(rèn)為 pdPASS */
while (1)
{
printf("LowPriority_Task 獲取信號量\n");
//獲取互斥量 MuxSem,沒獲取到則一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待時間 */
if (pdTRUE == xReturn)
{
printf("LowPriority_Task Runing\n\n");
}
for (i=0; i<2000000; i++)
{ //模擬低優(yōu)先級任務(wù)占用互斥量
taskYIELD();//發(fā)起任務(wù)調(diào)度
}
printf("LowPriority_Task 釋放信號量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//給出互斥量
LED1_TOGGLE;
vTaskDelay(1000);
}
}
/**********************************************************************
* @ 函數(shù)名 : MidPriority_Task
* @ 功能說明: MidPriority_Task 任務(wù)主體
* @ 參數(shù) :
* @ 返回值 : 無
********************************************************************/
static void MidPriority_Task(void* parameter)
{
while (1)
{
printf("MidPriority_Task Runing\n");
vTaskDelay(1000);
}
}
/**********************************************************************
* @ 函數(shù)名 : HighPriority_Task
* @ 功能說明: HighPriority_Task 任務(wù)主體
* @ 參數(shù) :
* @ 返回值 : 無
********************************************************************/
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定義一個創(chuàng)建信息返回值耙册,默認(rèn)為 pdPASS */
while (1)
{
printf("HighPriority_Task 獲取信號量\n");
//獲取互斥量 MuxSem,沒獲取到則一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待時間 */
if (pdTRUE == xReturn)
{
printf("HighPriority_Task Runing\n");
}
LED1_TOGGLE;
printf("HighPriority_Task 釋放信號量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//給出互斥量
vTaskDelay(1000);
}
}
/***********************************************************************
* @ 函數(shù)名 : BSP_Init
* @ 功能說明: 板級外設(shè)初始化,所有板子上的初始化均可放在這個函數(shù)里面
* @ 參數(shù) :
* @ 返回值 : 無
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32 中斷優(yōu)先級分組為 4愈魏,即 4bit 都用來表示搶占優(yōu)先級觅玻,范圍為:0~15
* 優(yōu)先級分組只需要分組一次即可想际,以后如果有其他的任務(wù)需要用到中斷培漏,
* 都統(tǒng)一用這個優(yōu)先級分組,千萬不要再分組胡本,切忌牌柄。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
/* 按鍵初始化 */
Key_GPIO_Config();
}
? 由 Leung 寫于 2020 年 11 月 23 日
? 參考:野火FreeRTOS視頻與PDF教程