FreeRTOS_mutually exclusive semaphore of semaphore

Table of Contents

1. Mutually exclusive semaphore

1.1 Introduction to mutually exclusive semaphores

1.2 Create a mutually exclusive semaphore

1.2.1 Function xSemaphoreCreateMutex()

1.2.2 Function xSemaphoreCreateMutexStatic()

1.2.3 Analysis of the Mutex Semaphore Creation Process

1.2.4 Release the mutex semaphore

1.2.5 Obtain mutex semaphore

2. Mutually exclusive semaphore operation experiment

2.1 Experimental procedures

2.1.1 main.c

2.1.2 Experimental phenomena


1. Mutually exclusive semaphore

1.1 Introduction to mutually exclusive semaphores

A mutually exclusive semaphore is actually a binary semaphore with priority inheritance. In synchronization applications (synchronization between tasks and tasks or interrupts and tasks), binary semaphores are most suitable. Mutex semaphores are suitable for applications that require mutually exclusive access. In mutually exclusive access, a mutually exclusive semaphore is equivalent to a key. When a task wants to use a resource, it must first obtain the key. After using the resource, the key must be returned so that other tasks can hold this key. Key to use resources.

The mutex semaphore uses the same API operation function as the binary semaphore, so the mutex semaphore can also set the blocking time. Unlike the binary semaphore, the mutex semaphore has the feature of priority inheritance. When a mutex semaphore is being used by a low-priority task, and a high-priority task also attempts to obtain the mutex semaphore, it will be blocked. This high-priority task will raise the priority of the low-priority task to the same priority as itself. This process is priority inheritance. Priority inheritance reduces the time that high-priority tasks are blocked as much as possible and minimizes the impact of “priority flips” that have already occurred.

Priority inheritance reduces the time that high-priority tasks are blocked as much as possible and minimizes the impact of “priority flips” that have already occurred.
This means: the low-priority task obtains the mutually exclusive semaphore. At this time, the high-priority task cannot access the mutually exclusive semaphore, and other medium-priority tasks cannot obtain the mutually exclusive semaphore. In this way, the low-priority task cannot obtain the mutually exclusive semaphore. High-priority tasks cannot be interrupted by medium-priority tasks. High-priority tasks only need to wait for low-priority tasks to release the mutex semaphore, and do not need to worry about being interrupted by other medium-priority tasks; this is why lowering high-priority tasks The time the level task is in the blocked state reduces the possibility of priority flipping!

Priority inheritance does not completely eliminate priority flipping, it only reduces the impact of priority flipping as much as possible. Hard real-time applications should be designed to avoid priority flipping from the beginning.

Mutex semaphores cannot be used in interrupt service functions:

Mutex semaphores have a priority inheritance mechanism and can only be used in tasks and cannot be used in interrupt service functions.

In the interrupt service function, the blocking time cannot be set to enter the blocking state because it needs to wait for the mutex semaphore.

1.2 Create a mutually exclusive semaphore

FreeRTOS provides two mutually exclusive semaphore creation functions.

function:

xSemaphoreCreateMutex() creates a mutex semaphore using dynamic methods

xSemaphoreCreateMutexStatic() creates a mutex semaphore using a static method

1.2.1 Function xSemaphoreCreateMutex()

This function is used to create a mutex semaphore, and the required memory is allocated through dynamic memory management methods. This function is essentially a macro. What actually completes the creation of the semaphore is the function xQueueCreateMutex(). The prototype of this function is as follows:

SemaphoreHandle_t xSemaphoreCreateMutex(void)

parameter:

none.

return value:

NULL: Mutex semaphore creation failed.

Other values: The handle of the mutex semaphore that was successfully created.

1.2.2 Function xSemaphoreCreateMutexStatic()

This function also creates a mutually exclusive semaphore, but if you use this function to create a mutually exclusive semaphore, the RAM required for the semaphore needs to be allocated by the user. This function is a macro, and the specific creation process is completed through the function xQueueCreateMutexStatic() , the function prototype is as follows:

SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer)

parameter:

pxMutexBuffer: This parameter points to a variable of type StaticSemaphore_t, used to save the semaphore structure.

return value:

NULL: Mutex semaphore creation failed.

Other values: The handle of the mutex semaphore that was successfully created.

1.2.3 Analysis of Mutually Exclusive Semaphore Creation Process

Here we only analyze the dynamically created mutex semaphore function xSemaphoreCreateMutex(). This function is a macro and is defined as follows:

