freeRTOS中使用延迟函数vTaskDelay()来对任务进行挂起延迟。
目录
vTaskDelay()实现原理:
将任务从就绪列表中删除,并添加到延迟列表中,从而任务调度时不会对延迟任务进行运行。直到延迟到期,任务重新回到就绪队列,参与任务调度。
当任务需要延时的时候, 则先将任务挂起,即先将任务从就绪列表删除,然后插入到任务延时列表,同时更新下一个任务的解锁时刻变量: xNextTaskUnblockTime 的值。
xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时的
值 xTicksToDelay。xNextTaskUnblockTime 的值初始化为可能的最大值。
使用了两个延迟链表维护被延迟挂起的任务。一个存储系统时基未溢出的延迟任务,一个存储系统时基溢出的延迟任务。
在vTaskDelay()中调用prvAddCurrentTaskToDelayedList(),将任务从就绪列表中移除,并添加延迟列表
prvAddCurrentTaskToDelayedList()实现如下:
任务被被延迟挂起时,要对就绪队列中相应的优先级进行清除,若对应优先级没有其它任务了(uxListRemove()会返回剩余节点数,所以判断是否为0),则将优先级位图表 uxTopReadyPriority的对应位清0。
被延迟挂起的任务的TCB排序值设置为该任务解锁时刻的值,插入到延迟列表中按升序排序。
当任务解锁时刻值未使系统时基溢出时,插入未溢出的延迟列表,并与xNextTaskUnblockTime 的值比较,若小于,则更新为当前任务的解锁时刻。
当任务解锁时刻值使系统时基溢出时,直接插入溢出的延迟列表,结束。
更新系统时基时对延迟列表中任务是否可恢复就绪状态进行判断:
SysTick每次中断调用更新系统时基的函数xTaskIncrementTick(),在xTaskIncrementTick()中完成对延迟列表中任务是否可恢复就绪状态进行判断。xTaskIncrementTick()实现如下:
每次只比较系统时基与xNextTaskUnblockTime的大小,每当系统时基到达下一个任务的解锁时刻变量:xNextTaskUnblockTime时,将解锁时刻值小于xNextTaskUnblockTime的任务从延迟列表移除,并添加到就绪队列(同时还会将任务的xEventListItem从等待列表中移除,上面简略了,见下图)。若延迟列表空,则只将xNextTaskUnblockTime设为最大值后结束。
注意在比较系统时基与xNextTaskUnblockTime前,还要先判断下一时刻系统时基是否溢出,若溢出了,需要将未溢出的延迟列表和溢出的延迟列表对换。通过taskSWITCH_DELAYED_LISTS()完成。
切换延迟列表和溢出列表后,在prvResetNextTaskUnblockTime()中还要使用新延迟列表的第一个节点的解锁时刻更新xNextTaskUnblockTime,同样,若新延迟列表为空,xNextTaskUnblockTime设为最大值。
问题思考:
1.为什么要额外使用一个列表存使系统时基溢出的延迟任务?
2.为什么xNextTaskUnblockTime要初始化为最大值?
1.当新任务被加入到延迟列表时,若使系统时基溢出,则会使系统时基从0重新开始计数,使用一个列表存使系统时基溢出的延迟任务确保该新挂起的任务延迟解锁时间不被误判
2.有新任务被加入到延迟列表时,若未使系统时基溢出,需要比较新任务的解锁时刻和xNextTaskUnblockTime,取较小值作为新的xNextTaskUnblockTime。当延迟列表为空时,设为最大值确保新任务能更新xNextTaskUnblockTime。