FreeRTOS學(xué)習(xí)筆記(5)——互斥量

一、頭文件

#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教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容