博主联系方式:QQ:1256153255 ,邮箱:1256153255@qq.com
最直接的办法就是从函数vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement )的源码入手分析
#if ( INCLUDE_vTaskDelayUntil == 1 )
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 );
/*通过挂起来创建临界区,保证suspend和resume之间不会切换到其他任务*/
vTaskSuspendAll();
{
/* 在这部分代码块中,tick count是不允许变化的。
保存系统节拍中断次数计数器 */
const TickType_t xConstTickCount = xTickCount;
/* 计算任务下次唤醒时间(以系统节拍中断次数表示) */
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
/* *pxPreviousWakeTime中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了 */
if( xConstTickCount < *pxPreviousWakeTime )
{
/*只有当周期性延时时间大于任务主体代码执行时间,才会将任务挂接到延时列表.*
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 也都是保证周期性延时时间大于任务主体代码执行时间 */
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 更新唤醒时间,为下一次调用本函数做准备. */
*pxPreviousWakeTime = xTimeToWake;
if( xShouldDelay != pdFALSE )
{
traceTASK_DELAY_UNTIL( xTimeToWake );
/* 将本任务加入延时列表,注意阻塞时间并不是以当前时间为参考,因此减去了当前系统节拍中断计数器值*/
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll();
/* 强制执行一次上下文切换 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskDelayUntil */
其实保证固定挂起时长的关键点就是这句代码
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
从源码中我们知道
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
xConstTickCount = xTickCount;
而pxPreviousWakeTime用于指向一个变量,变量保存上次任务解除阻塞的时间。
也就是说
pxPreviousWakeTime :表示上次任务解除阻塞时间
xTimeIncrement:表示任务挂起时长
xTimeToWake :表示任务下次唤醒时间
xConstTickCount:表示当前系统节拍中断次数
我们从下图开始分析
①调度器启动,第1次调用vTaskDelayUntil(),任务A进入阻塞。
此时pxPreviousWakeTime 等于当前系统节拍中断计数器值xTickCount记为pxPreviousWakeTime1;xConstTickCount 也等于当前系统节拍中断计数器值xTickCount记为xConstTickCount1。
在函数vTaskDelayUntil()中计算出了下一次任务A唤醒时间xTimeToWake = *pxPreviousWakeTime + xTimeIncrement记为xTimeToWake1,此时pxPreviousWakeTime指向xTimeToWake1记为pxPreviousWakeTime2。
在函数vTaskDelayUntil()中调用prvAddCurrentTaskToDelayedList( xTimeToWake1 - xConstTickCount1, pdFALSE )来计算任务A延时时长,由上图可知此时阻塞时长等于延时时长。
阻塞时长 = 任务A下一次唤醒时刻 - 任务A上一次唤醒时刻
= xTimeToWake1 - pxPreviousWakeTime1
= pxPreviousWakeTime1 + xTimeIncrement - pxPreviousWakeTime1
= xTimeIncrement
同时
阻塞时长 = 延时时长 + 任务A执行时长 = 延时时长
②阻塞时间到,任务A占用CPU开始执行
任务A执行完之后,第2次调用vTaskDelayUntil(),任务A进入阻塞。此时
xConstTickCount为当前系统节拍中断计数器值,记为xConstTickCount2
xTimeToWake记为xTimeToWake2
在函数vTaskDelayUntil()中调用prvAddCurrentTaskToDelayedList( xTimeToWake2 - xConstTickCount2, pdFALSE )来计算任务A延时时长
阻塞时长 = 任务A下一次唤醒时刻 - 任务A上一次唤醒时刻
= xTimeToWake2 - pxPreviousWakeTime2
= pxPreviousWakeTime2 + xTimeIncrement - pxPreviousWakeTime2
= xTimeIncrement
同时
阻塞时长 = 延时时长 + 任务A执行时长
= (xTimeToWake2 - xConstTickCount2)+ (xConstTickCount2 - pxPreviousWakeTime2)
= xTimeToWake2 - pxPreviousWakeTime2
= xTimeIncrement
③当任务A在执行过程中有中断或更高优先级任务抢占了CPU的使用权,怎任务A被挂起,待中断或更高优先级任务执行完成,再继续执行任务A
此时
阻塞时长 = 任务A下一次唤醒时刻 - 任务A上一次唤醒时刻
= xTimeToWake3 - pxPreviousWakeTime3
= pxPreviousWakeTime3 + xTimeIncrement - pxPreviousWakeTime3
= xTimeIncrement
同时
阻塞时长 = 延时时长 + (任务A执行时长 + 中断执行时长)
= (xTimeToWake3 - xConstTickCount3)+ (xConstTickCount3 - pxPreviousWakeTime3)
= xTimeToWake3 - pxPreviousWakeTime3
= xTimeIncrement
通过以上分析可知,使用vTaskDelayUntil()将任务A延时,最终延时时长都是xTimeIncrement,也就做到了固定周期延时。
在这个过程中任务A的阻塞时长是固定的,任务A的执行时长也是固定的,中断或其他更高优先级任务的执行时间是不固定的,因此其他任务的执行时间被占用,导致其他任务的执行时长缩短。