FreeRTOS时间管理分析
转载请注明出处:http://blog.ednchina.com/bluehacker
时间管理包括两个方面:系统节拍的维护,产生;以及任务延时管理。下面分别讨论下。
一.
时钟节拍
操作系统总是需要个时钟节拍的,这个需要硬件支持。freertos同样需要一个time tick产生器,通常是用处理器的硬件定时器来实现这个功能。它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。freertos的时钟节拍isr中除去保存现场,灰度现场这些事情外,核心的工作就是调用vTaskIncrementTick()函数。
比如freescale的MCF5235系列coldfire处理器的移植代码中,对应的时钟节拍isr如下:
static void
prvPortPreemptiveTick(
void )
{
asm volatile ( "move.w #0x2700, %sr\n\t" );
#if _GCC_USES_FP == 1
asm volatile ( "unlk %fp\n\t" );
#endif
portSAVE_CONTEXT( );
MCF_PIT_PCSR0 |= MCF_PIT_PCSR_PIF;
vTaskIncrementTick( );←-------这里调用
vTaskSwitchContext( );
portRESTORE_CONTEXT( );
}
vTaskIncrementTick()函数主要做两件事情:维护系统时间(以tick为单位,多少个节拍);处理那些延时的任务,如果延时到期,则唤醒任务。
inline void
vTaskIncrementTick( void )
{
/*检查调度器是否被禁止,如果没有被禁止. */
if( uxSchedulerSuspended == ( unsigned portBASE_TYPE ) pdFALSE
)
{/*xTickCount是一个系统全局变量,它维护系统当前时间(从启动到现在过去了多少个节拍),这里把这个变量加一*/
++xTickCount;
//如果加完后等于0,则说明溢出了
if( xTickCount == ( portTickType ) 0 )
{
xList *pxTemp;
/*
因为xTickCount溢出,所以我们需要交换任务延时链表,系统定义了两个链表指针pxDelayedTaskList和pxOverflowDelayedTaskList,其中pxDelayedTaskList始终指向当前正在使用的那个任务延时链表,而pxOverflowDelayedTaskList指向的则总是备用的那个任务链表
*在这里我们让pxDelayedTaskList指向原先由pxOverflowDelayedTaskList指向的那个链表,做个交换*/
pxTemp = pxDelayedTaskList;
pxDelayedTaskList = pxOverflowDelayedTaskList;
pxOverflowDelayedTaskList = pxTemp;
xNumOfOverflows++;
}
/* 检查有没有任务的延时已经到期,如果有,则唤醒 */
prvCheckDelayedTasks();
}
else
{//如果调度器被禁止,则我们把丢失的时钟节拍记录在全局变量uxMissedTicks中
++uxMissedTicks;
/* 调用用户定义的tick hook,即使调度器被禁止也要调用. */
#if ( configUSE_TICK_HOOK == 1 )
{
extern void vApplicationTickHook( void );
vApplicationTickHook();
}
#endif
}
#if ( configUSE_TICK_HOOK == 1 )
{
extern void vApplicationTickHook( void );
/* Guard against the tick hook being called when the
missed tick
count is being unwound (when the scheduler is being
unlocked. */
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;
/* 如果xTicksToDelay==0,则表示我们并不真的需要延时,而只是希望做一次调度*/
if( xTicksToDelay >
( portTickType ) 0 )
{//禁止调度器
vTaskSuspendAll();
{
traceTASK_DELAY();
/* 计算延时到期的绝对时间 */
xTimeToWake
= xTickCount + xTicksToDelay;
/* 把当前任务从就绪链表上拿下*/
vListRemove(
( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
/* 把任务延时到期的时间(即到何时任务该唤醒)值赋给TCB中的xGenericListItem.xItemValue */
listSET_LIST_ITEM_VALUE(
&( pxCurrentTCB->xGenericListItem ), xTimeToWake );
//判断xTimeToWake是否溢出
if(
xTimeToWake < xTickCount )
{
/* 唤醒时间(即前面我说的绝对到期时间)溢出,则把该任务加到pxOverflowDelayedTaskList . */
vListInsert( ( xList * )
pxOverflowDelayedTaskList, ( xListItem * ) &(
pxCurrentTCB->xGenericListItem ) );
}
else
{
/*
没有溢出,加到pxDelayedTaskList. */
vListInsert(
( xList * ) pxDelayedTaskList, ( xListItem * ) &(
pxCurrentTCB->xGenericListItem ) );
}
}
//打开调度器
xAlreadyYielded
= xTaskResumeAll();
}
/*如果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 ) ); \
/* Is the task waiting
on an event also? */ \
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而言的,这样就能保证比较精确的两次调用之间的时间是个确定的常数。
/*参数:pxPreviousWakeTime---上一次调用本函数的时间
* xTimeIncrement---相对于pxPreviousWakeTime本次延时的节拍数*/
void vTaskDelayUntil( portTickType * const pxPreviousWakeTime,
portTickType xTimeIncrement )
{
portTickType xTimeToWake;
portBASE_TYPE
xAlreadyYielded, xShouldDelay = pdFALSE;
//禁止调度
vTaskSuspendAll();
{
/* 计算下次唤醒的绝对时间 */
xTimeToWake =
*pxPreviousWakeTime + xTimeIncrement;
//自从上次调用vTaskDelayUtil,到现在xTickCount已经溢出
if( xTickCount
< *pxPreviousWakeTime )
{
/* 判断是否真的需要延时。只有当xTimeToWake < *pxPreviousWakeTime,并且xTimeToWake
> xTickCount时,才需要延时。这是因为:
1.
如果xTimeToWake>*pxPreviousWakeTime,则表示下一次唤醒的时间xTimeToWake并没有溢出,但是当前系统时间xTickCount已经溢出,也就是说xTimeToWake的时间早于xTickCount,当然不需要延时,我现在已经过了下次唤醒的时间了。
2.
如果xTimeToWake<*pxPreviousWakeTime ,但是xTimeToWake<xTickCount,则说明下次唤醒的时间xTimeToWake虽然也溢出了,但是还是早于当前系统时间xTickCount,故也不需要延时
3.
*/
if( (
xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xTickCount
) )
{//需要延时
xShouldDelay
= pdTRUE;
}
}
else
{
/* 如果系统当前时间xTickCount没有溢出,那么判断是否真的需要延时。标准是:xTimeToWake < *pxPreviousWakeTime或者xTimeToWake
> xTickCount;因为如果这两个条件都不满足,那么也就意味着xTimeToWake早于xTickCount,所以不需要延时 */
if( (
xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xTickCount ) )
{
xShouldDelay
= pdTRUE;
}
}
/*更新pxPreviousWakeTime,这样下次调用vTaskDelayUtil的时候才是相对于本次调用延时了xTimeIncrement个节拍. */
*pxPreviousWakeTime
= xTimeToWake;
if(
xShouldDelay )
{//如果确实需要延时,下面就和vTaskDelay一样做延时
traceTASK_DELAY_UNTIL();
/* We
must remove ourselves from the ready list before adding
ourselves
to the blocked list as the same list item is used for
both
lists. */
vListRemove(
( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
/* The
list item will be inserted in wake time order. */
listSET_LIST_ITEM_VALUE(
&( pxCurrentTCB->xGenericListItem ), xTimeToWake );
if(
xTimeToWake < xTickCount )
{
/* Wake time has overflowed. Place this item in the
overflow
list. */
vListInsert(
( xList * ) pxOverflowDelayedTaskList, ( xListItem * ) &(
pxCurrentTCB->xGenericListItem ) );
}
else
{
/*
The wake time has not overflowed, so we can use the
current
block list. */
vListInsert(
( xList * ) pxDelayedTaskList, ( xListItem * ) &(
pxCurrentTCB->xGenericListItem ) );
}
}
}
xAlreadyYielded =
xTaskResumeAll();
/* Force a reschedule
if xTaskResumeAll has not already done so, we may
have put ourselves to
sleep. */
if( !xAlreadyYielded )
{
taskYIELD();
}
}
关于任务延时的另外一种情况则是任务等待各种事件(信号量,mutex,消息队列)指定了timeout而把任务挂到任务延时链表中。其核心函数是xQueueGenericReceive(),这个下篇谈queue的时候再说吧。不过整体的思想是一样的,就是加到pxDelayedTaskList或者pxOverflowDelayedTaskList中。
用户1354974 2008-9-10 17:07
yoyowind 2008-5-23 12:00
用户1361860 2008-5-23 09:00
yoyowind 2008-5-22 09:21
用户1361860 2008-5-21 14:33
yoyowind 2008-5-21 09:12