既然是延时列表,那肯定要先定义相应的链表,延时列表的定义如下。这里定义了两条延时列表(其实就是前面小节里面提到的链表的根节点),一条是准备当记录 S y s t i c k Systick Systick周期个数的变量 x T i c k C o u n t xTickCount xTickCount溢出的时候使用的。这里还定义了指向两条链表的指针。
c
/*< Delayed tasks. */
List_t xDelayedTaskList1;
/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
List_t xDelayedTaskList2;
/*< Points to the delayed task list currently being used. */
List_t * volatile pxDelayedTaskList;
/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
List_t * volatile pxOverflowDelayedTaskList;
延时列表实现延时的基本思想是,当任务需要延时的时候就将任务插入到延时列表,不让任务继续执行且将任务从就绪列表里面删除,同时记录延时结束的时刻,延时结束的时刻为当前的记录 S y s t i c k Systick Systick周期个数的变量 x T i c k C o u n t xTickCount xTickCount值加上当前任务需要延时的 S y s t i c k Systick Systick周期个数的值。当 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值等于对应任务记录的延时结束时刻的时候,对应任务延时结束重新加入就绪列表并退出延时列表。变量 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime是一个在源文件 t a s k s . c tasks.c tasks.c中定义的全局变量,它表示下一个即将结束延时的任务的时刻的值,它在接口 v T a s k S t a r t S c h e d u l e r vTaskStartScheduler vTaskStartScheduler里面初始化为值 0 x F F F F F F F F 0xFFFFFFFF 0xFFFFFFFF(这里需要注意的是假设现在有两个任务几乎在同一时刻0,还是有先后顺序,进行了延时操作,任务1延时5个 S y s t i c k Systick Systick周期,任务2延时10个 S y s t i c k Systick Systick周期,任务1先调用延时操作,任务2后调用延时操作,那么此时任务1的结束时刻为5,任务2的结束时刻为10,但是这里 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值并不等于10,也就是说 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值永远指向最先结束延时的时刻)。
延时列表定义之后肯定要初始化,前面的小节中的实现只有就绪列表,因此接口 p r v I n i t i a l i s e T a s k L i s t s prvInitialiseTaskLists prvInitialiseTaskLists中只对就绪列表进行了初始化,但是现在延时列表的初始化也在这个接口里面。有了延时列表之后,前面在任务控制块里面添加的元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay可以删除,同时延时接口 v T a s k D e l a y vTaskDelay vTaskDelay里面的操作 p x T C B − > x T i c k s T o D e l a y = x T i c k s T o D e l a y pxTCB->xTicksToDelay = xTicksToDelay pxTCB−>xTicksToDelay=xTicksToDelay改为调用接口 p r v A d d C u r r e n t T a s k T o D e l a y e d L i s t prvAddCurrentTaskToDelayedList prvAddCurrentTaskToDelayedList将相应的任务插入到延时列表。接口 p r v A d d C u r r e n t T a s k T o D e l a y e d L i s t prvAddCurrentTaskToDelayedList prvAddCurrentTaskToDelayedList(在 F r e e R T O S FreeRTOS FreeRTOS源码里面的 t a s k s . c tasks.c tasks.c文件里面定义,相对于源码中的定义这里做了大量的精简)的定义如下。该接口首先记录此时的 S y s t i c k Systick Systick周期个数值,然后将任务从就绪列表里面删除,如果删除该任务之后该任务之前所在的链表里面已经没有了任务就将该任务的优先级数在变量 u x T o p R e a d y P r i o r i t y uxTopReadyPriority uxTopReadyPriority对应的比特位清0。根据前面保存的 S y s t i c k Systick Systick周期个数值加上任务将要延时的 S y s t i c k Systick Systick周期个数就是结束延时的时刻,这个时刻的值会保存到任务控制块里面的元素 x S t a t e L i s t I t e m xStateListItem xStateListItem的元素 x I t e m V a l u e xItemValue xItemValue里面,延时列表是根据元素 x I t e m V a l u e xItemValue xItemValue的值做从小到大排序的。然后看 x T i c k C o u n t xTickCount xTickCount是否溢出,如果溢出就将当前任务插入到用于溢出的延时列表,如果没有溢出就当前任务插入到另一个延时列表,如果当前任务结束延时的时刻小于变量 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值,那么更新 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值为当前任务结束延时的时刻,因为 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值总是所有任务中延时结束时刻最早的任务的结束延时的时刻值。
c
/*
* The currently executing task is entering the Blocked state. Add the task to
* either the current or the overflow delayed task list.
*/
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait)
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;
/* Remove the task from the ready list before adding it to the blocked list
* as the same list item is used for both lists. */
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* The current task must be in a ready list, so there is no need to
* check, and the port reset macro can be called directly. */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); /*lint !e931 pxCurrentTCB cannot change as it is the calling task. pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called with scheduler suspended or in a critical section. */
}
/* Calculate the time at which the task should be woken if the event
* does not occur. This may overflow but this doesn't matter, the kernel
* will manage it correctly. */
xTimeToWake = xConstTickCount + xTicksToWait;
/* The list item will be inserted in wake time order. */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
if( xTimeToWake < xConstTickCount )
{
/* Wake time has overflowed. Place this item in the overflow list. */
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
/* The wake time has not overflowed, so the current block list is used. */
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* If the task entering the blocked state was placed at the head of the
* list of blocked tasks then xNextTaskUnblockTime needs to be updated
* too. */
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
}
}
相应的接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick也需要大的改动,但是常规操作还是每次都会将变量 x T i c k C o u n t xTickCount xTickCount的值加1。如果 x T i c k C o u n t xTickCount xTickCount发生溢出的话需要调用宏接口 t a s k S W I T C H _ D E L A Y E D _ L I S T S taskSWITCH\_DELAYED\_LISTS taskSWITCH_DELAYED_LISTS切换当前使用的延时列表为另一个延时列表。
c
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
/* Minor optimisation. The tick count cannot change in this
* block. */
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
/* Increment the RTOS tick, switching the delayed and overflowed
* delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
{
taskSWITCH_DELAYED_LISTS();
}
/* See if this tick has made a timeout expire. Tasks are stored in
* the queue in the order of their wake time - meaning once one task
* has been found whose block time has not expired there is no need to
* look any further down the list. */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The delayed list is empty. Set xNextTaskUnblockTime
* to the maximum possible value so it is extremely
* unlikely that the
* if( xTickCount >= xNextTaskUnblockTime ) test will pass
* next time through. */
xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
break;
}
else
{
/* The delayed list is not empty, get the value of the
* item at the head of the delayed list. This is the time
* at which the task at the head of the delayed list must
* be removed from the Blocked state. */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
/* It is not time to unblock this item yet, but the
* item value is the time at which the task at the head
* of the blocked list must be removed from the Blocked
* state - so record the item value in
* xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
}
/* It is time to remove the item from the Blocked state. */
uxListRemove( &( pxTCB->xStateListItem ) );
/* Place the unblocked task into the appropriate ready
* list. */
prvAddTaskToReadyList( pxTCB );
}
}
}
return pdTRUE;
}
宏接口 t a s k S W I T C H _ D E L A Y E D _ L I S T S taskSWITCH\_DELAYED\_LISTS taskSWITCH_DELAYED_LISTS(在 F r e e R T O S FreeRTOS FreeRTOS源码里面的 t a s k s . c tasks.c tasks.c文件里面定义)的定义如下。它在切换当前使用的延时列表之后还会调用接口 p r v R e s e t N e x t T a s k U n b l o c k T i m e prvResetNextTaskUnblockTime prvResetNextTaskUnblockTime(在 F r e e R T O S FreeRTOS FreeRTOS源码里面的 t a s k s . c tasks.c tasks.c文件里面定义)重新设置变量 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值,如果切换之后的延迟列表为空则将 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime初始化为值 0 x F F F F F F F F 0xFFFFFFFF 0xFFFFFFFF,否则将 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime初始化为最近的一个即将延迟结束的任务的延迟结束时刻。
c
/* pxDelayedTaskList and pxOverflowDelayedTaskList are switched when the tick
* count overflows. */
#define taskSWITCH_DELAYED_LISTS() \
{ \
List_t * pxTemp; \
\
/* The delayed tasks list should be empty when the lists are switched. */ \
configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) ); \
\
pxTemp = pxDelayedTaskList; \
pxDelayedTaskList = pxOverflowDelayedTaskList; \
pxOverflowDelayedTaskList = pxTemp; \
xNumOfOverflows++; \
prvResetNextTaskUnblockTime(); \
}
c
static void prvResetNextTaskUnblockTime( void )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The new current delayed list is empty. Set xNextTaskUnblockTime to
* the maximum possible value so it is extremely unlikely that the
* if( xTickCount >= xNextTaskUnblockTime ) test will pass until
* there is an item in the delayed list. */
xNextTaskUnblockTime = portMAX_DELAY;
}
else
{
/* The new current delayed list is not empty, get the value of
* the item at the head of the delayed list. This is the time at
* which the task at the head of the delayed list should be removed
* from the Blocked state. */
xNextTaskUnblockTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxDelayedTaskList );
}
}
接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick接着判断是否有任务的延时时间到期了如果有则判断当前的延时列表是否为空,如果为空则只是简单的更新 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值为 0 x F F F F F F F F 0xFFFFFFFF 0xFFFFFFFF并跳出循环,如果不为空则判断延时列表中最早延时结束的任务的延时时刻是否大于等于当前 S y s t i c k Systick Systick周期个数的变量 x T i c k C o u n t xTickCount xTickCount的值,如果是则将 x N e x t T a s k U n b l o c k T i m e xNextTaskUnblockTime xNextTaskUnblockTime的值更新为这个延时列表中最早延时结束的任务的延时时刻并跳出循环,否则将当前延时列表中最早延时结束的任务从延时列表删除并加入到就绪列表并继续循环。 如果觉得接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick这一部分代码不好理解的话主要是因为 S y s t i c k Systick Systick周期个数的变量 x T i c k C o u n t xTickCount xTickCount的值会溢出,如果仔细想一想 x T i c k C o u n t xTickCount xTickCount的溢出的情况应该可以较好的理解接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick这一部分代码。这里的实验现象应该和前一小节一样,工程代码在这里。