In the front, you can create a task, start the scheduler and control the task. Next, you can start to analyze the arrival of a Tick, FreeRTOS What behavior is about to happen;
When the scheduler is started, SysTick has been configured. As the heartbeat of the OS, SysTick comes every fixed cycle interrupt To drive the OS to do things (task scheduling);
Taking STM32 as an example, the defined "configtick"_ RATE_ The Hz is 1000 and is controlled by< FreeRTOS -- (9) startup scheduler for task management >It is known that the system beat clock cycle is 1ms;
Different processor structures may be different, so it is the part that needs to be transplanted in port C) xPortSysTickHandler:
void xPortSysTickHandler( void ) { /* The SysTick runs at the lowest interrupt priority, so when this interrupt executes all interrupts must be unmasked. There is therefore no need to save and then restore the interrupt mask value as its value is already known - therefore the slightly faster vPortRaiseBASEPRI() function is used in place of portSET_INTERRUPT_MASK_FROM_ISR(). */ vPortRaiseBASEPRI(); { /* Increment the RTOS tick. */ /* If the return value marks task switching, there are tasks with high priority */ if( xTaskIncrementTick() != pdFALSE ) { /* A context switch is required. Context switching is performed in the PendSV interrupt. Pend the PendSV interrupt. */ /* Set the middle break of PendSV */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } } vPortClearBASEPRIFromISR(); }
Call vPortRaiseBASEPRI() at the beginning; Call vPortClearBASEPRIFromISR() at the end; To create critical zones;
Xtask incrementtick is called; If pdTRUE is returned, it means that task switching is required, then manually pull up PendSV; Otherwise, context switching will not be performed;
Next, let's take a look at what xtask incrementtick has done. I guess it is the task with the highest priority and put into operation in the Ready linked list:
BaseType_t xTaskIncrementTick( void ) { TCB_t * pxTCB; TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE; /* Called by the portable layer each time a tick interrupt occurs. Increments the tick then checks to see if the new tick value will cause any tasks to be unblocked. */ traceTASK_INCREMENT_TICK( xTickCount ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { /* Minor optimisation. The tick count cannot change in this block. */ const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1; /* Increment the RTOS tick, switching the delayed and overflowed delayed lists if it wraps to 0. */ xTickCount = xConstTickCount; if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */ { taskSWITCH_DELAYED_LISTS(); } else { mtCOVERAGE_TEST_MARKER(); } /* See if this tick has made a timeout expire. Tasks are stored in the queue in the order of their wake time - meaning once one task has been found whose block time has not expired there is no need to look any further down the list. */ if( xConstTickCount >= xNextTaskUnblockTime ) { for( ;; ) { if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) { /* The delayed list is empty. Set xNextTaskUnblockTime to the maximum possible value so it is extremely unlikely that the if( xTickCount >= xNextTaskUnblockTime ) test will pass next time through. */ xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ break; } else { /* The delayed list is not empty, get the value of the item at the head of the delayed list. This is the time at which the task at the head of the delayed list must be removed from the Blocked state. */ pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */ xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); if( xConstTickCount < xItemValue ) { /* It is not time to unblock this item yet, but the item value is the time at which the task at the head of the blocked list must be removed from the Blocked state - so record the item value in xNextTaskUnblockTime. */ xNextTaskUnblockTime = xItemValue; break; /*lint !e9011 Code structure here is deedmed easier to understand with multiple breaks. */ } else { mtCOVERAGE_TEST_MARKER(); } /* It is time to remove the item from the Blocked state. */ ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); /* Is the task waiting on an event also? If so remove it from the event list. */ if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) { ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); } else { mtCOVERAGE_TEST_MARKER(); } /* Place the unblocked task into the appropriate ready list. */ prvAddTaskToReadyList( pxTCB ); /* A task being unblocked cannot cause an immediate context switch if preemption is turned off. */ #if ( configUSE_PREEMPTION == 1 ) { /* Preemption is on, but a context switch should only be performed if the unblocked task has a priority that is equal to or higher than the currently executing task. */ if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_PREEMPTION */ } } } /* Tasks of equal priority to the currently running task will share processing time (time slice) if preemption is on, and the application writer has not explicitly turned time slicing off. */ #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 ) { /* Guard against the tick hook being called when the pended tick count is being unwound (when the scheduler is being unlocked). */ if( xPendedTicks == ( TickType_t ) 0 ) { vApplicationTickHook(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TICK_HOOK */ #if ( configUSE_PREEMPTION == 1 ) { if( xYieldPending != pdFALSE ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_PREEMPTION */ } else { ++xPendedTicks; /* The tick hook gets called at regular intervals, even if the scheduler is locked. */ #if ( configUSE_TICK_HOOK == 1 ) { vApplicationTickHook(); } #endif } return xSwitchRequired; }
This function is a little long, but the idea is very clear and easy to understand:
1. Because FreeRTOS supports suspending the scheduler, that is, after calling vtask suspendall, RTOS will not switch the context of the scheduled task when each Tick comes; Therefore, every time you enter xtask incrementtick, you should judge whether the scheduler is suspended;
2. If scheduling is allowed, first increase the count of the current counter: xTickCount;
3. After adding # xTickCount, judge whether the counter overflows. If it overflows, call # taskSWITCH_DELAYED_LISTS to exchange # pxDelayedTaskList and # pxOverflowDelayedTaskList (in order to solve the overflow problem of xTickCount, FreeRTOS uses two delay lists: xDelayedTaskList1 and xDelayedTaskList2. It also uses the delay list pointer pxDelayedTaskList and overflow delay list pointer pxOverflowDelayedTaskList to point to delay list 1 and delay list 2 respectively (point the delay list pointer to the delay list when creating the task). These two delay list pointer variables and two delay list variables are in tasks (static local variables defined in C)
/* pxDelayedTaskList and pxOverflowDelayedTaskList are switched when the tick count overflows. */ #define taskSWITCH_DELAYED_LISTS() \ { \ List_t *pxTemp; \ \ /* The delayed tasks list should be empty when the lists are switched. */ \ configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) ); \ \ pxTemp = pxDelayedTaskList; \ pxDelayedTaskList = pxOverflowDelayedTaskList; \ pxOverflowDelayedTaskList = pxTemp; \ xNumOfOverflows++; \ prvResetNextTaskUnblockTime(); \ }
These two linked lists exist specifically to deal with counter overflow; In case of overflow, it will be exchanged. The OS always takes the Delay Task in the # pxDelayedTaskList. When hanging the task, judge the clock counter to see whether it is necessary to hang to # pxoverflow delayedtasklist;
4. Compare the current time, xConstTickCount, and the time of the next blocked task, xNextTaskUnblockTime, to see if the blocking time expires. xNextTaskUnblockTime is a global variable that records the blocking time of the next most recent task;
5. If the blocking time expires, first judge whether the current delay linked list is empty. If it is empty, it indicates that there are no tasks blocked in time. Assign ^ xNextTaskUnblockTime to the maximum ^ portMAX_DELAY, exit directly;
6. If the blocking time expires and the # pxDelayedTaskList list is not empty, take out the time of the first element of the # pxDelayedTaskList list (note that when inserting items into the pxDelayedTaskList list, use # vListInsert, which is inserted orderly according to the wake-up time, that is, the one with the smallest Delay time is placed in front and the one with the largest Delay is placed later), Compare with the current time # xConstTickCount to see if it is overdue. If it is not overdue, update it to the next wake-up time # xNextTaskUnblockTime and exit; If it expires, remove it from the pxDelayedTaskList list (if the Event is also removed from the Event when waiting for it), and add it to the ReadyList (prvAddTaskToReadyList),
/* * Place the task represented by pxTCB into the appropriate ready list for * the task. It is inserted at the end of the list. */ #define prvAddTaskToReadyList( pxTCB ) \ traceMOVED_TASK_TO_READY_STATE( pxTCB ); \ taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \ vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \ tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
7. If preemption is supported (RTOS that do not support preemption have no soul), it is necessary to judge the priority of the unblocked task and the current task, which is higher. If it is higher than the current task priority, then # xSwitchRequired is set to pdTRUE, indicating that a context switch is required;
8. Cycle steps 4 to 7, that is, traverse the elements in the pxdelayedtask list until the pxdelayedtask list is empty or the running time of some elements has not yet arrived, and it needs to continue blocking;
9. At this moment, all the tasks due have been moved from the pxDelayedTaskList linked list to the corresponding priority in the pxReadyTasksLists;
10. If configure_preemption is defined and the same priority rotation scheduling is also defined (in general, both need to be defined, otherwise there is no soul), as long as the pxReadyTasksLists list where the current task is located contains more than one task to be run, You have to rotate and schedule another task to execute; Therefore, xSwitchRequired is set to pdTRUE;
11. If the application layer defines "configure"_ TICK_ Hook, then the {vApplicationTickHook hook will be called;
12. If configure_preemption is defined and # xYieldPending is also pdTRUE, xSwitchRequired will also be set to # pdTRUE to force context switching,
When will the variable xYieldPending be set to pdTRUE?
For queues and semaphore and mutex using queue mechanism, these API functions are invoked in the interrupt service routine, and the task is removed from the blocking. Then the function xTaskRemoveFromEventList() is called to remove the task list item from the list of events. During the process of removing the event list item, it will judge whether the priority of the released task is higher than that of the current task. If the priority of the released task is higher, the variable xYieldPending will be set to pdTRUE. Trigger a task switching in the next system beat interrupt service function;
if(pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority) { /*If the task has a higher priority, pdTRUE is returned. Tell the task calling this function that it needs to force context switching.*/ xReturn= pdTRUE; /*API functions with interrupt protection will have a parameter "xhyperprioritytaskwoken". 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 will be checked. If it is pdTRUE, a context switch will be triggered.*/ xYieldPending= pdTRUE; }