#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)

It can be seen that the real thing is the function xQueueCreateMutex(). This function is defined as follows in the file queue.c,

QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType)
{
    Queue_t *pxNewQueue;
    const UBaseType_t uxMutexLength = (UBaseType_t) 1, uxMutexSize = (UBaseType_t) 0;
 
    pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, (1)
                                                    ucQueueType );
    prvInitialiseMutex( pxNewQueue ); (2)
 
    return pxNewQueue;
}

(1) Call the function xQueueGenericCreate() to create a queue with a queue length of 1, a queue item length of 0, and the queue type as the parameter ucQueueType. Since this function creates a mutually exclusive semaphore, the parameter ucQueueType is queueQUEUE_TYPE_MUTEX.

(2) Call the function prvInitialiseMutex() to initialize the mutually exclusive semaphore.

Function prvInitialiseMutex() initializes the mutex semaphore code as follows:

static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
    if(pxNewQueue != NULL)
    {
        //Although the member variables of the queue structure will be initialized when the queue is created, what is created at this time is mutual exclusion.
        //Semaphore, so some member variables need to be reassigned, especially those used for priority inheritance.
        pxNewQueue->pxMutexHolder = NULL; (1)
        pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; (2)
 
        //If it is a recursive mutex semaphore.
        pxNewQueue->u.uxRecursiveCallCount = 0; (3)
 
        traceCREATE_MUTEX( pxNewQueue );
 
        //Release the mutex semaphore
        ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U,
                                    queueSEND_TO_BACK );
    }
    else
    {
        traceCREATE_MUTEX_FAILED();
    }
} 

(1) and (2), there are no two member variables pxMutexHolder and uxQueueType in Queue_t in the queue structure. These two member variables are actually a macro, specially prepared for mutually exclusive semaphores. They are in the file queue.c As defined below:

#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL

When Queue_t is used to represent a queue, pcHead and pcTail point to the storage area of the queue. When Queue_t is used to represent a mutually exclusive semaphore, pcHead and pcTail are not needed. When used for a mutex semaphore, point pcHead to NULL to indicate that pcTail holds the owner of the mutex queue, and pxMutexHolder points to the task control block of the task that owns the mutex semaphore. pcTail and pcHead were renamed to improve code readability.

(3) If the semaphore created is a recursive mutually exclusive semaphore, the member variable u.uxRecursiveCallCount in the queue structure also needs to be initialized.

After the mutually exclusive semaphore is successfully created, the function xQueueGenericSend() will be called to release the semaphore once, indicating that the mutually exclusive semaphore is valid by default!

1.2.4 Release mutex semaphore

When releasing a mutually exclusive semaphore, just like binary semaphores and counting semaphores, the function xSemaphoreGive() is used (actually the function xQueueGenericSend() is used to release the semaphore). Since mutually exclusive semaphores involve priority inheritance, the specific processing procedures will be different. The most important step in using the function xSemaphoreGive() to release a semaphore is to increment uxMessageWaiting by one, and this step is completed through the function prvCopyDataToQueue(). The function xQueueGenericSend() that releases the semaphore will call prvCopyDataToQueue(). The priority inheritance of mutually exclusive semaphores is also completed in the function prvCopyDataToQueue(). This function has the following code:

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
                                      const void * pvItemToQueue,
                                      const BaseType_t xPosition )
{
    BaseType_t xReturn = pdFALSE;
    UBaseType_t uxMessagesWaiting;
 
    uxMessagesWaiting = pxQueue->uxMessagesWaiting;
 
    if(pxQueue->uxItemSize == (UBaseType_t) 0)
    {
        #if (configUSE_MUTEXES == 1) //Mutually exclusive semaphore
        {
             if(pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) (1)
             {
                 xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder ); (2)
                 pxQueue->pxMutexHolder = NULL; (3)
             }
             else
             {
                 mtCOVERAGE_TEST_MARKER();
             }
         }
         #endif /* configUSE_MUTEXES */
     }
 
    /****************************************************** ************************/
    /******************************Omit other processing codes************************ *******/
    /****************************************************** ************************/
 
    pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
 
    return xReturn;
} 

(1). The current operation is a mutually exclusive semaphore.

(2) Call the function xTaskPriorityDisinherit() to handle the priority inheritance issue of the mutually exclusive semaphore.

(3) After the mutex semaphore is released, the mutex semaphore no longer belongs to any task, so pxMutexHolder must point to NULL.

