Real-time operating system Freertos pit study notes: (2): Creation and deletion of tasks


1. API functions for task creation

Example: pandas is a tool based on NumPy that was created to solve data analysis tasks.
As you can see, there are mainly two methods: dynamic creation and static creation.

1. The difference between dynamic creation and static creation

(1) For memory allocation and management :
dynamically create tasks: dynamically allocate the memory space required by the task at runtime, and release the memory space after the task is completed . This method can dynamically create and delete tasks according to the needs of tasks, and is suitable for scenarios where the number of tasks is uncertain or tasks need to be managed dynamically . It should be noted that dynamically creating tasks needs to allocate enough memory space on the heap to store the stack of the task.
Static creation of tasks: The memory space required by the task is allocated at compile time and will not be released at runtime . This method is suitable for cases where the number of tasks is fixed and known at compile time . The memory allocation of the statically created task is to define the structure and stack space of the task in the global scope, and use these statically allocated memory when the task is created.

2. Dynamically create task function prototypes

insert image description here
These parameters for dynamically creating task functions are extremely important.
(1) pxTaskCode: Pointer to the task function, indicating the function to be executed by the task .
The prototype of the task function is void vTaskFunction(void *pvParameters), where pvParameters are the parameters passed to the task function.

(2) pcName: task name, which is a string used to identify the name of the task . The maximum length is defined by the macro configMAX_TASK_NAME_LEN.

(3) usStackDepth: task stack size, in words. Indicates the size of the stack space required by the task . For a 32-bit microcontroller, one word is 4 bytes, so for example, the task stack is 128, which is equivalent to 512 bytes of dynamic space for this task.

(4) pvParameters: The parameters passed to the task function can be pointers of any type . Through this parameter, you can pass the required parameters to the task function. In fact, it is the function parameter.

(5) uxPriority: Task priority, ranging from 0 ~ configMAX_PRIORITIES - 1 . The larger the value, the higher the priority. The range here is 0-32.

(6) pxCreatedTask: Task handle, that is, the task control block of the task . Through this parameter, you can get the handle of the created task, which can be used for subsequent operations, such as deleting the task.

task handle

The task handle is a variable used to identify the task. It is used to uniquely identify a task in FreeRTOS. Through the task handle, tasks can be operated, such as suspend, resume, delete, etc.
The type of task handle is generally TaskHandle_t in FreeRTOS, which is actually a pointer to a task control block (TCB, Task Control Block). The task control block is a data structure used to store and manage task-related information, such as task status, priority, stack pointer, etc.
When creating a task, you can obtain the handle of the created task by passing a pointer to TaskHandle_t type as a parameter. In this way, the handle can be used to refer to the task in subsequent operations.
The main functions of the task handle are:
task creation: the handle of the created task can be obtained through the task handle, which can be used for subsequent operations.
Task suspending and resuming: You can use the task handle to suspend and resume the execution of a task.
Task deletion: You can use the task handle to delete a task.
Task query and status acquisition: You can use the task handle to query and obtain task status, priority and other information.

Process for creating dynamic tasks

Let me take the actual code as an example:

//创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,    //任务函数         
                (const char*    )"task1_task",   //函数名        
                (uint16_t       )TASK1_STK_SIZE,  //任务堆栈大小      
                (void*          )NULL,   //传参为空               
                (UBaseType_t    )TASK1_TASK_PRIO,   //任务优先级     
                (TaskHandle_t*  )&Task1Task_Handler); //任务句柄  
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )task2_task,     
                (const char*    )"task2_task",   
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler); 

The summary of punctual atoms is as follows: The macro is 1, indicating that the task form is dynamically created, and then the above code is implemented in two steps.
insert image description here
Internally, there is a TCB structure member assignment, TCB is the TASK CONTROL BLOCK task control block.

Introduction to the members of the task control block structure

insert image description here

3. Create task function prototype statically

insert image description here
Here, unlike the dynamic xTaskCreate function, the static xTaskCreateStatic function uses the user-allocated task control block (pxTaskBuffer) and task stack (puxStackBuffer) instead of dynamically allocated memory .

It should be noted that the allocation and management of task control blocks and task stacks are the responsibility of the user. Therefore, when using the xTaskCreateStatic function to create a task, you need to ensure that the allocated memory is sufficient and that the memory is no longer used after the task is created. In addition, the life cycle of the task control block and task stack must cover the entire task execution cycle.

The process of creating static tasks

insert image description here

