FreeRTOSConfig.h
中 configTICK_RATE_HZ
设置任务节拍中断频率, 在启动任务调度器时,系统会根据另一个变量, CPU 的频率configCPU_CLOCK_HZ
计算对应写入节拍计数器的值,启动定时器中断。
系统在每一次节拍计数器中断服务程序xPortSysTickHandler
(平台实现 port.c 中) 中调用处理函数 xTaskIncrementTick, 依据该函数返回值判断是否需要触发 PendSV 异常, 进行任务切换。 xTaskIncrementTick
完成。 uxPendedTicks
, 调用用户钩子函数, 此时,正在运行的任务不会被切换, 一直运行。 xTaskIncrementTick
补偿 (uxPendedTicks
次)。
不管, 系统调度器是否挂起, 每次节拍中断都会调用用户的钩子函数 vApplicationTickHook
。 由于函数是中断中调用,不要在里面处理太复杂的事情!!
task.c
开头。
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;
pxDelayedTaskList
指向 xDelayedTaskList1
, pxOverflowDelayedTaskList
指向 pxOverflowDelayedTaskList
,一开始我还在郁闷延时链表为什么要两个,到这里才明白。
当任务由于等待事件(延时,消息队列什么的堵塞)时,会设置一个时间,这时候,响应的任务会被挂到延时链表中,如果超过设置时间没有事件响应,则系统会从延时链表中取出任务恢复就绪。 xTickCount
, 加入链表前依据当前计数器的值计算出超时的值 ( xTickCount+ xTicksToDelay ), 顺序插入到延时链表中。
对不同平台xTickCount
表示的位数不同,但是每次节拍中断加一,总会溢出。 上述计算任务延时时间,如果系统发现计算出来的时间已经溢出,则会将该任务加入到 pxOverflowDelayedTaskList
这个链表中。 taskSWITCH_DELAYED_LISTS()
切换上述的链表指针。 pxTemp = pxDelayedTaskList;
pxDelayedTaskList = pxOverflowDelayedTaskList;
pxOverflowDelayedTaskList = pxTemp;
xNumOfOverflows++;
prvResetNextTaskUnblockTime();
xNextTaskUnblockTime
记录下一个需要退出延时链表的任务时间, 因此, 接下来判断当前时间,延时链表中是否有任务需要推出阻塞状态。
if( xConstTickCount >= xNextTaskUnblockTime )
{
for ( ;; ) {
// 取出唤醒任务, 推进就绪链表
}
}
xNextTaskUnblockTime
。
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK( xTickCount );
// 调度器正在运行
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
// 节拍计数器递增 1
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
{
// 节拍计数器溢出
// 比如32位 0xFFFFFFFF + 1 -> 0
// 延时链表切换
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
// 没有任务延时, 时间设置"无穷大" 退出循环
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
// 取出延时链表头任务 TCB
pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
// 取该任务延时值
xItemValue = listGET_LIST_ITEM_VALUE(&(pxTCB->xStateListItem));
// 判断任务是否超时
if( xConstTickCount < xItemValue )
{
// 任务还没到时间,更新全局变量
// 直接退出
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 任务恢复就绪, 从堵塞的链表中删除
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 插入就绪链表等待被执行
prvAddTaskToReadyList( pxTCB );
// 如果系统允许抢占
#if ( configUSE_PREEMPTION == 1 )
{
// 如果新就绪任务优先级高于当前任务
// 标记需要切换任务
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
// 同优先级任务 时间轮
#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 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else
{
// 记录调度器被挂起器件节拍中断次数
// 恢复后用于补偿, 执行本函数 uxPendedTicks 次先
++uxPendedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
// 函数返回值, 如果为 pdTRUE,
// 则调用的系统节拍中断会触发 PendSV 异常, 任务切换
#if ( configUSE_PREEMPTION == 1 ) // 允许抢占
{
// 其他地方标记需要执行一次任务切换
// 所以不管前面需不需要 这里都会返回需要切换
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired;
}
void vTaskDelay( const TickType_t xTicksToDelay );
该函数调用到另一个函数是 prvAddCurrentTaskToDelayedList
, 将任务加入到延时链表中, 函数中会判断设定时间是否溢出, 选择加入到对应的延时链表, 同上提到计数器溢出的问题。
void vTASKReadSensor(void *pvParameters)
{
// 500ms 转换为 节拍
const portTickType xDelay = pdMS_TO_TICKS(500);
for( ;; )
{
readSensor();
vTaskDelay( xDelay );
}
}
vTaskDelayUntil
,
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
多了一个参数 pxPreviousWakeTime
, 就不会有这个问题了 void vTASKReadSensor(void *pvParameters)
{
const portTickType xDelay = pdMS_TO_TICKS(500);
static portTickType xLastWakeTime;
// 记录第一次调用函数的时间 , 后续该变量由延时函数自己叠加
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
readSensor();
vTaskDelayUntil( xDelay );
}
}