The code of function xTaskPriorityDisinherit() is as follows:

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
    TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
    BaseType_t xReturn = pdFALSE;
 
    if( pxMutexHolder != NULL ) (1)
    {
        //When a task obtains a mutex semaphore, it will involve the issue of priority inheritance and the mutex is being released.
        //The task of the semaphore must be the currently running task pxCurrentTCB.
        configASSERT(pxTCB == pxCurrentTCB);
        configASSERT(pxTCB->uxMutexesHeld);
 
        (pxTCB->uxMutexesHeld)--; (2)
 
        //Is there priority inheritance? If it exists, the current priority of the task must be different from the base priority of the task.
        if( pxTCB->uxPriority != pxTCB->uxBasePriority ) (3)
        {
            //The current task only obtains one mutually exclusive semaphore
            if(pxTCB->uxMutexesHeld == (UBaseType_t) 0) (4)
            {
                if( uxListRemove( & amp;( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (5)
                {
                    taskRESET_READY_PRIORITY( pxTCB->uxPriority ); (6)
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
 
                //Add the task back to the ready list with the new priority
                traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
                                        pxTCB->uxPriority = pxTCB->uxBasePriority; (7)
 
                /* Reset the event list item value. It cannot be in use for
                any other purpose if this task is running, and it must be
                running to give back the mutex. */
                listSET_LIST_ITEM_VALUE( & amp;( pxTCB->xEventListItem ), \ (8)
                (TickType_t) configMAX_PRIORITIES - \
                ( TickType_t ) pxTCB->uxPriority );
                prvAddTaskToReadyList( pxTCB ); (9)
                xReturn = pdTRUE; (10)
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
 
    return xReturn;
}

(1) The parameter pxMutexHolder of the function indicates that the task control block owns this mutex semaphore, so it is necessary to first determine whether the mutex semaphore has been acquired by other tasks.

(2) Some tasks may obtain multiple mutually exclusive semaphores, so it is necessary to mark the number of mutually exclusive semaphores currently obtained by the task. The member variable uxMutexesHeld of the task control block structure is used to save the mutually exclusive semaphores obtained by the current task. The number of semaphores. Every time the task releases the mutex semaphore, the variable uxMutexesHeld must be decremented by one.

(3) Determine whether there is priority inheritance. If it exists, the current priority of the task is definitely not equal to the base priority of the task. (The base priority of a task refers to the fixed priority assigned to the task when it is created. It is a value used to schedule tasks. The higher the value, the higher the priority)

(4) Determine whether the current release is the last mutex semaphore obtained by the task, because if the task also obtains other mutex semaphores, it cannot handle priority inheritance. Priority inheritance must be processed when the last mutex semaphore is released.

(5) The processing of priority inheritance is to reduce the current priority of the task to the base priority of the task, so the current task must be removed from the task ready list first. When the task priority is restored to its original priority, it is re-added to the ready list.

FreeRTOS is a real-time operating system (RTOS) used in the development of embedded systems. Priority inheritance is a scheduling strategy used to solve the priority flip problem.

In a multitasking system, tasks can have different priorities, and higher priority tasks can preempt lower priority tasks. However, priority inversion problems may arise when task dependencies exist.

The priority flipping problem is when a task with a lower priority occupies a shared resource, and a task with a higher priority is blocked waiting for the resource. At this time, higher priority tasks cannot run, resulting in reduced system response performance.

Through priority inheritance, when a high-priority task needs to use the resources occupied by a low-priority task, the priority of the low-priority task will be temporarily raised to the same priority as the high-priority task to ensure that the high-priority task The required resources can be obtained as quickly as possible. When the high-priority task releases the resource, the priority of the low-priority task is restored to its original state.

(6) If there are no other tasks in the ready list corresponding to the priority inherited by the task, the ready state of this priority will be cancelled.

(7) Reset the priority of the task to the base priority of the task uxBasePriority.

(8). Reset the event list item of the task.

(9) Add the tasks with restored priorities back to the task ready list.

(10). Return pdTRUE, indicating that task scheduling is required.

1.2.5 Get the mutually exclusive semaphore

The function to obtain a mutually exclusive semaphore is the same as the function to obtain a binary semaphore and a counting semaphore. They are all xSemaphoreTake() (the function that actually performs the semaphore acquisition is xQueueGenericReceive()). The process of obtaining a mutually exclusive semaphore also requires To deal with the issue of priority inheritance, the function xQueueGenericReceive() is defined in the file queue.c)

2. Mutually exclusive semaphore operation experiment

This experiment designed four tasks: start_task, high_task, middle_task, low_task. The task functions of these four tasks are as follows:

start_task: used to create three other tasks.

high_task: A high-priority task will obtain the mutually exclusive semaphore. After successful acquisition, the corresponding processing will be performed. After the processing is completed, the mutually exclusive semaphore will be released.

middle_task: medium priority task, a simple application task.

low_task: Low-priority tasks, like high-priority tasks, will obtain the mutex semaphore. After successful acquisition, corresponding processing will be performed. However, the difference is that low-priority tasks occupy the mutex semaphore for a longer time. (Software simulation occupation).

In the experiment, a mutex semaphore MutexSemaphore was created. The two tasks of high priority and low priority will use this mutex semaphore.

2.1 Experimental Procedure

2.1.1 main.c

#include "stm32f4xx.h"
#include "FreeRTOS.h" //Note here that you must first reference the FreeRTOS header file, and then reference task.h
#include "task.h" //There is a sequential relationship
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//task priority
#define START_TASK_PRIO 1 //Used to create other three tasks
//Task stack size
#define START_STK_SIZE 256
//task handle
TaskHandle_t StartTask_Handler;
//task function
void start_task(void *pvParameters);

//task priority
#define LOW_TASK_PRIO 2 //Low priority tasks will obtain the mutually exclusive semaphore. After successful acquisition, corresponding processing will be performed. The mutually exclusive semaphore will be occupied for a longer time.
//Task stack size
#define LOW_STK_SIZE 256
//task handle
TaskHandle_t LowTask_Handler;
//task function
void low_task(void *pvParameters);

//task priority
#define MIDDLE_TASK_PRIO 3 //Medium priority task, a simple application task
//Task stack size
#define MIDDLE_STK_SIZE 256
//task handle
TaskHandle_t MiddleTask_Handler;
//task function
void middle_task(void *pvParameters);

//task priority
#define HIGH_TASK_PRIO 4 //High priority tasks will obtain the mutually exclusive semaphore. After successful acquisition, the corresponding processing will be performed. After the processing is completed, the mutually exclusive semaphore will be released.
//Task stack size
#define HIGH_STK_SIZE 256
//task handle
TaskHandle_t HighTask_Handler;
//task function
void high_task(void *pvParameters);

//mutually exclusive semaphore handle
SemaphoreHandle_t MutexSemaphore; //mutually exclusive semaphore

//The color used when LCD refreshes the screen
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN); //Initialize the internal memory pool
    
    POINT_COLOR=RED;
    LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(30,50,200,16,16,"Mutex Semaphore");
    LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,90,200,16,16,"2023/10/31");
    
    //Create a start task
    xTaskCreate((TaskFunction_t)start_task, //task function
                (const char* )"start_task", //Task name
                (uint16_t )START_STK_SIZE, //Task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )START_TASK_PRIO, //Task priority
                (TaskHandle_t* ) & amp;StartTask_Handler);//Task handle
    vTaskStartScheduler(); //Enable task scheduling
}

//Start task task function
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); //Enter the critical section
    
    //Create a mutually exclusive semaphore
    MutexSemaphore=xSemaphoreCreateMutex(); //Create a mutually exclusive semaphore function and return a successfully created mutually exclusive semaphore handle
    
    
    //Create high priority tasks
    xTaskCreate((TaskFunction_t)high_task, //task function
                (const char* )"high_task", //task name
                (uint16_t )HIGH_STK_SIZE, //Task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )HIGH_TASK_PRIO, //task priority
                (TaskHandle_t* ) & amp;HighTask_Handler);//Task handle
    //Create medium priority tasks
    xTaskCreate((TaskFunction_t)middle_task, //task function
                (const char* )"middle_task", //task name
                (uint16_t )MIDDLE_STK_SIZE, //Task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )MIDDLE_TASK_PRIO, //task priority
                (TaskHandle_t* ) & amp;MiddleTask_Handler);//Task handle
    //Create low priority tasks
    xTaskCreate((TaskFunction_t)low_task, //task function
                (const char* )"low_task", //task name
                (uint16_t )LOW_STK_SIZE, //Task stack size
                (void* )NULL, //Parameters passed to the task function
                (UBaseType_t )LOW_TASK_PRIO, //task priority
                (TaskHandle_t* ) & amp;LowTask_Handler);//Task handle
    vTaskDelete(StartTask_Handler);
    taskEXIT_CRITICAL(); //Exit the critical section
}

