前面有了创建任务、启动调度器、任务控制,接下来便开始分析一个 Tick 到来之后,FreeRTOS 即将有什么行为;
在启动调度器的时候,就已经配置好了 SysTick,它作为 OS 的心跳,每隔一个固定周期来一次 SysTick 中断,来驱动 OS 做事(任务调度);
以 STM32 为例,定义的 configTICK_RATE_HZ 为 1000,由《FreeRTOS --(9)任务管理之启动调度器》得知,系统节拍时钟周期为1ms;
不同的处理器结构可能有所区别,所以他是需要移植的部分,在 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(
xTaskIncrementTick() != pdFALSE )
-
{
-
/* A context switch is required. Context switching is performed in
-
the PendSV interrupt. Pend the PendSV interrupt. */
-
/* 设置PendSV中断位 */
-
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
-
}
-
}
-
vPortClearBASEPRIFromISR();
-
}
开头调用 vPortRaiseBASEPRI();结尾调用 vPortClearBASEPRIFromISR(); 是为了创造临界区;
调用了 xTaskIncrementTick;如果返回 pdTRUE 则代表要进行任务切换,那么就手动拉起 PendSV;否则不进行上下文切换;
接下来看下 xTaskIncrementTick 做了什么,大概猜测是选最高优先级的,并且在 Ready 链表的任务投入运行:
-
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;
-
}
这个函数有点长,但是其实思路是非常清晰易懂的:
1、因为 FreeRTOS 支持挂起调度器,也就是调用 vTaskSuspendAll 后,RTOS 在每个 Tick 来临的时候,不在调度任务进行上下文切换;所以,每次进入 xTaskIncrementTick 的时候,要判断调度器是否被挂起;
2、如果允许调度,首先增加当前的计数器的计数:xTickCount;
3、增加完 xTickCount 后,判断计数器是否溢出,如果溢出了,那么调用 taskSWITCH_DELAYED_LISTS 来交换 pxDelayedTaskList 和 pxOverflowDelayedTaskList (为了解决xTickCount溢出问题,FreeRTOS使用了两个延时列表:xDelayedTaskList1 和 xDelayedTaskList2。并使用延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)。这两个延时列表指针变量和两个延时列表变量都是在tasks.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(); \
-
}
这两个链表专门为了处理计数器溢出而存在;一旦溢出,就交换,OS 始终取的是 pxDelayedTaskList 中的 Delay Task,在挂接任务的时候,判断时钟计数器,看是否需要往 pxOverflowDelayedTaskList 上面挂;
4、对比当前的时间 xConstTickCount 和下一个阻塞在时间上的任务的时间 xNextTaskUnblockTime 大小,查看阻塞时间是否到期,xNextTaskUnblockTime 是一个全局变量,记录着下一个最近的任务阻塞时间;
5、如果阻塞时间到期,那么首先判断当前的 Delay 链表是否为空,如果为空,则说明没有阻塞在时间上的任务,将 xNextTaskUnblockTime 赋值为最大 portMAX_DELAY,直接退出;
6、如果阻塞时间到期,而且 pxDelayedTaskList 链表不为空,那么取出 pxDelayedTaskList 链表的第一个元素(注意,往 pxDelayedTaskList 链表中插入 Item 的时候,是用 vListInsert ,根据唤醒时间有序插入的,即前面放置的是 Delay 时间最小的,后面是 Delay 大的)的时间,和当前的时间 xConstTickCount 进行比对,看看是否超期,如果没有超期,那么将其更新到下一个唤醒时间 xNextTaskUnblockTime 中,退出;如果到期,那么将其从 pxDelayedTaskList 链表中移除(如果在等 Event 也同时从 Event 中移除),将其添加到 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、如果支持抢占的话(不支持抢占的 RTOS 是没有灵魂的),就要判断解除阻塞的任务和当前的任务的优先级,哪个更高,如果高于当前的任务优先级,那么 xSwitchRequired 设置为 pdTRUE,表示要进行一次上下文切换;
8、循环步骤 4 到 7,也就是对 pxDelayedTaskList 链表中的元素进行遍历,直到 pxDelayedTaskList 为空,或者有元素的运行时间还未到,还需要继续阻塞;
9、此刻,该到期的任务,已经全部从 pxDelayedTaskList 链表移动到了 pxReadyTasksLists 中,对应优先级的地方;
10、如果定义了抢占(configUSE_PREEMPTION),同时也定义了同一个优先级轮转调度(configUSE_TIME_SLICING) 的话呢(普通情况下,这两个都需要定义,不然没有灵魂),只要当前的任务所在的 pxReadyTasksLists 链表中,包含不止一个待运行的任务,就要去轮转调度另一个任务执行;所以 xSwitchRequired 设置为 pdTRUE;
11、如果应用层定义了 configUSE_TICK_HOOK,那么会调用 vApplicationTickHook 钩子;
12、如果定义了抢占(configUSE_PREEMPTION),而且 xYieldPending 也是 pdTRUE 的时候,也会设置 xSwitchRequired 设置为 pdTRUE,强制去进行上下文切换,
xYieldPending 这个变量什么时候会被设置称为 pdTRUE?
对于队列以及使用队列机制的信号量、互斥量等,在中断服务程序中调用了这些API函数,将任务从阻塞中解除,则需要调用函数xTaskRemoveFromEventList()将任务的事件列表项从事件列表中移除。在移除事件列表项的过程中,会判断解除的任务优先级是否大于当前任务的优先级,如果解除的任务优先级更高,会将变量xYieldPending设置为pdTRUE。在下一次系统节拍中断服务函数中,触发一次任务切换;
-
if(pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority)
-
{
-
/*任务具有更高的优先级,返回pdTRUE。告诉调用这个函数的任务,它需要强制切换上下文。*/
-
xReturn= pdTRUE;
-
-
/*带中断保护的API函数的都会有一个参数参数"xHigherPriorityTaskWoken",如果用户没有使用这个参数,这里设置任务切换标志。在下个系统中断服务例程中,会检查xYieldPending的值,如果为pdTRUE则会触发一次上下文切换。*/
-
xYieldPending= pdTRUE;
-
}