xPortSysTickHandler函数在FreeRTOS中在给系统提供系统节拍的系统时钟或是定时器中断中调用的。他更新系统节拍数,检查任务阻塞,任务切换等。
必要知识(粗略)
代码解析必须粗略了解的一些关于FreeRTOS的知识。
就绪列表
由configMAX_PRIORITIES决定大小。
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
任务加入就绪列表时按照各自优先级存入到数组对应的列表中,这样切换任务时从高优先级的列表开始找,这样就可以确保高优先级任务优先。
阻塞列表
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. */
阻塞列表有两个xDelayedTaskList1,xDelayedTaskList2。在创建第一个任务时会初始化就绪,阻塞等等的列表,在这个过程中pxDelayedTaskList指向xDelayedTaskList1,pxOverflowDelayedTaskList指向pxOverflowDelayedTaskList。阻塞列表是一个循环列表。
我们知道FreeRTOS系统在每个系统时钟中断(提供的系统时钟中断)时会更新节拍数,然后检查阻塞任务的阻塞时间是否到达,事件等等时间是否到达。但是这系统中任务阻塞时间一般是延时时间+调用阻塞函数时的系统节拍。系统节拍在启动后一直在累加,以STM32为例系统节拍是32位存储当他溢出后怎么保证阻塞系统的正常运行?FreeRTOS使用两个阻塞列表,pxDelayedTaskList指向的阻塞列表存放未溢出的阻塞任务,pxOverflowDelayedTaskList指向的阻塞列表存放溢出的阻塞任务。在任务调用会导致阻塞的函数时,系统计算阻塞时间(延时时间+调用阻塞函数时的系统节拍)这个数小于调用阻塞函数时的系统节拍时,将任务放入pxOverflowDelayedTaskList指向的阻塞列表,否则放入pxDelayedTaskList。然后在xPortSysTickHandler函数中系统节拍更新时,也会判断系统节拍是否溢出,如果溢出了,就像pxDelayedTaskList和pxOverflowDelayedTaskList的指向互换,然后开始查找是否有阻塞时间到达的任务。
代码解析
void xPortSysTickHandler( void )
{
uint32_t ulPreviousMask;
ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();//这是在终端中屏蔽终端的宏。
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )//xTaskIncrementTick()非常重要
{
/* Pend a context switch. */
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;//除非Pend中断,切换任务
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}
/* 更新系统节拍数 */
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
/*判断调度器是否挂起*/
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//没有挂起
{
/*
更新系统节拍数,使用xConstTickCount变量存储因为后续要判断节拍数是否溢出,
直接使用xTickCount全局变量不好(这个变量如果在其他地方被更改,导致后续判断错误,这样无
法确保所有任务的准确运行)
*/
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )//判断是否溢出
{
taskSWITCH_DELAYED_LISTS();
}
/* 有任务要被唤醒了,xNextTaskUnblockTime记录下一个任务要被唤醒的节拍数 */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )//阻塞列表为空
{
xNextTaskUnblockTime = portMAX_DELAY;//下一个任务的阻塞时间设置为最大值
break;//跳出
}
else //不为空
{
//获取阻塞列表头的项的任务控制块
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
/* 找到了阻塞列表中阻塞时间还没到的任务 */
if( xConstTickCount < xItemValue )
{
/* 更新下一个解除阻塞任务的节拍数 */
xNextTaskUnblockTime = xItemValue;
break; //跳出
}
//阻塞列表移除阻塞时间到了的任务
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
//如果该任务还在事件阻塞列表中,也从事件中删除
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
//将任务加入到就绪列表中
prvAddTaskToReadyList( pxTCB );
#if ( configUSE_PREEMPTION == 1 )
{
/* 如果是抢占式调度 */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
/*
函数返回true,由上面
xPortSysTickHandler可知会触发pend中断
*/
}
}
#endif
}
}
}
/*时间片调度,如果configUSE_PREEMPTION等于0那么认为不会自动切换任务*/
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if(
listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[ pxCurrentTCB->uxPriority ]))
> ( UBaseType_t ) 1
)
{
xSwitchRequired = pdTRUE;
}
}
#endif
}
else //调度器挂起
{
++uxPendedTicks;//记录调度器挂起后,系统时钟中断的次数。
}
#if ( configUSE_PREEMPTION == 1 )
{
//如果xYieldPending 标记为true代表应该执行一次任务切换
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
}
#endif
}