//High priority task task function
void high_task(void *pvParameters)
{
    u8num;
    
    POINT_COLOR=BLACK;
    LCD_DrawRectangle(5,110,115,314); //Draw a rectangle
    LCD_DrawLine(5,130,115,130); //Draw line
    POINT_COLOR=BLUE;
    LCD_ShowString(6,111,110,16,16,"High Task");
    
    while(1)
    {
        vTaskDelay(500); //Delay 500ms, which is 500 clock beats
        num + + ;
        printf("high task Pend Semaphore\r\\
");
        xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //Get the mutually exclusive semaphore
        //The blocking time for acquiring the mutually exclusive semaphore is set to infinite waiting. Since this task can acquire the mutually exclusive semaphore, there is always a time when the mutually exclusive semaphore can be acquired.
        //Otherwise the program will get stuck here
        printf("high task Running!\r\\
"); //Get the mutually exclusive semaphore, and the high-priority task starts running
        LCD_Fill(6,131,114,313,lcd_discolor[num]); //Fill area
        LED1=!LED1;
        xSemaphoreGive(MutexSemaphore); //Release the mutex semaphore. When the high-priority task acquires the mutex semaphore and completes the corresponding processing, the semaphore will be released.
        vTaskDelay(500); //Delay 500ms, which is 500 clock beats
    }
}

//Task function for medium priority tasks
void middle_task(void *pvParameters)
{
    u8num;
    
    POINT_COLOR=BLACK;
    LCD_DrawRectangle(125,110,234,314); //Draw a rectangle
    LCD_DrawLine(125,130,234,130); //Draw line
    POINT_COLOR=BLUE;
    LCD_ShowString(126,111,110,16,16,"Middle Task");
    
    while(1)
    {
        num + + ;
        printf("middle task Running!\r\\
");
        LCD_Fill(126,131,233,313,lcd_discolor[13-num]); //Fill the area upside down
        LED0=!LED0;
        vTaskDelay(1000); //Delay 1s, which is 1000 clock beats
    }
}

//Task function for low priority tasks
//Low priority tasks occupy the mutex semaphore for a longer time
void low_task(void *pvParameters)
{
    static u32 times;
    
    while(1)
    {
        xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //Get binary semaphore
        printf("low task Running!\r\\
");
        for(times=0;times<20000000;times + + ) //Simulate low priority occupying binary semaphore
        {
            taskYIELD(); //Initiate task scheduling
            //This also ensures that low-priority tasks occupy the binary semaphore for a longer time
            //Because once I initiate task scheduling, the binary semaphore preempted by low priority cannot be preempted by high priority tasks.
        }
        xSemaphoreGive(MutexSemaphore); //Release binary semaphore
        vTaskDelay(1000); //Delay 1s, which is 1000 clock beats
    }
}




2.1.2 Experimental Phenomenon

Analyze the data output through the above serial port!

First, there is a delay in high-priority tasks, so medium-priority tasks seize the CPU. After the medium-priority tasks run for a time slice, the low-priority tasks obtain the mutually exclusive semaphore, and the high-priority tasks seize the CPU usage rights after the delay time expires. , the high-priority task requests the semaphore and waits for the low-priority task to release the mutex semaphore. At this time, the medium-priority task will not run. Because of the priority inheritance of the mutex semaphore, the low-priority task temporarily obtains and high priority Tasks of equal priority have the same priority. After waiting for the low-priority task to release the mutex semaphore, the high-priority task can run. After the high-priority task releases the semaphore, task scheduling, the medium-priority task runs, and the high-priority task requests the signal. At this time, the semaphore is acquired by the high-priority task, the high-priority task runs, then the medium task runs, and the low-priority task runs! ! !

The essence of a mutually exclusive semaphore is that a low-priority task is using a mutually exclusive semaphore, and a high-priority task requests the use of a mutually exclusive semaphore. At this time, the priority of the low-priority task will be temporarily raised to that of the high-priority task. Tasks are at one level. At this time, the promoted tasks will not be interrupted by other priority tasks, ensuring that high-priority tasks obtain mutually exclusive semaphores as soon as possible and improving the response performance of the system. After the high-priority task releases the semaphore, the promoted task returns to its previous priority.

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 57203 people are learning the system