FreeRTOS study notes (5)-mutex

One, the header file

#include "FreeRTOS.h"
#include "semphr.h"

Two, mutual exclusion

2.1 Basic concepts

Mutex, also known as mutual exclusion semaphore (essentially a semaphore), is a special binary semaphore, which is different from a semaphore in that it supports mutex ownership, recursive access, and features to prevent priority inversion , Used to achieve exclusive treatment of critical resources. There are only two states of mutex at any time, unlocked or locked . When the mutex is held by the task, the mutex is in a locked state, and the task obtains the ownership of the mutex. When the task releases the mutex, the mutex is in an unlocked state, and the task loses ownership of the mutex. When a task holds a mutex, other tasks can no longer unlock or hold the mutex. The task holding the mutex can also obtain the lock again without being suspended. This is the recursive access, which is the characteristic of the recursive mutex. This characteristic is very different from the general semaphore. In, because there is no available semaphore, when the task recursively acquires the semaphore, the active suspension of the task will eventually form a deadlock.

If you want to use for synchronization (between tasks or between tasks and interrupt) implementation, a binary semaphore may be a better choice, although the task and interrupt synchronization mutex can also be used for tasks and task, but mutual Expulsion is more an interlock used to protect resources.

The mutex used for interlocking can act as a token to protect resources. When a task wants to access a certain resource, it must first obtain the token. When the task has finished using the resource, it must return the token so that other tasks can access the resource. Is it very familiar? It is the same in our binary semaphore, which is used to protect critical resources and ensure orderly access to multitasking. Only when the task acquires the semaphore can it start using the protected resources, and release the semaphore when it is used up, and the next task can acquire the semaphore so that the protected resources can be used. But another potential problem that semaphores can cause is task priority inversion. The mutexes provided by FreeRTOS can use priority inheritance algorithms to reduce the impact of priority inversion problems. Therefore, it is generally recommended to use mutexes for the protection of critical resources.

2.2 Operation mechanism


When using mutexes to handle simultaneous access to critical resources by different tasks, tasks can only access resources if they want to obtain the mutex. Once a task successfully obtains the mutex, the mutex will immediately become locked. Other tasks will not be able to access this resource because they cannot obtain the mutex. The task will wait according to the user-defined waiting time. After the mutex is released by the held task, other tasks can obtain the mutex and gain access. The critical resource, the mutex is locked again at this time, so that it can be ensured that only one task is accessing the critical resource at any time, ensuring the safety of critical resource operations.

2.3 Mutex and Recursive Mutex

  • Mutex is more suitable for situations that may cause priority inversion.
  • Recursive mutexes are more suitable for situations where tasks may acquire mutexes multiple times. This can avoid deadlocks caused by multiple recursive holdings of the same task.

3. Related API description

3.1 xSemaphoreCreateMutex

Used to create a mutex and return a mutex handle.

function #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
parameter no
return value Mutex handle

To use this function must be FreeRTOSConfig.hin the configSUPPORT_DYNAMIC_ALLOCATIONdefined to be enabled.

At the same time we must be FreeRTOSConfig.hin the configUSE_MUTEXESdefined to be enabled.

3.2 xSemaphoreCreateRecursiveMutex

Used to create a recursive mutex, a mutex that is not recursive is created by the function xSemaphoreCreateMutex() or xSemaphoreCreateMutexStatic(), and can only be acquired by the same task once. If the same task wants to acquire it again, it will fail. Recursive semaphore is the opposite. It can be acquired many times by the same task, and it needs to be released as many times as it is acquired. The recursive semaphore, like the mutex, implements the priority inheritance mechanism, which can reduce the anti-generation of priority inversion.

function #define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
parameter no
return value Recursive mutex handle

To use this function must be FreeRTOSConfig.hin the configSUPPORT_DYNAMIC_ALLOCATIONdefined to be enabled.

At the same time we must be FreeRTOSConfig.hin the configUSE_RECURSIVE_MUTEXESdefined to be enabled.

3.3 vSemaphoreDelete

Used to delete a semaphore, including binary semaphore, counting semaphore, mutex and recursive
mutex. If there are tasks blocked on the semaphore, do not delete the semaphore.

