Preface
The operation of the operating system is driven by the system beat clock.
In FreeRTOS, we know that system delay and blocking time are both based on system beat clock cycles. In the configuration file, changing the value of the macro configTICK_RATE_HZ can change the interrupt frequency of the system beat clock, and indirectly change the system beat clock cycle (T=1/f). For example, if the macro configTICK_RATE_HZ is set to 100, the system beat clock cycle is 10ms, and if the macro configTICK_RATE_HZ is set to 1000, the system beat clock cycle is 1ms.
The system beat interrupt service program will call the function xTaskIncrementTick() to complete the main work. If the return value of the function is true (not equal to pdFALSE), it means that the priority of the task in the ready state is higher than the task currently running. This triggers a PendSV interrupt and performs a context switch. Let's focus on what the function xTaskIncrementTick() does, and under what circumstances the true value is returned.
1. Normal condition of the scheduler
The scheduler is normal (not suspended), i.e. the value of the variable uxSchedulerSuspended is pdFALSE. The variable uxSchedulerSuspended is a static variable defined in a file that records the running status of the scheduler. When the API function vTaskSuspendAll() is called to suspend the scheduler, the variable uxSchedulerSuspended will be incremented by 1. Therefore, when the variable uxSchedulerSuspended is true, it means that the scheduler is suspended.
Under normal circumstances, the scheduler first increments the variable xTickCount by 1. The variable xTickCount is also a static variable defined in the file. It is cleared when the scheduler is started. It is added 1 after each interruption of the system beat clock, which is used to record the number of interruptions of the system beat clock. The kernel will compare all blocking tasks with this variable to determine whether the timeout is timed out (timeout means that the blocking can be unblocked).
The data type of the variable xTickCount is related to the specific hardware. The 32-bit architecture hardware is generally an unsigned 32-bit variable, an 8-bit or 16-bit architecture is generally an unsigned 16-bit variable. Even for 32-bit variables, xTickCount will overflow after being accumulated to 0xFFFFFFF. Therefore, in the program, we must determine whether the variable xTickCount overflows. If overflow (xTickCount is 0), the macro taskSWITCH_DELAYED_LISTS() is called to exchange the delay list pointer and the overflow delay list pointer. This involves a bit broad, let's explain it slowly.
To solve the xTickCount overflow problem, FreeRTOS uses two delay lists: xDelayedTaskList1 and xDelayedTaskList2. And use the delay list pointer pxDelayedTaskList and the overflow delay list pointer pxOverflowDelayedTaskList to point to the above delay list 1 and delay list 2 respectively (point the delay list pointer to the delay list when creating a task). By the way, the above two delay list pointer variables and the two delay list variables are both static local variables defined in it.
For example, we use the API delay function vTaskDelay (xTicksToDelay) to delay the task xTicksToDelay system beat cycles. The delay function will refer to the current number of interrupts in the system beat xTickCount as a reference. This value plus the delay time specified by the parameter xTicksToDelay, that is, xTickCount + xTicksToDelay, which is the time to wake up the task next time.
xTickCount+xTicksToDelay will be recorded in the task TCB and will be attached to the delay list along with the task. If the kernel determines that xTickCount + xTicksToDelay overflow (greater than the maximum value that 32 bits can represent), the current task is attached to the list pointed to by the list pointer pxOverflowDelayedTaskList, otherwise it is attached to the list pointed to by the list pointer pxDelayedTaskList. Tasks are inserted into the delay list in sequence according to the delay time.
So when the system beat interrupt count counter xTickCount overflow, the delay list pointer pxDelayedTaskList and the overflow delay list pointer pxOverflowDelayedTaskList must be exchanged to correctly handle the delayed task. The code of macro taskSWITCH_DELAYED_LISTS() is as follows:
#definetaskSWITCH_DELAYED_LISTS() \ { \ List_t *pxTemp \ \ /* The delayed tasks list should beempty when the lists are switched. */ \ configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList) ) ); \ \ pxTemp = pxDelayedTaskList; \ pxDelayedTaskList = pxOverflowDelayedTaskList; \ pxOverflowDelayedTaskList = pxTemp; \ xNumOfOverflows++; \ prvResetNextTaskUnblockTime \ }
This code completes two parts of the work. The first is to exchange the delay list pointer pxDelayedTaskList and the overflow delay list pointer pxOverflowDelayedTaskList; the second is to call the function prvResetNextTaskUnblockTime() to re-get the next time of unblocking. This time is saved in the static variable xNextTaskUnblockTime, which is also defined in. This variable will be used when checking whether the delay list task expires.
Next, the function will check the delay list to see if the delayed task expires. As we mentioned earlier, the delay tasks are inserted into the delay list in sequence according to the delay time sequence. The delay time is short, the delay time is long, and the time value of the next task to be awakened is saved in the variable xNextTaskUnblockTime. So by comparing xTickCount with xNextTaskUnblockTime, you can know whether there is a task that can be awakened.
if( xConstTickCount >=xNextTaskUnblockTime ) { /* Delayed task expires and needs to be awakened */ }
If the task is awakened, the task is deleted from the delay list and re-added to the ready list. If the task priority of newly added to the ready list is greater than the current task priority, a context switch is triggered.
FreeRTOS supports multiple tasks to share the same priority. If it is set to preemptive scheduling (macro configUSE_PREEMPTION is set to 1) and the macro configUSE_TIME_SLICING is also 1 (or undefined), task switching will be performed between multiple tasks with the same priority.
Finally, the time slice hook function vApplicationTickHook() will be called. You can see that the time slice hook function is actually called in the interrupt service function, so this hook function must be concise and cannot call API functions without interrupt protection.
2. Scheduler suspend status
If the scheduler hangs, the executing task will continue to be executed and the kernel will no longer schedule (meaning that the current task will not be switched out) until the task calls the xTaskResumeAll() function.
During the scheduler suspend phase, FreeRTOS uses the static variable uxPendedTicks to record the number of system beat interrupts during suspend. When the recovery scheduler function xTaskResumeAll() is called, the uxPendedTicks function (xTaskIncrementTick()) will be executed. The variable uxPendedTicks is also defined in it.
3. Automatic task switching
The last few lines of the function are quite difficult to understand. The local variable xSwitchRequired is the return value of this function. At the beginning of the article, it also said: "If the return value of the function is true, it means that the priority of the task in the ready state is higher than the priority of the current running task, a PendSV interrupt will be triggered to perform context switching." Now, if the variable xYieldPending is true, the return value will also be true, and the context switching will be performed after the function ends. What is the function of this variable xYieldPending? When is it assigned to true? I really have to start from the beginning.
if( xYieldPending != pdFALSE ) { xSwitchRequired = pdTRUE; }
API functions with interrupt protection will have a parameter pxHigherPriorityTaskWoken. If the API function causes a task to be unlocked and the unlocked task priority is higher than the currently running task, the API function sets *pxHigherPriorityTaskWoken to pdTRUE. Before the interrupt exits, the old version of FreeRTOS needs to manually trigger a task switch. For exampleFreeRTOS uses task notifications to implement command line interpreterIn this article, we call the API function vTaskNotifyGiveFromISR() with interrupt protection in the serial port reception interrupt. After the function is executed, we will use the code portYIELD_FROM_ISR(xHigherPriorityTaskWoken) to determine whether the parameter xHigherPriorityTaskWoken is true. If it is true, it will manually force the context switch.
BaseType_txHigherPriorityTaskWoken = pdFALSE; /*Received a frame of data and send a notification to the command line interpreter task*/ vTaskNotifyGiveFromISR(xCmdAnalyzeHandle,&xHigherPriorityTaskWoken); /*Is it necessary to force context switching*/ portYIELD_FROM_ISR(xHigherPriorityTaskWoken );
Starting from FreeRTOSV7.3.0, pxHigherPriorityTaskWoken becomes an optional parameter and can be set to NULL. If the parameter xHigherPriorityTaskWoken is set to NULL, and the API function with interrupt protection causes higher priority tasks to be unlocked, when and how to switch tasks?
It turns out that starting from FreeRTOSV7.3.0, the kernel has added a static variable xYieldPending, which is also defined in it. If the variable xYieldPending is set to pdTRUE, a task switch will be triggered in the next system beat interrupt service function, see the code description of the first paragraph of this section.
Let's see how this process is implemented.
For queues and semaphores, mutexes, etc. that use the queue mechanism, these API functions are called in the interrupt service program to remove the task from the blocking, and the function xTaskRemoveFromEventList() needs to be called to remove the task's event list item from the event list. During the process of removing event list items, it will be judged whether the priority of the resolved task is greater than the priority of the current task. If the priority of the resolved task is higher, the variable xYieldPending will be set to pdTRUE. In the next system beat interrupt service function, a task switch is triggered. The code looks like this:
if(pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority) { /* Tasks have higher priority, return pdTRUE. Tell the task that calls this function that it needs to force the context to switch. */ xReturn= pdTRUE; /*The API functions with interrupt protection will have a parameter parameter "xHigherPriorityTaskWoken". If the user does not use this parameter, set the task switching flag here. In the next system interrupt service routine, the value of xYieldPending is checked, and if pdTRUE is pd, a context switch will be triggered. */ xYieldPending= pdTRUE; }
For the newly launched task notifications of FreeRTOSV8.2.0, API functions with interrupt protection version are also provided. According to logical inference, the parameter xHigherPriorityTaskWoken of these API functions can also be used, and the variable xYieldPending should also act on these API functions. But the fact is that in versions before FreeRTOSV9.0, FreeRTOS did not implement this function. If you use these API functions to remove a higher priority task, you must manually switch contexts. This may be a bug, as in FreeRTOS V9.0, this issue has been fixed, and the context can be automatically switched using the variable xYieldPending. This bug was encountered by netizens who were nicknamed "Director" in QQ.
In V9.0 and above, if the notification released in the interrupt causes higher priority task unlocking, the API function will determine whether the parameter xHigherPriorityTaskWoken is valid. If it is valid, the *xHigherPriorityTaskWoken is set to pdTRUE, and the context needs to be switched manually; otherwise, the variable xYieldPending is set to pdTRUE, and a task switching is triggered in the next system beat interrupt service function. The code looks like this:
if( pxTCB->uxPriority >pxCurrentTCB->uxPriority ) { /*If the priority of unblocking task is greater than the priority of the current task, set the context switching flag, manually switch the context after exiting the function, or automatically switch the context in the system beat interrupt service program*/ if(pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken= pdTRUE; /* Set manual switching flag*/ } else { xYieldPending= pdTRUE; /* Set the automatic switching flag*/ } }
The complete code of the function xTaskIncrementTick() is shown below. According to the above explanation and the code comments, it should not be difficult to understand these codes.
BaseType_t xTaskIncrementTick( void ) { TCB_t * pxTCB; TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE; /* Whenever the system beat timer interrupt occurs, the transplant layer will call this function. The function increases the system beat interrupt counter by 1, Then check whether the new system beat interrupt counter value releases a task.*/ if(uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { /* Scheduler normal situation */ const TickType_txConstTickCount = xTickCount + 1; /* The system beat interrupt counter is increased by 1. If the counter overflows (0), exchange the delay list pointer and the overflow delay list pointer */ xTickCount = xConstTickCount; if( xConstTickCount == ( TickType_t ) 0U ) { taskSWITCH_DELAYED_LISTS(); } /* Check whether there are delayed tasks expire. Tasks are stored in the queue in order of wake-up times, which means that as long as the first wake-up task in the queue does not expire, other tasks must not expire.*/ if( xConstTickCount >=xNextTaskUnblockTime ) { for( ;; ) { if( listLIST_IS_EMPTY( pxDelayedTaskList) != pdFALSE ) { /* If the delay list is empty, set xNextTaskUnblockTime to the maximum value */ xNextTaskUnblockTime = portMAX_DELAY; break; } else { /* If the delay list is not empty, get the first list item value of the delay list, which stores the task wake-up time. The wake-up time expires, and the task to which the first list item in the delay list belongs to is removed from the blocking state */ pxTCB = ( TCB_t * )listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); xItemValue =listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); if( xConstTickCount < xItemValue ) { /* The task has not yet reached the unblocking time? Set the current task wake-up time to the next unblocking time. */ xNextTaskUnblockTime = xItemValue; break; } /* Remove expired tasks from blocking list */ ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); /* Is it blocking because of waiting for an event? If so, delete the expired task from the event list */ if(listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) { ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); } /* Put unblocked tasks into the ready list */ prvAddTaskToReadyList( pxTCB ); #if ( configUSE_PREEMPTION == 1 ) { /* The preemptive kernel is enabled. If the priority of the unblocking task is greater than the current task, a context switch flag is triggered */ if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xSwitchRequired= pdTRUE; } } #endif /*configUSE_PREEMPTION */ } } } /* If other tasks share a priority with the current task, these tasks share the processor (time slice) */ #if ( (configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) { if(listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* ( (configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */ #if (configUSE_TICK_HOOK == 1 ) { /* Call the time slice hook function*/ if( uxPendedTicks == ( UBaseType_t ) 0U ) { vApplicationTickHook(); } } #endif /*configUSE_TICK_HOOK */ } else { /* Scheduler suspend status, variable uxPendedTicks is used to count the number of system beat interrupts during scheduler suspend. When the recovery scheduler function is called, the uxPendedTicks function will be executed (xTaskIncrementTick()): Restore the system beat interrupt counter. If a task blocking expires, delete the blocking state */ ++uxPendedTicks; /* Call the time slice hook function*/ #if (configUSE_TICK_HOOK == 1 ) { vApplicationTickHook(); } #endif } #if (configUSE_PREEMPTION == 1 ) { /* If the API function called in the interrupt wakes up a higher priority task, and the parameter pxHigherPriorityTaskWoken of the API function is NULL, the variable xYieldPending is used for the context switching flag */ if( xYieldPending!= pdFALSE ) { xSwitchRequired = pdTRUE; } } #endif /*configUSE_PREEMPTION */ return xSwitchRequired; }
The above is the detailed content of the complete analysis of the FreeRTOS Advanced System Beat Clock Example. For more information about the FreeRTOS Advanced System Beat Clock, please pay attention to my other related articles!