[FreeRTOS check-in station 3 is open] Task status and switching, closing time is August 20
[Copy link]
Activity Overview: Click here to view (including activity encouragement and activity learning content)
Check-in start and end time for this site: August 18th - August 20th (3 days)
Check-in tasks:
1. Read Cruelfox's third article: FreeRTOS Learning Notes (3) Task Status and Switching
2. Reply to this thread: In FreeRTOS task programs, the xTaskDelay() function is often used to achieve the purpose of delayed execution. If xTaskDelay(20) is called in a task, and you want to wait for 20 time units, but the delay operation exceeds 25 time units, please analyze the possible reasons?
FreeRTOS Learning Notes (3) Task Status and Switching
For your convenience, I copied cruelfox's " FreeRTOS Learning Notes (3) Task Status and Switching " here~
FreeRTOS tasks have the following states:
run |
Running |
Ready |
Ready |
block |
Blocked |
Suspend |
Suspended |
The states other than the running state are collectively referred to as the non-running state. Because FreeRTOS is designed for a single CPU system, at any time, only one task can be in the running state, even if it seems that there are multiple tasks running at the same time - this is just the effect of multiple tasks switching constantly. When a task switches from the running state to the non-running state, the execution scene - the CPU registers are saved in the task's private stack; when it returns to the running state, the previously saved registers are restored from the stack. This is the most basic function of task scheduling.
Ready task
The ready state of a FreeRTOS task means that the task is not currently being executed, but can be executed at any time. When the next task switching opportunity comes, FreeRTOS will select the task with the highest priority from the list of ready tasks and switch it to the running state.
Task priority is a property of a task. FreeRTOS uses an integer to represent the priority, with the lowest priority being 0 and the highest being the value defined by the configMAX_PRIORITIES macro minus 1. A priority needs to be specified when creating a task, and it can also be changed after creation through the vTaskPrioritySet() function. The meaning of priority is that when a task is in the ready state, as long as there is a task with a higher priority than it in the ready state, it will not get the opportunity to execute.
When there are two or more tasks of the same priority in the ready state, they will take turns to get the opportunity to execute (but the execution time is not guaranteed to be evenly distributed), and the system will not favor any one of them.
Timing of task switching
FreeRTOS will inevitably switch tasks in the following situations:
(A) The running task calls vTaskSuspend() to suspend itself.
(B) The running task calls the delay function and requires waiting for a period of time before executing.
(C) The running task needs to wait for a synchronization event that has not occurred.
Because the current task actively suspends execution, FreeRTOS needs to switch it to the suspended or blocked state, and then select the highest priority task from the list of ready tasks to execute. If there are no other tasks that need to be executed, the system's own Idle task with the lowest priority will also be executed. There is also a special case that
(D) the running task calls taskYIELD() to actively request to switch tasks.
At this time, if there is a task with the same or higher priority in the ready task list, a task switch will occur, otherwise the current task will be returned to continue execution.
If there is configUSE_PREEMPITON=1 in the FreeRTOS configuration, preemptive task scheduling will be performed. The meaning of preemption is that when the newly added tasks in the task ready list (including new tasks, tasks resumed from the suspended state, and tasks exited from the blocked state) and the tasks whose priorities have been modified have a higher priority than the currently running task, they will be immediately switched to the higher priority task for execution. The previously running task does not know when its execution is suspended, so it is called "preempted".
Preemptive scheduling ensures that the highest priority task will immediately obtain processor resources once the running conditions are ripe. However, if there is more than one task with the highest priority, they will not preempt each other by default, unless configUSE_TIME_SLICING=1 is configured, and FreeRTOS performs time slice management. After enabling time slice management, ready tasks of the same priority are executed in turn, and each task executes at most one fixed cycle each time, basically evenly distributing processor resources. To summarize, under preemptive scheduling, the timing of task switching is:
(E) The running task is preempted by a higher priority task.
(F) The running task is preempted by a task of the same priority at the end of a time slice.
Let's borrow an example from the manual. When there is no preemptive scheduling, it is like the following figure: Task3 is not blocked and needs to actively hand over control to switch to the ready Task1 and Task2.
While preemptive scheduling is as follows: Always immediately schedule to the ready task with a higher priority.
The preemptive scheduling of the same priority task in turn with time slices is enabled: Task2 and Idle task are both of the lowest priority, and they are executed in turn, and they must switch at the end of the time slice.
Implementation of task switching
Analyze the details of the implementation and pick a simple one to start with: vTaskSuspend() function, which is used to adjust a task state to the suspended state.
- void vTaskSuspend( TaskHandle_t xTaskToSuspend)
- {
- TCB_t *pxTCB;
- taskENTER_CRITICAL();
- pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
- if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
- {
- taskRESET_READY_PRIORITY( pxTCB->uxPriority );
- }
- if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
- {
- ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
- }
- vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
- taskEXIT_CRITICAL();
- if( xSchedulerRunning != pdFALSE )
- {
- taskENTER_CRITICAL();
- prvResetNextTaskUnblockTime();
- taskEXIT_CRITICAL();
- }
- if( pxTCB == pxCurrentTCB )
- {
- if( xSchedulerRunning != pdFALSE )
- {
- portYIELD_WITHIN_API();
- }
- else
- {
- if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
- {
- pxCurrentTCB = NULL;
- }
- else
- {
- vTaskSwitchContext();
- }
- }
- }
- }
Copy code
This function has three parts: the first is to delete the xStateListItem member in the task TCB structure from the state list, and then insert it into the suspended task list; and delete the xEventListItem member from the list. The second part is to execute prvResetNextTaskUnblockTime() if the scheduler is working. The third part is to perform task scheduling if the current task is suspended: portYIELD_WITHIN_API() or vTaskSwitchContext() switches to the unsuspended task when the scheduler is not working.
Ignore the operations related to the two critical sections taskENTER_CRITICAL() and taskEXIT_CRITICAL(). It is a bit strange that the uxListRemove() function only requires one parameter. You need to check list.c and list.h to understand that the FreeRTOS list is implemented using a doubly linked list data structure to understand: from the xStateListItem list member, you can index the entire list. When the state list to which the task belongs is empty, do you need to execute taskRESET_READY_PRIORITY() to reset the ready state of this priority level? It's hard to understand, I have to trace the code... It turns out that each priority has a list of ready tasks, not just one list for all ready tasks as I thought. The following lists are globally defined in tasks.c:
- /* Lists for ready and blocked tasks. ------------------*/
- PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
- PRIVILEGED_DATA static List_t xDelayedTaskList1;
- PRIVILEGED_DATA static List_t xDelayedTaskList2;
- PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
- PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
- PRIVILEGED_DATA static List_t xPendingReadyList;
Copy code
It can be guessed that FreeRTOS tasks must be in a certain list (running tasks are in the ready list); when task scheduling occurs, the list must be adjusted. The
second step of vTastSuspend() is to reset the global variable xNextTaskUnblockTime, because the suspended task may be in a blocked state waiting for a delay, and its influence needs to be eliminated. The
third step is task scheduling, which is implemented by the scheduler using portYIELD() (otherwise the scheduler is not running...this will be analyzed later). And portYIELD() is implemented in portmacro.h for the ARM Cortex-m3 platform as follows:
- #define portYIELD() \
- { \
- portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
- __asm volatile( "dsb" ); \
- __asm volatile( "isb" ); \
- }
Copy code
What this macro does is to set the PendSV status bit, which will cause the PendSV exception, and then implement the task switch in the PendSV ISR handler. Now we can boldly guess that FreeRTOS uses the PendSV exception handler to perform task switching. Continue to analyze this handler, which is an assembly implementation function in port.c:
- void xPortPendSVHandler(void)
- {
- __asm volatile (
- " mrs r0, psp \n"
- "isb\n"
- " ldr r3, pxCurrentTCBConst \n"
- " ldr r2, [r3] \n"
- " stmdb r0!, {r4-r11} \n"
- " str r0, [r2] \n"
- " stmdb sp!, {r3, r14} \n"
- " mov r0, %0 \n"
- " msr basepri, r0 \n"
- " bl vTaskSwitchContext \n"
- " mov r0, #0 \n"
- " msr basepri, r0 \n"
- " ldmia sp!, {r3, r14} \n"
- " ldr r1, [r3] \n"
- " ldr r0, [r1] \n"
- " ldmia r0!, {r4-r11} \n"
- " msr psp, r0 \n"
- "isb\n"
- " bx r14 \n"
- " .align 4 \n"
- "pxCurrentTCBConst: .word pxCurrentTCB \n"
- ::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY) );
- }
Copy code
Because before entering the interrupt, 8 registers are saved in the PSP stack: r0, r1, r2, r3, r12, LR, PC, xPSR, r0 to r3 can be used without any worries. First read the current task TCB address to r2, save the registers from r4 to r11 to the task stack, and then save the new task stack top (here is the r0 register) in the TCB (the first member of the TCB is the stack top). Next, call the vTaskSwitchContext() function to switch the context, then get the current task TCB address to r1, read the task stack top from the TCB, pop the register from the stack, set the PSP stack register, and finally exit the exception handling.
Because the PSP register has been changed, the stack before entering the exception and the stack after exiting the exception belong to different tasks. From the perspective of a task, it seems that nothing has changed after calling the portYIELD() function. If the vTaskSwitchContext() function does nothing, the above exception handling will not do any substantial operations and will still return to the current task.
Let's see how context switching is implemented: In fact, the key is
taskSELECT_HIGHEST_PRIORITY_TASK();
This is another macro, defined as
- {
- UBaseType_t uxTopPriority = uxTopReadyPriority;
- while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )
- {
- --uxTopPriority;
- }
- listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
- uxTopReadyPriority = uxTopPriority;
- }
Copy code
It is not difficult to understand. It searches downward from the highest priority until it finds a ready task list that is not empty. It selects a task from it, sets the current TCB address to its TCB address, and then saves the priority of the global ready state. So the main thing vTaskSwitchContext() does is to select the task to be executed and set the current pxCurrentTCB global variable.
How does the time slice work?
Since the time slice is needed, there must be hardware timer interrupt support. FreeRTOS calls this interrupt a tick interrupt, and what timer is used to implement it depends on the platform. After checking the source code, I confirmed that for ARM Cortex-m0/m3, Systick Timer is used, which is a timer provided by the ARM core instead of a timer device on the APB bus. This makes full use of the hardware and is compatible with MCUs from different manufacturers.
The timer interrupt function is relatively simple
- void xPortSysTickHandler(void)
- {
- portDISABLE_INTERRUPTS();
- {
- if( xTaskIncrementTick() != pdFALSE )
- {
- portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
- }
- }
- portENABLE_INTERRUPTS();
- }
Copy code
The main thing is to call xTaskIncrementTick() to count and determine whether task switching is needed. In addition to the rotation of ready tasks with the same priority, there are also cases where switching is required, such as using xTaskDelay() and other functions to apply for delay expiration, and waiting for the event timeout to expire. The latter two are in the DelayedTaskList list, and I will not list the implementation details here.
The questions on this site seem to be simpler than the previous one. Come on!
After reading, you are welcome to reply to the questions on this site: In FreeRTOS task programs, the xTaskDelay() function is often used to achieve the purpose of delayed execution. If xTaskDelay(20) is called in a task, and you want to wait for 20 time units, but the delay operation exceeds 25 time units, please analyze the possible reasons?
|