function void vSemaphoreDelete( SemaphoreHandle_t xSemaphore )
parameter xSemaphore: semaphore handle
return value no

3.4 xSemaphoreTake

It is used to obtain semaphore without interrupt protection. The acquired semaphore objects can be binary semaphores, counting semaphores and mutexes, but recursive mutexes cannot be acquired using this API function.

function xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime )
parameter xSemaphore: semaphore handle
xBlockTime: the maximum timeout time for waiting for the semaphore to be available, in tick (that is, the system tick period). If the macro INCLUDE_vTaskSuspend is defined as 1 and the formal parameter xTicksToWait is set to portMAX_DELAY, the task will always be blocked on the semaphore (that is, there is no timeout period)
return value Return pdTRUE on success, otherwise return errQUEUE_EMPTY

3.5 xSemaphoreTakeRecursive

The macro used to obtain the recursive mutex is the same as the mutex acquisition function. xSemaphoreTakeRecursive() is also a macro definition. It finally uses the existing queue mechanism. The actual execution function is xQueueTakeMutexRecursive(). Before obtaining the recursive mutex, it must be created by the function xSemaphoreCreateRecursiveMutex(). It should be noted that this function cannot be used to obtain the mutex created by the function xSemaphoreCreateMutex().

function #define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
parameter xMutex: semaphore handle
xBlockTime: if it is not a task that holds a mutex to obtain an invalid mutex, the task will wait for the user to specify a timeout period, in tick (ie the system tick period). If the macro INCLUDE_vTaskSuspend is defined as 1 and the formal parameter xTicksToWait is set to portMAX_DELAY, the task will always be blocked on the recursive mutex (that is, there is no timeout)
return value Return pdTRUE on success, otherwise return errQUEUE_EMPTY

To use this function must be FreeRTOSConfig.hin the configUSE_RECURSIVE_MUTEXESdefined to be enabled.

3.6 xSemaphoreGive

The macro used to release the semaphore. The released semaphore object must have been created, and can be used to release binary semaphores, counting semaphores, and mutexes, but cannot release the recursive mutex created by the function xSemaphoreCreateRecursiveMutex(). In addition, this function cannot be used in interrupts.

function xSemaphoreGive( SemaphoreHandle_t xSemaphore )
parameter xSemaphore: semaphore handle
return value Return pdTRUE on success, otherwise return pdFALSE

3.7 xSemaphoreGiveRecursive

Used to release a recursive mutex. Tasks that have already acquired the recursive mutex can repeatedly acquire the recursive mutex. If you use the xSemaphoreTakeRecursive() function to successfully obtain the recursive mutex several times, you must use the xSemaphoreGiveRecursive() function to return it several times. Before that, the recursive mutex is in an invalid state, and other tasks cannot obtain the recursive mutex. When using this function interface, only the task that has the ownership of the mutex can release it. Each time the recursive mutex is released, its count value is reduced by 1. When the count value of the mutex is 0 (that is, the holding task has released all holding operations), the mutex becomes unlocked, and the tasks waiting on the mutex will be awakened. If the priority of the task is temporarily increased by the priority inversion mechanism of the mutex, then when the mutex is released, the priority of the task will return to the originally set priority.

function #define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
parameter xMutex: semaphore handle
return value Return pdTRUE on success, otherwise return pdFALSE

To use this function must be FreeRTOSConfig.hin the configUSE_RECURSIVE_MUTEXESdefined to be enabled.

Four, example

/* FreeRTOS 头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 开发板硬件 bsp 头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t LowPriority_Task_Handle = NULL;/* LowPriority_Task 任务句柄 */
static TaskHandle_t MidPriority_Task_Handle = NULL;/* MidPriority_Task 任务句柄 */
static TaskHandle_t HighPriority_Task_Handle = NULL;/* HighPriority_Task 任务句柄 */
 
