目录
1. FreeRTOS 延时函数
1.1 函数 vTaskDelay()
1.2 函数 prvAddCurrentTaskToDelayedList()
1.3 函数 vTaskDelayUntil()
2. FreeRTOS 系统时钟节拍
在使用 FreeRTOS 的过程中我们通常会在一个任务函数中使用延时函数对这个任务延时,当执行延时函数的时候就会进行任务切换(这里我们要明白为什么其他的延时函数不会发生任务调度,而 FreeRTOS 延时函数会发生系统调用;),并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。延时函数属于 FreeRTOS 的时间管理,这里我们就来学习 FreeRTOS 的时间管理过程,看一下调用延时函数以后究竟发生了什么?任务是如何进入阻塞态的,在延时完成以后任务又是如何从阻塞态恢复到就绪态的?
1. FreeRTOS 延时函数
1.1 函数 vTaskDelay()
在 FreeRTOS 中延时函数可以设置为两种不同的模式:相对模式、绝对模式。并且 FreeRTOS 中不同模式用的函数不同,其中函数 vTaskDelay() 是相对模式(相对延时函数),函数 vTaskDelayUntil() 是绝对模式(绝对延时函数)。
相对延时:指每次延时都是从执行函数 vTaskDelay() 开始,直到延时指定的时间结束;
绝对延时:指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务;
下图就是一个绝对延时函数的图,延时时间是整个 xTimeIncrement ;
(1) 为任务主体,也就是任务真正要做的工作
(2) 是任务函数中调用 vTaskDelayUntil() 对任务进行延时
(3) 为其他任务在运行 (高优先级) ,意思就是该任务难免被其他更高优先级的任务抢占;
函数 vTaskDelay() 在文件 tasks.c 中有定义,要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1,函数代码如下:
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
//延时时间要大于 0。
if( xTicksToDelay > ( TickType_t ) 0U ) (1)
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (2)
{
traceTASK_DELAY();
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); (3)
}
xAlreadyYielded = xTaskResumeAll(); (4)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xAlreadyYielded == pdFALSE ) (5)
{
portYIELD_WITHIN_API(); (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1)延时函数由参数 xTicksToDelay 来确定,为了有延时的时间节拍数,延时时间肯定要大于 0 ,否则相当于没有延时。否则的话就相当于直接调用函数 portYIELD() 进行任务切换。
(2)调用函数 vTaskSuspendAll() 挂起任务调度器。
(3)调用函数 prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时(阻塞)列表 pxDelayedTaskList 或者 pxOverflowDelayedTaskList() 中。(这里就说明了为什么延时函数会产生系统调用,因为延时函数会将目前运行的任务添加到延时阻塞队列; )
(4)调用函数 xTaskResumeAll() 恢复任务调度器。
(5)如果函数 xTaskResumeAll() 没有进行任务调度的话那么在这里就得进行任务调度。
(6)调用函数 portYIELD_WITHIN_API 进行一次任务调度。
1.2 函数 prvAddCurrentTaskToDelayedList()
函数 prvAddCurrentTaskToDelayedList() 用于将当前任务添加到等待列表中,函数在文件 tasks.c 中有定义,缩减后的函数如下:
static void prvAddCurrentTaskToDelayedList( TickType_t x TicksToWait,
const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; (1)
#if( INCLUDE_xTaskAbortDelay == 1 )
{
//如果使能函数 xTaskAbortDelay()的话复位任务控制块的 ucDelayAborted 字段为
//pdFALSE。
pxCurrentTCB->ucDelayAborted = pdFALSE;
}
#endif
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (2)
{
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); (3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
(4)
{
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); (5)
}
else
{
xTimeToWake = xConstTickCount + xTicksToWait; (6)
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), \ (7)
xTimeToWake );
if( xTimeToWake < xConstTickCount ) (8)
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->\ (9)
xStateListItem ) );
}
else
{
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); (10)
if( xTimeToWake < xNextTaskUnblockTime ) (11)
{
xNextTaskUnblockTime = xTimeToWake; (12)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
/************************************************************* **************/
/****************************其他条件编译语句*******************************/
/***************************************************************************/
}
(1)读取进入函数 prvAddCurrentTaskToDelayedList() 的时间点并保存在 xCountTickCount 中,后面计算任务唤醒时间点的时候要用到。xTickCount 是时钟节拍计数器,每个滴答定时器中断 xTickCount 都会加一。
(2)要将当前正在运行的任务添加到延时列表中,肯定要先将当前任务从就绪列表中移除。
(3)将当前任务从就绪列表中移除以后还要取消任务在 uxTopReadyPriority 中的就绪标记。也就是将 uxTopReadyPriority 中对应的 bit 清零。
(4)延时时间为最大值 portMAX_DELAY,并且 xCanBlockIndefinitely 不为 pdFALSE(xCanBlockIndefinitely 不为 pdFALSE 的话表示允许阻塞任务)的话直接将当前任务添加到挂起列表中,任务就不用添加到延时列表中。
(5)将当前任务添加到挂起列表 xSuspendedTaskList 的末尾。
(6)计算任务唤醒时间点,也就是(1)中获取到的进入函数 prvAddCurrentTaskToDelayedList() 的时间值 xCountTickCount 加上延时时间值 xTicksToWait。
(7)将计算到的任务唤醒时间点值 xTimeToWait 写入到任务列表中状态列表项的相应字段中。
(8)计算得到的任务唤醒时间点小于 xCountTickCount,说明发生了溢出。全局变量 xTickCount 是 TickType_t 类型的,这是个 32 位的数据类型,因此在用 xTickCount 计算任务唤醒时间点 xTimeToWake 的时候肯定会出现溢出的现象。
FreeRTOS 针对此现象专门做了处理,在 FreeRTOS 中定义了两个延时列表 xDelayedTaskList1 和 xDelayedTaskList2,并且也定义了两个指针 pxDelayedTaskList 和 pxOverflowDelayedTaskList 来访问这两个列表,在初始化列表函数 prvInitialiseTaskLists() 中指针 pxDelayedTaskList 指向了列表 xDelayedTaskList1,指针 pxOverflowDelayedTaskList 指向了列表 xDelayedTaskList2。这样发生溢出的话就将任务添加到 pxOverflowDelayedTaskList 所指向的列表中,如果没有溢出的话就添加到 pxDelayedTaskList 所指向的列表中。
(9)如果发生溢出的话就将当前任务添加到 pxOverflowDelayedTaskList 所指向的列表中。
(10)如果没有发生溢出的话就将当前任务添加到 pxDelayedTaskList 所指向的列表中。
(11)xNextTaskUnblockTime 是个全局变量,保存着距离下一个要取消阻塞的任务最小时间点值。当 xTimeToWake 小于 xNextTaskUnblockTime 的话说明有个更小的时间点来啦。(简单来说就是:xTimeToWake 保存的是任务唤醒时间,也就是当前任务还有多少时间会进入就绪态;xNextTaskUnblockTime 保存距离下一个取消阻塞的任务的最小时间点值,如果 xTimeToWake < xNextTaskUnblockTime,就说明此任务进入就绪态的时间比到下一个通过任务取消阻塞从而进入就绪态的时间还要小,那么就告诉操作系统有个更小的值来啦,从而进行赋值)
(12)更新 xNextTaskUnblockTime 为 xTimeToWake。
1.3 函数 vTaskDelayUntil()
函数 vTaskDelayUntil() 会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数 vTaskDelayUntil()。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT( pxPreviousWakeTime );
configASSERT( ( xTimeIncrement > 0U ) );
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (1)
{
const TickType_t xConstTickCount = xTickCount; (2)
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; (3)
if( xConstTickCount < *pxPreviousWakeTime ) (4)
{
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake >\ (5)
xConstTickCount ) )
{
xShouldDelay = pdTRUE; (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > \ (7)
xConstTickCount ) )
{
xShouldDelay = pdTRUE; (8)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
*pxPreviousWakeTime = xTimeToWake; (9)
if( xShouldDelay != pdFALSE ) (10)
{
traceTASK_DELAY_UNTIL( xTimeToWake );
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
(11)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll(); (12)
if( xAlreadyYielded == pdFALSE )
{
ortYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
参数:
pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用 vTaskDelayUntil 的话需要将 pxPreviousWakeTime 初始化进入任务的 while() 循环体的时间点值。在以后的运行中函数 vTaskDelayUntil() 会自动更新 pxPreviousWakeTime。
xTimeIncrement: 任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次延时的节拍数)
(1)挂起任务调度器。
(2)记录进入函数 vTaskDelayUntil() 的时间值,并保存在 xCountTickCount 中。所以 xCountTickCount 中保存的是进入函数 vTaskDelayUntil() 的时间值。
(3)根据延时时间 xTimeIncrement 来计算任务下一次要唤醒的时间点,并保存在 xTimeToWake 中。可以看出这个延时时间是相对于 pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。pxPrevious WakeTime、xTimeToWake、xTimeIncrement 和 xConstTickCount 的关系如下:
其中,(1)为任务主体,也就是任务真正要做的工作,(2)是任务函数中调用 vTaskDelayUntil() 对任务进行延时,(3)为其他任务在运行。任务的延时时间是 xTimeIncrement,这个延时时间是相对于 pxPreviousWakeTime 的,可以看出任务总的执行时间一定要小于任务的延时时间 xTimeIncrement!也就是说如果使用 vTaskDelayUntil() 的话相当于任务的执行周期永远都是 xTimeIncrement,而任务一定要在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行了,这个延时值就是绝对延时时间,因此函数 vTaskDelayUntil() 也叫做绝对延时函数。
(4)理论上 xCountTickCount 要大于 pxPreviousWakeTime 的,但是也有一种情况会导致 xConstTickCount 小于 pxPreviousWakeTime,那就是 xConstTickCount 溢出了!
(5)既然 xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且 xTimeToWake 肯定也是要大于 xConstTickCount 的。
(6)满足(5)条件的话就将 pdTRUE 赋值给 xShouldDelay,标记允许延时。
(7)还有其他两种情况,一:只有 xTimeToWake 溢出,二:都没有溢出。
只有 xTimeToWake 溢出的话如下图所示:
(8)将 pdTRUE 赋值给 xShouldDelay,标记允许延时。
(9)更新 pxPreviousWakeTime 的值,更新为 xTimeToWake,为本函数的下一次执行做准备。
(10)经过前面的判断,允许进行任务延时。
(11)调用函数 prvAddCurrentTaskToDelayedList() 进行延时。函数的第一个参数就是设置任务的阻塞时间,在之前,我们已经计算出了任务的下一次唤醒时间点了,那么任务还需要的阻塞时间就是下一次唤醒时间点 xTimeToWake 减去当前的时间 xConstTickCount 。而在函数 vTaskDelay() 中只是简单的将这参数设置为 xTicksToDelay。
(12)调用函数 xTaskResumeAll() 恢复任务调度器。
函数 vTaskDelayUntil() 的使用方法如下:
void TestTask( void * pvParameters )
{
TickType_t PreviousWakeTime;
//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
//设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
PreviousWakeTime = xTaskGetTickCount(); //获取当前的系统节拍值
for( ;; )
{
/******************************************************************/
/*************************任务主体*********************************/
/******************************************************************/
//调用函数 vTaskDelayUntil 进行延时
vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
}
}
参数:
pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用 vTaskDelayUntil 的话需要将 pxPreviousWakeTime 初始化进入任务的 while() 循环体的时间点值。在以后的运行中函数 vTaskDelayUntil() 会自动更新 pxPreviousWakeTime。
xTimeIncrement: 任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次延时的节拍数)
函数的第一个参数是上一次任务延时结束被唤醒的时间点,通过调用函数 xTaskGetTickCount 来获取即可。
函数的第二个参数是任务需要延时的时间节拍数(注意:延时50ms,不能直接设置延时时间,需要转换成延时节拍数,通过调用函数 pdMS_TO_TICKS 转换即可)
其实使用函数 vTaskDelayUntil() 延时的任务也不一定就能周期性的运行,使用函数 vTaskDelayUntil() 只能保证你按照一定的周期取消阻塞,进入就绪态。如果你有更高优先级或者中断的话还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于 vTaskDelay() 这个简单的延时函数而言的。
2. FreeRTOS 系统时钟节拍
不管是什么系统,运行都需要有个系统时钟节拍,xTickCount 就是 FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中 xTickCount 就会加一,xTickCount 的具体操作过程是在函数 xTaskIncrementTick() 中进行的,此函数在文件 task.c 中有定义:
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
//每个时钟节拍中断(滴答定时器中断)调用一次本函数,增加时钟节拍计数器 xTickCount 的
//值,并且检查是否有任务需要取消阻塞。
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) (1)
{
const TickType_t xConstTickCount = xTickCount + 1; (2)
//增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列
//表指针值。
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) (3)
{
taskSWITCH_DELAYED_LISTS(); (4)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升
//序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的
//延时时间都没有到的话后面的任务就不用看了,肯定也没有到。
if( xConstTickCount >= xNextTaskUnblockTime ) (5)
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) (6)
{
//延时列表为空,设置 xNextTaskUnblockTime 为最大值。
xNextTaskUnblockTime = portMAX_DELAY; (7)
break;
}
else
{
//延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值
//判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。
pxTCB = ( TCB_t * )\ (8)
listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue =\ (9)
listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue ) (10)
{
//任务延时时间还没到,但是 xItemValue 保存着下一个即将解除
//阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新
//变量 xNextTaskUnblockTime
xNextTaskUnblockTime = xItemValue; (11)
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//将任务从延时列表中移除
( void ) uxListRemove( &( pxTCB->xStateListItem ) ); (12)
//任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些
//任务从相应的事件列表中移除。相当于等待事件超时退出!
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ (13)
= NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) ); (14)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//将任务添加到就绪列表中
prvAddTaskToReadyList( pxTCB ); (15)
#if ( configUSE_PREEMPTION == 1 )
{
//使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
//运行的任务优先级,如果是的话就需要进行一次任务切换!
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) (16)
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
//如果使能了时间片的话还需要处理同优先级下任务之间的调度
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) (17)
{
if( listCURRENT_LIST_LENGTH( &( \
pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
//使用时钟节拍钩子函数
#if ( configUSE_TICK_HOOK == 1 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook(); (18)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else //任务调度器挂起 (19)
{
++uxPendedTicks; (20)
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE ) (21)
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired; (22)
}
(1)判断任务调度器是否被挂起。
(2)将时钟节拍计数器 xTickCount 加一,并将结果保存在 xConstTickCount 中,下一行程会将 xConstTickCount 赋值给 xTickCount,相当于给 xTickCount 加一。
(3)xCountTickCount 为 0,说明发生了溢出!
(4)如果发生了溢出的话使用函数 taskSWITCH_DELAYED_LISTS 将延时列表指针 pxDelayedTaskList 和溢出列表指针 pxOverflowDelayedTaskList 所指向的列表进行交换,函数 taskSWITCH_DELAYED_LISTS() 本质上是个宏,在文件 tasks.c 中有定义,将这两个指针所指向的列表交换以后还需要更新 xNextTaskUnblockTime 的值。
(5)变量 xNextTaskUnblockTime 保存着下一个要解除阻塞的任务的时间点值,如果 xConstTickCount 大于 xNextTaskUnblockTime 的话就说明有任务需要解除阻塞了。
(6)判断延时列表是否为空。
(7)如果延时列表为空的话就将 xNextTaskUnblockTime 设置为 portMAX_DELAY。
(8)延时列表不为空,获取延时列表第一个列表项对应的任务控制块。
(9)获取(8)中获取到的任务控制块中的状态列表项值。
(10)任务控制块中的状态列表项值保存了任务的唤醒时间点,如果这个唤醒时间点值大于当前的系统时钟(时钟节拍计数器值),说明任务的延时时间还未到。
(11)任务延时时间还未到,而且 xItemValue 已经保存了下一个要唤醒的任务的唤醒时间点,所以需要用 xItemValue 来更新 xNextTaskUnblockTime。
(12)任务延时时间到了,所以将任务先从延时列表中移除。
(13)检查任务是否还在等待某个事件,比如等待信号量、队列等。如果还在等待的话就将任务从相应的事件列表中移除。因为超时时间到了!
(14)将任务从相应的事件列表中移除。
(15)任务延时时间到了,并且任务已经从延时列表或者事件列表中移除了。所以这里需要将任务添加到就绪列表中。
(16)如果延时时间到的任务的任务优先级还高于正在运行的任务的任务优先级,此时就需要进行任务切换了,标记 xSwitchRequired 为 pdTRUE,表示需要进行任务切换。
(17)如果使能了时间片调度的话,还要处理跟时间片调度有关的工作。
(18)如果使能了时间片钩子函数的话就执行时间片钩子函数 vApplicationTickHook() ,函数的具体内容由用户自行编写。
(19)如果调用函数 vTaskSuspendAll() 挂起了任务调度器的话,在每个滴答定时器中断中就不会更新 xTickCount 了。取而代之的是用 uxPendedTicks 来记录调度器挂起过程中的时钟节拍数。这样在调用函数 xTaskResumeAll() 恢复任务调度器的时候就会调用 uxPendedTicks 次函数 xTaskIncrementTick(),这样 xTickCount 就会恢复,并且那些应该取消阻塞的任务都会取消阻塞。
函数 xTaskResumeAll() 中相应的处理代码如下:
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
configASSERT( uxSchedulerSuspended );
taskENTER_CRITICAL();
/************************************************************************/
/****************************省略部分代码********************************/
/************************************************************************/
UBaseType_t uxPendedCounts = uxPendedTicks;
if( uxPendedCounts > ( UBaseType_t ) 0U )
{
//do-while()循环体,循环次数为 uxPendedTicks
do
{
if( xTaskIncrementTick() != pdFALSE ) //调用函数 xTaskIncrementTick
{
xYieldPending = pdTRUE; //标记需要进行任务调度。
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedCounts; //变量减一
} while( uxPendedCounts > ( UBaseType_t ) 0U );
uxPendedTicks = 0; //循环执行完毕,uxPendedTicks 清零
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/************************************************************************/
/****************************省略部分代码********************************/
/************************************************************************/
taskEXIT_CRITICAL();
return xAlreadyYielded;
}
(20)uxPendedTicks 是个全局变量,在文件 task.c 中定义,任务调度器挂起以后此变量用来记录时钟节拍数。
(21)有时候调用其他的 API 函数会使用变量 xYieldPending 来标记是否需要进行上下文切换。
(22)返回 xSwitchRequired 的值,xSwitchRequired 保存了是否进行任务切换的信息,如果为 pdTRUE 的话就需要进行任务切换,pdFALSE 的话就不需要进行任务切换了。函数 xPortSysTickHandler() 中调用 xTaskIncrementTick() 的时候就会判断返回值,并且根据返回值决定是否进行任务切换。
FreeRTOS 中的延时函数会产生系统调用,进行任务切换,其本质如上图所示:
延时时间必须大于 0 才有效,将调度器挂起,保证此时不再调用新的任务上 CPU 运行,然后将此时正在运行的任务加入到阻塞队列,通过滴答定时器记录时间,当时间到达延时的时间后,进行任务切换,将原本运行的任务从阻塞队列中调入就绪队列;文章来源:https://www.toymoban.com/news/detail-519591.html
这就是延时函数运行的本质;文章来源地址https://www.toymoban.com/news/detail-519591.html
到了这里,关于FreeRTOS_时间管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!