FreeRTOS in-depth tutorial (in-depth task creation and task scheduling mechanism analysis)


Preface

This article will take you through an in-depth study of task creation and analysis of the task scheduling mechanism.

1. In-depth understanding of task creation

Create a task function prototype:

    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )

Only a few important parameters are explained here. Students who are not clear about other parameters can read previous articles:

Task creation

usStackDepth:任务栈的大小

Each task needs its own stack to save register values ​​and local variables.

In FreeRTOS, pvPortMalloc is used to apply for the stack, and the size is the passed usStackDepth * 4 bytes.

StackType_t * pxStack;

/* Allocate space for the stack used by the task being created. */
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );

The size of the stack allocation is determined by local variables and call depth.

TaskHandle_t:TCB控制块

Streamlined TCB mission control block:

typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    
    
    volatile StackType_t * pxTopOfStack; /*< Points to the location of 
    ListItem_t xStateListItem;                  /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t xEventListItem;                  /*< Used to reference a task from an event list. */
    UBaseType_t uxPriority;                     /*< The priority of the task.  0 is the lowest priority. */
    StackType_t * pxStack;                      /*< Points to the 
    char pcTaskName[ configMAX_TASK_NAME_LEN ];
} tskTCB;

The TCB control block stores important information about the task:

pxTopOfStack:这个参数指向任务堆栈的最顶部,即最近放入任务堆栈的项目的位置。这必须是 TCB 结构的第一个成员。

ListItem_t xStateListItem:这是一个用于任务状态管理的链表项。它用于将任务插入到就绪、阻塞或挂起状态链表中,以便操作系统可以有效地管理任务状态。

ListItem_t xEventListItem:这是用于将任务插入到事件列表中的链表项。当任务等待某个事件发生时,它会被插入到事件列表中。这允许任务在事件发生时被及时唤醒。

UBaseType_t uxPriority:这是任务的优先级。任务的优先级用于决定它在多任务系统中的调度顺序。较低的数值表示更高的优先级,0通常是最低优先级。

StackType_t * pxStack:这个参数指向任务堆栈的起始位置。任务堆栈是用于保存任务上下文信息的内存区域,包括寄存器值、局部变量等。

char pcTaskName[configMAX_TASK_NAME_LEN]:这个数组用于保存任务的名称,以便在调试和诊断中使用。configMAX_TASK_NAME_LEN 是一个配置参数,定义了任务名称的最大长度。

Then there is a question here: where are the functions in the created task and the parameters in the task saved.

创建任务中的函数其实就是一个函数指针也就是一个地址,当创建任务时PC会保存函数的地址,当任务被调用时,立刻从PC中取出地址跳转到函数中执行。

参数会保存在R0寄存器中。

2. Task scheduling mechanism

1. Task scheduling strategy in FreeRTOS

The scheduling of tasks in FreeRTOS supports 可抢占 and 时间片轮转.

可抢占:

In preemptible scheduling, tasks can be preempted by higher priority tasks. When a high-priority task becomes available, it can interrupt the currently executing low-priority task, causing the system to immediately switch to high-priority task execution.

In FreeRTOS, task scheduling is based on task priority. When a task preempts another task, it executes immediately, regardless of whether the preempted task has completed its time slice. This approach ensures that high-priority tasks can respond promptly and be executed immediately when needed, without being hindered by lower-priority tasks.

Determine whether to start preemption by configuringconfigUSE_PREEMPTION in FreeRTOS.

时间片轮转:
Time slice rotation means that the operating system assigns a time slice to each task, which is a predefined amount of time. In time slice round-robin scheduling, each task can execute a time slice, and then the system hands over control to the next ready task. If a task is not completed before the end of its time slice, the system pauses the task and hands control to the next ready task.

FreeRTOS allows you to enable or disable time slice rotation when configuring the system. The size of the time slice can be adjusted according to the needs of the application. This scheduling method helps ensure fairness among tasks, preventing certain tasks from occupying the processor for long periods of time, while allowing multiple tasks to share processing time.

Determine whether to start time slice rotation by configuringconfigUSE_TIME_SLICING in FreeRTOS.

组合应用

In FreeRTOS, preemptible and time slice round robin scheduling can be used together. This enables flexible task management, ensures that high-priority tasks can preempt low-priority tasks, and provides fair processor time to tasks, thereby effectively managing system resources.

2. The core of FreeRTOS task scheduling strategy implementation

In FreeRTOS, task management uses ready lists, blocking lists, and pending lists to manage the status and scheduling of tasks. These linked lists are used to maintain task lists in different states. Let’s learn about them one by one:

1.就绪链表(Ready List)
The ready list contains all tasks in the ready state. Tasks in the ready state are ready to run, but they cannot be executed immediately because the currently executing task is occupying CPU resources. These tasks are organized in a ready list according to priority. When the currently executing task releases the CPU (for example, due to the time slice running out, task blocking or suspension, etc.), the scheduler selects the highest priority task from the ready list for execution.

2.阻塞链表(Blocked List)
The blocking list contains tasks that cannot be executed immediately for some reason. These reasons may include waiting for an event, resource unavailability, delays, etc. When tasks are blocked, they are not executed by the scheduler. These tasks will be put back into the ready list after specific conditions are met, waiting for the scheduler to select them for execution.

3.挂起链表(Suspended List)
The suspended list contains tasks that have been explicitly suspended. When tasks are suspended, they temporarily stop running and no longer participate in scheduling. These tasks will not appear in the ready list or blocking list because they are explicitly suspended and do not participate in task scheduling.

在 FreeRTOS 中,任务的状态转换是动态的。任务可以从就绪状态变为阻塞状态或挂起状态,然后再返回到就绪状态。这些状态的变化取决于任务的执行和系统中的事件。管理任务状态的链表是 FreeRTOS 在调度和管理任务时使用的数据结构。这些链表确保了任务的有效调度和管理,以满足实时系统的要求。

These linked lists are part of FreeRTOS's internal task management, and developers can manage and operate the status of tasks and the tasks in the linked lists through the API functions provided by FreeRTOS.

3. FreeRTOS internal linked list source code analysis

FreeRTOS uses the following linked list to manage task scheduling:

PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1;                         /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;                         /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;              /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;      /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;                         /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */

pxReadyTasksLists:This is an array containing multiple linked lists, the number of which is equal to configMAX_PRIORITIES, which is used to store tasks in the ready state. Each linked list corresponds to a priority, so each element in the array stores ready tasks of the same priority. When a task is ready to run, it is added to a linked list of appropriate priority to wait to be selected for execution by the scheduler.

xDelayedTaskList1 和 xDelayedTaskList2:These two linked lists are used to store delayed and suspended tasks. Typically, xDelayedTaskList1 contains all delayed tasks that have not overflowed, while xDelayedTaskList2 is used to store tasks whose delays have overflowed. This design allows FreeRTOS to handle delayed tasks with different time frames. Delayed tasks will not be executed within the specified time period, but will be moved to the ready list after the delay expires.

pxDelayedTaskList 和 pxOverflowDelayedTaskList:These two pointer variables are used to point to the currently used delayed task list. Typically, pxDelayedTaskList points to one of xDelayedTaskList1 or xDelayedTaskList2, depending on the current delay. These linked lists are used to store delayed tasks in different time ranges.

xPendingReadyList:This linked list is used to store tasks that are ready to run when the scheduler is suspended. When the scheduler is in the suspended state, if any tasks become ready, they will be added to this linked list. When the scheduler is resumed, these tasks will be moved to the appropriate pxReadyTasksLists to wait to be scheduled for execution.

4. How to manage the execution order of tasks through the ready list

When creating a task, the task is added to the ready list through the prvAddNewTaskToReadyList function.