/***************************** 内核对象句柄 *****************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
SemaphoreHandle_t MuxSem_Handle = NULL;

static void AppTaskCreate(void);/* 用于创建任务 */ 
static void LowPriority_Task(void* pvParameters);/* LowPriority_Task 任务实现 */
static void MidPriority_Task(void* pvParameters);/* MidPriority_Task 任务实现 */
static void HighPriority_Task(void* pvParameters);/* HighPriority_Task 任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

int main(void)
{
    
    
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */

    /* 开发板硬件初始化 */
    BSP_Init();
    /* 创建 AppTaskCreate 任务 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,/* 任务入口函数 */
                          (const char* )"AppTaskCreate",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL,/* 任务入口函数参数 */
                          (UBaseType_t )1, /* 任务的优先级 */
                          (TaskHandle_t*)&AppTaskCreate_Handle);/* 任务控制块指针 */
    /* 启动任务调度 */
    if (pdPASS == xReturn)
    {
    
    
        vTaskStartScheduler(); /* 启动任务,开启调度 */
    }
    else
    {
    
    
        return -1;
    } 

    while (1); /* 正常不会执行到这里 */
}

/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
***************************************************************/
static void AppTaskCreate(void)
{
    
    
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
  
    taskENTER_CRITICAL(); //进入临界区
   
    /* 创建 MuxSem */ 
    MuxSem_Handle = xSemaphoreCreateMutex();
    if (NULL != MuxSem_Handle) 
    {
    
    
        printf("MuxSem_Handle 互斥量创建成功!\r\n"); 
    }

    xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
  
    /* 创建 LowPriority_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )LowPriority_Task,/* 任务入口函数 */
                          (const char* )"LowPriority_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t* )&LowPriority_Task_Handle);/* 任务控制块指针 */
    if (pdPASS == xReturn)
    {
    
    
        printf("创建 LowPriority_Task 任务成功!\r\n");
    }
  
    /* 创建 MidPriority_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )MidPriority_Task,/* 任务入口函数 */
                          (const char* )"MidPriority_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t* )&MidPriority_Task_Handle);/* 任务控制块指针 */
    if (pdPASS == xReturn)
    {
    
    
        printf("创建 MidPriority_Task 任务成功!\r\n");
    }

    /* 创建 HighPriority_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task,/* 任务入口函数 */
                          (const char* )"HighPriority_Task",/* 任务名字 */
                          (uint16_t )512, /* 任务栈大小 */
                          (void* )NULL, /* 任务入口函数参数 */
                          (UBaseType_t )2, /* 任务的优先级 */
                          (TaskHandle_t* )&HighPriority_Task_Handle);/* 任务控制块指针 */
    if (pdPASS == xReturn)
    {
    
    
        printf("创建 HighPriority_Task 任务成功!\r\n");
    }

    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
    
    taskEXIT_CRITICAL(); //退出临界区
}

/**********************************************************************
* @ 函数名 : LowPriority_Task
* @ 功能说明: LowPriority_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void LowPriority_Task(void* parameter) 
{
    
     
    static uint32_t i; 
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 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++) 
        {
    
     //模拟低优先级任务占用互斥量 
            taskYIELD();//发起任务调度 
        } 

        printf("LowPriority_Task 释放信号量!\r\n"); 
        xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量 

        LED1_TOGGLE; 

        vTaskDelay(1000); 
    }
} 

/**********************************************************************
* @ 函数名 : MidPriority_Task
* @ 功能说明: MidPriority_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void MidPriority_Task(void* parameter) 
{
    
     
    while (1) 
    {
    
     
        printf("MidPriority_Task Runing\n"); 
        vTaskDelay(1000); 
    } 
}

/**********************************************************************
* @ 函数名 : HighPriority_Task
* @ 功能说明: HighPriority_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void HighPriority_Task(void* parameter) 
{
    
     
    BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 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); 
    }
} 

/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
    
    
    /*
    * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
    * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
    * 都统一用这个优先级分组,千万不要再分组,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
   
    /* LED 初始化 */
    LED_GPIO_Config();
  
    /* 串口初始化 */
    USART_Config();
   
    /* 按键初始化 */
    Key_GPIO_Config();     
}


Written by Leung on November 23, 2020

• Reference: Wildfire FreeRTOS video and PDF tutorial

Guess you like

Origin blog.csdn.net/qq_36347513/article/details/110003247