系统提供了两个任务延时函数:相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil()
相对延时是指:vTaskDelay()开始执行到退出执行的时间固定
/* 相对延时函数 */
void vTaskDelay(const TickType_t xTicksToDelay)
{
BaseType_t xAlreadyYielded = pdFALSE;
/* 延时时间大于0 */
if(xTicksToDelay > (TickType_t)0U)
{
configASSERT(uxSchedulerSuspended == 0);
/* 调度器挂起 */
vTaskSuspendAll();
{
traceTASK_DELAY();
/* 将任务添加到延时列表 */
prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE);
}
/* 解除调度器挂起,解除的时候可能请求调度 */
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 解除调度器挂起的时候没有请求调度 */
if(xAlreadyYielded == pdFALSE)
{
/* 请求切换任务 */
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
绝对延时是指:相邻两次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();
{
const TickType_t xConstTickCount = xTickCount;
/* 下一次任务唤醒时间 */
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
/* 前一次唤醒时间大于系统当前节拍,说明系统节拍已经溢出 */
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,留作下一次使用 */
*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();
}
}
系统节拍采用32位计数,所以最终肯定会溢出,同样唤醒时间也存在溢出。系统一共维护了两个延时列表,分别是延时列表和溢出延时列表,两条列表都是按照唤醒时间的从小到大进行排列。当唤醒时间溢出的时候,就不能和没溢出的插入同一个列表了。系统将唤醒时间没有溢出的任务放入延时列表,将唤醒时间溢出的任务放入溢出延时列表。
/* 将任务添加到延时列表 */
static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely)
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;
#if (INCLUDE_xTaskAbortDelay == 1)
{
pxCurrentTCB->ucDelayAborted = pdFALSE;
}
#endif
/* 当前任务肯定挂接在就绪列表中,将其移除。该就绪列表中没有任何任务 */
if(uxListRemove(&(pxCurrentTCB->xStateListItem)) == (UBaseType_t)0)
{
/* 清除任务优先级记录中的优先级 */
portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#if (INCLUDE_vTaskSuspend == 1)
{
/* 延时时间为portMAX_DELAY并且允许无限期阻塞 */
if((xTicksToWait == portMAX_DELAY) && (xCanBlockIndefinitely != pdFALSE))
{
/* 将任务挂接到挂起列表 */
vListInsertEnd(&xSuspendedTaskList, &(pxCurrentTCB->xStateListItem));
}
else
{
/* 计算唤醒时间 */
xTimeToWake = xConstTickCount + xTicksToWait;
/* 设置状态列表项值为唤醒时间 */
listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
/* 唤醒时间小于当前节拍说明已经溢出了 */
if(xTimeToWake < xConstTickCount)
{
/* 将当前任务挂接到延时溢出列表 */
vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
}
/* 唤醒时间大于当前节拍说明没有溢出 */
else
{
/* 将当前任务挂接到延时列表 */
vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));
/* 当前任务唤醒时间,小于原先下一个解除阻塞任务的唤醒时间 */
if(xTimeToWake < xNextTaskUnblockTime)
{
/* 更新当前任务唤醒时间为下一个解除阻塞任务的唤醒时间 */
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
#else
{
xTimeToWake = xConstTickCount + xTicksToWait;
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
if( xTimeToWake < xConstTickCount )
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));
if(xTimeToWake < xNextTaskUnblockTime)
{
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(void)xCanBlockIndefinitely;
}
#endif
}
当前系统节拍到达最近的唤醒时间时,系统会将应该唤醒的任务从延时列表中清除,并挂接到就绪列表中。
/* 系统节拍加一 */
BaseType_t xTaskIncrementTick(void)
{
TCB_t *pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK(xTickCount);
/* 调度器没有被挂起 */
if(uxSchedulerSuspended == (UBaseType_t)pdFALSE)
{
/* 系统节拍计数器加一 */
const TickType_t xConstTickCount = xTickCount + (TickType_t)1;
xTickCount = xConstTickCount;
/* 系统节拍溢出 */
if(xConstTickCount == (TickType_t)0U)
{
/* 切换延时列表,更新下一次解除阻塞的时间 */
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 处理延时列表中超时的任务 */
if(xConstTickCount >= xNextTaskUnblockTime)
{
for(;;)
{
/* 如果延时列表已经为空,则将下一次解除时间设为最大 */
if(listLIST_IS_EMPTY(pxDelayedTaskList) != pdFALSE)
{
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
/* 延时列表不是空的 */
else
{
/* 获取延时列表中需要最先解除的任务 */
pxTCB = 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);
......
}
}
}
......
}
/* 调度器被挂起 */
else
{
......
}
......
return xSwitchRequired;
}
一旦系统节拍出现溢出,系统会对延时列表和溢出延时列表进行切换。由于系统节拍溢出,原来溢出延时列表变成了延时列表,而原来的延时列表里的任务在系统节拍溢出之后肯定已经全部被超时唤醒,之后这条原先的延时列表则被拿来作为溢出延时列表。
/* 切换延时列表 */
#define taskSWITCH_DELAYED_LISTS() \
{ \
List_t *pxTemp; \
\
configASSERT((listLIST_IS_EMPTY(pxDelayedTaskList))); /* 延时列表必须为空 */ \
\
/* 交换延时列表和溢出延时列表 */ \
pxTemp = pxDelayedTaskList; \
pxDelayedTaskList = pxOverflowDelayedTaskList; \
pxOverflowDelayedTaskList = pxTemp; \
\
xNumOfOverflows++; /* 系统节拍溢出次数加一 */ \
prvResetNextTaskUnblockTime(); /* 更新下一个要解除阻塞的时间 */ \
}