//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
								   StackType_t **ppxIdleTaskStackBuffer, 
								   uint32_t *pulIdleTaskStackSize)
{
    
    
	*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
	*ppxIdleTaskStackBuffer=IdleTaskStack;
	*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

//获取定时器服务任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer:任务控制块内存
//ppxTimerTaskStackBuffer:任务堆栈内存
//pulTimerTaskStackSize:任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
									StackType_t **ppxTimerTaskStackBuffer, 
									uint32_t *pulTimerTaskStackSize)
{
    
    
	*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
	*ppxTimerTaskStackBuffer=TimerTaskStack;
	*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;
}

The creation tasks at the code level are basically the same as dynamic ones.
insert image description here

2. API function for task deletion

insert image description here
Example:

 //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄 



vTaskDelete(StartTask_Handler); //删除开始任务

I first created a start task, and then called the vTaskDelete() function to delete the task after executing the start task. The parameter passed was the task handle StartTask_Handler set earlier.

Note:
For dynamically created tasks, idle tasks will automatically release the memory allocated by the system for you when deleted. However, for statically created tasks, the user must release the memory in advance, otherwise memory leaks will occur.

The process of deleting tasks

insert image description here
The user level is very simple, just two steps, but it is actually very complicated internally.

3. Code routines for dynamic task creation and deletion

insert image description here
There is a way to write this, including subsequent code writing: the functional tasks that the user needs must be created in the start_task, and then the start task can be deleted. The start task is executed only once.

First use macro definition shortcomings to determine the relevant configuration for each task.insert image description here

task stack

(1) Why is the task stack setting 128 words?
Answer: This is a relatively large value, in order to make the stack space opened up by this task larger and avoid memory explosion.
(2) Is there a function that can view the historical remaining minimum value of the task stack of a specified task?
Answer: Yes. In FreeRTOS, you can use the vTaskGetStackHighWaterMark function to view the historical remaining minimum value of the task stack of a specified task.

The function prototype is as follows:
UBaseType_t vTaskGetStackHighWaterMark( TaskHandle_t xTask);
The parameter xTask is the handle of the task to be queried.

The function returns the historical remaining minimum value of the task stack, in words. This value represents the minimum number of bytes remaining in the task stack, that is, the maximum number of bytes used by the task stack.
You can monitor the usage of the task stack by periodically calling the vTaskGetStackHighWaterMark function to adjust the size of the task stack to prevent stack overflow.

Create four task functions

void freertos_demo(void)
{
    
        
    xTaskCreate((TaskFunction_t         )   start_task,
                (char *                 )   "start_task",
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   START_TASK_PRIO,
                (TaskHandle_t *         )   &start_task_handler );
    vTaskStartScheduler();
}


void start_task( void * pvParameters )
{
    
    
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
                
    xTaskCreate((TaskFunction_t         )   task3,
                (char *                 )   "task3",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &task3_handler );
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
}

Specific three function codes

/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
    
    
    while(1)
    {
    
    
        printf("task1正在运行!!!\r\n");
        LED0_TOGGLE();
        vTaskDelay(500);
    }
}

/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
    
    
    while(1)
    {
    
    
        printf("task2正在运行!!!\r\n");
        LED1_TOGGLE();
        vTaskDelay(500);
    }
}

/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{
    
    
    uint8_t key = 0;
    while(1)
    {
    
    
        printf("task3正在运行!!!\r\n");
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
    
    
            if(task1_handler != NULL)
            {
    
    
                printf("删除task1任务\r\n");
                vTaskDelete(task1_handler);
                task1_handler = NULL;
            }

        }
        vTaskDelay(10);
    }
}

specific effect

insert image description here
One question is: Why are low-priority tasks 1 and 2 executed first, and then task 3?
Answer: In the start task, the order of task1-task2-task3 is created. After task1 is created, its priority is higher than that of the start task, so it directly enters the task1 task first. Task1 enters the blocking state when the delay is 500ms, so that Go back to start to start the task, create task2 task, execute task2 task first, and then create task3 task after blocking, which leads to the situation as shown in the figure.

So how to avoid this situation? ---- Use critical section
The purpose of entering the critical section is to protect the operation of the task list. Before creating a task, enter the critical section by calling the taskENTER_CRITICAL() function. Then two tasks are created: task1_task and task2_task. The process of creating a task may involve modification operations to the task list, such as adding the task's control block and stack information to the task list. By entering the critical section, you can ensure that only one task can access the task list during task creation, avoiding race conditions and data consistency issues.

4. The internal process of dynamically creating tasks

The xTaskCreate() function performs a series of operations internally to create a task.