Insert image description here
When creating a task, when the priority of the newly created task is greater than or equal to the priority of the current task, the current task pointer of pxCurrentTCB points to the pointer of the newly added task of pxNewTCB.
Insert image description here

In the prvAddNewTaskToReadyList function, tasks of different priorities are added to different ready lists through the prvAddTaskToReadyList function:

Insert image description here
The vListInsertEnd function will add the newly created task to the last item of the current ready list.
Insert image description here

Let's give an example to verify the above code:

void vTask1( void *pvParameters )
{
    
    
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
    
    
		flagIdleTaskrun = 0;
		flagTask1run = 1;
		flagTask2run = 0;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T1\r\n");				
	}
}

void vTask2( void *pvParameters )
{
    
    	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
    
    
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 1;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T2\r\n");				
	}
}

void vTask3( void *pvParameters )
{
    
    	
	const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );		
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
    
    
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 0;
		flagTask3run = 1;
		
		/* 打印任务的信息 */
		printf("T3\r\n");				

		// 如果不休眠的话, 其他任务无法得到执行
		//vTaskDelay( xDelay5ms );
	}
}

xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);

Running result:
Insert image description here
The running result is that task 3 runs first.

Based on the above code analysis, a diagram can be drawn to represent:

First run Task3:

Second run Task1:

Third run Task2:

Insert image description here

3. How long can a task run?

1. High-priority tasks can preempt low-priority tasks and keep running.

void vTask1( void *pvParameters )
{
    
    
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
    
    
		flagIdleTaskrun = 0;
		flagTask1run = 1;
		flagTask2run = 0;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T1\r\n");		
	}
}

void vTask2( void *pvParameters )
{
    
    	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
    
    
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 1;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T2\r\n");				
	}
}

void vTask3( void *pvParameters )
{
    
    	
	const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );		
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
    
    
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 0;
		flagTask3run = 1;
		
		/* 打印任务的信息 */
		printf("T3\r\n");				

		// 如果不休眠的话, 其他任务无法得到执行
		//vTaskDelay( xDelay5ms );
	}
}

xTaskCreate(vTask1, "Task 1", 1000, NULL, 2, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);

operation result:

When configUSE_PREEMPTION is configured to 1, if high-priority tasks do not actively release the CPU, other low-priority tasks will not be able to execute.
Insert image description here

2. Tasks with the same priority follow time slice rotation

When configUSE_TIME_SLICING is configured to 1, tasks with the same priority will be executed in turn for one Tick.

xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);

operation result:

Insert image description here

4. How to release the CPU by tasks in FreeRTOS

1.任务主动让出CPU:

A task can call the vTaskDelay() function or the vTaskDelayUntil() function to suspend itself for a period of time so that other tasks can run. This method is a way for the task to actively give up the CPU.

2.阻塞等待事件:

Tasks can call blocking functions provided by FreeRTOS, such as xQueueReceive(), xSemaphoreTake(), etc., to wait for specific events to occur. When a task is waiting for an event, it is placed in a blocking state, freeing up the CPU and will not be awakened until the event occurs.

3.时间片轮转:

If the time slice rotation scheduling strategy is used, the task will automatically release the CPU when its time slice is exhausted, allowing other tasks to run. Time slice rotation is a strategy for fairly allocating CPU time. Each task has a small time slice to execute, and then is put back into the ready queue to wait for the next execution.

4.任务进入阻塞状态:

During the execution of the task, if certain blocking events occur, such as waiting for a queue to meet conditions, waiting for a mutex semaphore, etc., it will automatically enter the blocking state, and the CPU will be released. Once the blocking condition is met, the task will be put back into the ready state.

Summarize

This article provides an in-depth explanation of the internal implementation of task creation and the source code analysis and implementation of task scheduling. Studying this article will help you learn the source code of FreeRTOS more deeply.

Guess you like

Origin blog.csdn.net/m0_49476241/article/details/133973941