分类: 嵌入式
2014-09-27 15:31:46
时间管理包括两个方面:系统节拍的维护,产生;以及任务延时管理。下面分别讨论下。
时钟节拍
操作系统总是需要个时钟节拍的,这个需要硬件支持。freertos同样需要一个time tick产生器,通常是用处理器的硬件定时器来实现这个功能。它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。freertos的时钟节拍isr中除去保存现场,灰度现场这些事情外,核心的工作就是调用vTaskIncrementTick()函数。
比如CortexM3 STM32中处理器的移植代码中,对应的时钟节拍isr如下:
void xPortSysTickHandler( void )
{
unsigned long ulDummy;
#if configUSE_PREEMPTION == 1
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
#endif
ulDummy = portSET_INTERRUPT_MASK_FROM_ISR();
{
vTaskIncrementTick();
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulDummy );
}
vTaskIncrementTick()函数主要做两件事情:维护系统时间(以tick为单位,多少个节拍);处理那些延时的任务,如果延时到期,则唤醒任务。
inline void vTaskIncrementTick( void )
{
if( uxSchedulerSuspended == ( unsigned portBASE_TYPE ) pdFALSE )
{
++xTickCount;
//如果加完后等于0,则说明溢出了
if( xTickCount == ( portTickType ) 0 )
{
xList *pxTemp;
pxTemp = pxDelayedTaskList;
pxDelayedTaskList = pxOverflowDelayedTaskList;
pxOverflowDelayedTaskList = pxTemp;
xNumOfOverflows++;
}
prvCheckDelayedTasks();
}
else
{//如果调度器被禁止,则我们把丢失的时钟节拍记录在全局变量uxMissedTicks中
++uxMissedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
extern void vApplicationTickHook( void );
vApplicationTickHook();
}
#endif
}
#if ( configUSE_TICK_HOOK == 1 )
{
extern void vApplicationTickHook( void );
if( uxMissedTicks == 0 )
{
vApplicationTickHook();
}
}
#endif
traceTASK_INCREMENT_TICK( xTickCount );
}
上一篇“freertos任务管理分析”中已经说过,freertos定义了如下两个链表:
static xList xDelayedTaskList1;
static xList xDelayedTaskList2;
另外定义了两个指针:
static xList * volatile pxDelayedTaskList;
static xList * volatile pxOverflowDelayedTaskList;
这两个双向链表把所有需要延时的任务挂在上面,而pxDelayedTaskList则永远指向当前正在使用那个链表(可能是xDelayedTaskList1,也可能是xDelayedTaskList2)。而pxOverflowDelayedTaskList指向的链表包含的任务都是延时到期的绝对时间发生溢出的那些任务。所谓延时绝对时间就是任务相延时的时间(比如延时100个节拍)加上当前系统时间(即xTickCount)的值。
如果一个时钟节拍中断发生,在vTaskIncrementTick()函数中发现xTickCount溢出了,那么我们就需要交换pxDelayedTaskList和pxOverflowDelayedTaskList。
任务延时管理
任务可能需要延时,两种情况,一种是任务被vTaskDelay或者vTaskDelayUntil延时,另外一种情况就是任务等待事件(比如semaphore)时候指定了timeout(即最多等待timeout时间,如果等待的事件还没发生,则不再继续等待)
延时管理离不开上面的时钟节拍isr。下面先看vTaskDelay()函数。它的功能就是把任务从就绪链表中拿下,加到xDelayedTaskList1或者xDelayedTaskList2上。
//参数:xTicksToDelay—延时的节拍数
void vTaskDelay( portTickType xTicksToDelay )
{
portTickType xTimeToWake;
signed portBASE_TYPE xAlreadyYielded = pdFALSE;
if( xTicksToDelay > ( portTickType ) 0 )
{//禁止调度器
vTaskSuspendAll();
{
traceTASK_DELAY();
xTimeToWake = xTickCount + xTicksToDelay;
vListRemove( ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xGenericListItem ), xTimeToWake );
//判断xTimeToWake是否溢出
if( xTimeToWake < xTickCount )
{
vListInsert( ( xList * ) pxOverflowDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
}
else
{
vListInsert( ( xList * ) pxDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
}
}
//打开调度器
xAlreadyYielded = xTaskResumeAll();
}
if( !xAlreadyYielded )
{
taskYIELD();
}
}
这里巧妙的设计是vListInsert(),我们调用它将任务插到pxDelayedTaskList/ pxOverflowDelayedTaskList中,但是我们并不是随便找个地方插入的,而是按照延时的到期时间(即唤醒时间xTimeToWake)的大小,按照从小到大的次序插入到链表的,这样vTaskIncrementTick()里调用的prvCheckDelayedTasks()只需要检查pxDelayedTaskList链表的第一个任务的唤醒时间是否已到,而不需要扫描整个链表,这个效率是O(1)的,非常高。
vTaskIncrementTick()àprvCheckDelayedTasks()
#define prvCheckDelayedTasks()
{ \
register tskTCB *pxTCB;
//得到延时任务链表上的第一个任务(链表头)
while( ( pxTCB = ( tskTCB * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ) ) != NULL )
{
//如果当前时间小于第一个任务的唤醒时间,则说明也不需要做
if( xTickCount < listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) ) )
{
break;
}
//否则,则第一个任务到期,把它从延时任务链表上拿下
vListRemove( &( pxTCB->xGenericListItem ) );
if( pxTCB->xEventListItem.pvContainer )
{
vListRemove( &( pxTCB->xEventListItem ) );
}
//加到就绪链表
\
prvAddTaskToReadyQueue( pxTCB );
//然后再检查延时任务链表上的下一个任务是否到期。这是因为有可能多个任务的
}
}
FreeRTOS提供了另外一个延时函数vTaskDelayUntil(),这个函数与vTaskDelay()的不同之处在于:一个周期性任务可以利用它可以保证一个固定的(确定的)常数执行频率,而vTaskDelay()无法保证。原因如下:vTaskDelay()指定的延时量是相对于当前调用vTaskDelay()这个函数的时刻而言的,比如你传给vTaskDelay()函数参数xTicksToDelay=100,意味这你这个任务将以调用vTaskDelay()这个函数的时刻为零点,延时100个时钟节拍后被唤醒。
因为从任务被唤醒开始执行到下一次调用vTaskDelay这段时间不是个确定的常数(因为可能在多次调用vTaskDelay之间任务执行的路径是不一样的),所以没办法通过调用vTaskDelay来实现这个功能。然而vTaskDelayUtil不一样,它指定的延时节拍数是相对于前一次调用vTaskDelayUtil而言的,这样就能保证比较精确的两次调用之间的时间是个确定的常数。
void vTaskDelayUntil( portTickType * const pxPreviousWakeTime, portTickType xTimeIncrement )
{
portTickType xTimeToWake;
portBASE_TYPE xAlreadyYielded, xShouldDelay = pdFALSE;
//禁止调度
vTaskSuspendAll();
{
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
//自从上次调用vTaskDelayUtil,到现在xTickCount已经溢出
if( xTickCount < *pxPreviousWakeTime )
{
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xTickCount ) )
{
xShouldDelay = pdTRUE;
}
}
*pxPreviousWakeTime = xTimeToWake;
if( xShouldDelay )
{//如果确实需要延时,下面就和vTaskDelay一样做延时
traceTASK_DELAY_UNTIL();
vListRemove( ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xGenericListItem ), xTimeToWake );
if( xTimeToWake < xTickCount )
{
vListInsert( ( xList * ) pxOverflowDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
}
else
{
vListInsert( ( xList * ) pxDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
}
}
}
xAlreadyYielded = xTaskResumeAll();
if( !xAlreadyYielded )
{
taskYIELD();
}
}