Task stack allocation : First, the xTaskCreate() function will allocate a piece of memory space according to the specified task stack size (TASKx_STK_SIZE) as the task stack . The stack is a space used to save local variables and function call information of the task.
②Creation of task control block : Next, the xTaskCreate() function will create a task control block, also known as a task control structure (TCB). The task control block contains task status information, priority, stack top pointer and other task-related parameters .
③Initialize the task control block : xTaskCreate() function will initialize the task control block, including setting the task priority, stack pointer, status and other information.
④Add the task to the ready queue : After the task is created, the xTaskCreate() function will add the task to the ready queue so that the task can be scheduled by the scheduler.
Task execution : Once a task is added to the ready queue, the scheduler will determine the execution order of the task according to the priority of the task and the scheduling algorithm . A task will execute within its assigned time slice until it is actively suspended or preempted by another task.

5. The internal process of deleting tasks

In FreeRTOS, the process of deleting a task can be achieved by calling the vTaskDelete() function. The vTaskDelete() function performs the following internal processes:

①Task voluntary suspension: First, the vTaskDelete() function will voluntarily suspend the current task. This means that the current task will enter the suspended state, no longer participate in scheduling, and wait to be deleted.
② Release task resources: The vTaskDelete() function will release the resources occupied by the current task, including the task's control block and stack space. This way, these resources can be used by other tasks.
③ Remove from the task list: The vTaskDelete() function will remove the current task from the task list, so that the scheduler will no longer schedule the task.
④ Cleanup operations for deleting tasks: The vTaskDelete() function will perform some cleaning operations, such as releasing the semaphore held by the task or deleting the message queue of the task.
⑤Task switching: Once the vTaskDelete() function completes the task deletion operation, the scheduler will select a new task to execute. This new task can be another task in the ready queue, or an idle task if there are no other tasks to execute.

6. The method of statically creating tasks

insert image description here
insert image description here
The first one is an advance statement during dynamic creation, and the second one is a static creation task. The difference between the two is that static requires creating a static task control block.

1. Why do we need to create idle tasks and timer tasks when creating static tasks?

The purpose of creating idle tasks and timer tasks is to provide basic functions and scheduling of the system.

Idle Task: An idle task is a special task in the system that is executed when no other tasks need to be run. The function of idle tasks is to prevent the system from entering an infinite loop and ensure the normal operation of the system. Idle tasks are scheduled for execution when there are no other tasks in the system that need to be run. It usually performs some low-priority operations, such as system hibernation, energy-saving mode, etc.
The static task control block and stack of the idle task are created to allocate the memory space of the idle task and use these memory spaces to create the idle task when the system starts .

Timer Task: The timer task is a special task used to manage the system's software timer. Software timer is a feature provided by FreeRTOS for triggering tasks or events within a specific time interval. The timer task is responsible for managing and processing software timer-related operations, including creating, deleting, starting, and stopping timers, etc.
The static task control block and stack of the timer task are created to allocate the memory space of the timer task and use these memory spaces to create the timer task when the system starts.

By creating static task control blocks and stacks for idle tasks and timer tasks, you can allocate sufficient memory space for these tasks and use this memory space to create and manage these tasks at system startup. This can ensure the normal operation of the basic functions and scheduling of the system.

2. Why do you not need to create idle tasks and timer tasks when creating dynamic tasks?

When creating dynamic tasks, the reason why you do not need to explicitly create idle tasks and timer tasks is because FreeRTOS automatically creates and manages these tasks .

Idle task: FreeRTOS will automatically create an idle task named "Idle" and execute it when the system has no other tasks to run. The priority of the idle task is the lowest, and it will perform some low-priority operations, such as system hibernation, energy-saving mode, etc. Because idle tasks are automatically created and managed, you do not need to explicitly create idle tasks when creating tasks dynamically.

Timer task: FreeRTOS will automatically create a timer task named "Timer" to manage the system's software timer. The timer task is responsible for managing and processing software timer-related operations, including creating, deleting, starting, and stopping timers, etc. Because timer tasks are automatically created and managed, you do not need to explicitly create timer tasks when creating tasks dynamically.

When creating a dynamic task, you only need to call the task creation function provided by FreeRTOS and pass in the task-related parameters to create a dynamic task. FreeRTOS automatically performs task scheduling and management, including the creation and execution of idle tasks and timer tasks.

7. Summary

insert image description here
insert image description here
insert image description here

Guess you like

Origin blog.csdn.net/qq_53092944/article/details/132521251