http://blog.sina.com.cn/s/blog_5f0bed160100tqr3.html
2011
时间管理包括两个方面:系统节拍的维护,产生;以及任务延时管理。下面分别讨论下。
时钟节拍
操作系统总是需要个时钟节拍的,这个需要硬件支持。freertos同样需要一个timetick产生器,通常是用处理器的硬件定时器来实现这个功能。它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。freertos的时钟节拍isr中除去保存现场,灰度现场这些事情外,核心的工作就是调用vTaskIncrementTick函数。
比如CortexM3
void xPortSysTickHandler
{
unsigned long ulDummy;
}
vTaskIncrementTick函数主要做两件事情:维护系统时间;处理那些延时的任务,如果延时到期,则唤醒任务。
inline void vTaskIncrementTick
{
if pdFALSE )
{
++xTickCount;
//如果加完后等于0,则说明溢出了
if 0 )
{
xList *pxTemp;
pxTemp = pxDelayedTaskList;
pxDelayedTaskList = pxOverflowDelayedTaskLis
pxOverflowDelayedTaskLis
xNumOfOverflows++;
}
prvCheckDelayedTasks;
}
else
{//如果调度器被禁止,则我们把丢失的时钟节拍记录在全局变量uxMissedTicks中
++uxMissedTicks;
#if
{
extern void vApplicationTickHook;
vApplicationTickHook;
}
#endif
}
#if
{
extern void vApplicationTickHook;
if
{
vApplicationTickHook;
}
}
#endif
traceTASK_INCREMENT_TICK;
}
上一篇“freertos任务管理分析”中已经说过,freertos定义了如下两个链表:
static xList xDelayedTaskList1;
static xList xDelayedTaskList2;
另外定义了两个指针:
static xList * volatile pxDelayedTaskList;
static xList * volatilepxOverflowDelayedTaskLis
这两个双向链表把所有需要延时的任务挂在上面,而pxDelayedTaskList则永远指向当前正在使用那个链表。而pxOverflowDelayedTaskLis
如果一个时钟节拍中断发生,在vTaskIncrementTick函数中发现xTickCount溢出了,那么我们就需要交换pxDelayedTaskList和pxOverflowDelayedTaskLis
任务延时管理
任务可能需要延时,两种情况,一种是任务被vTaskDelay或者vTaskDelayUntil延时,另外一种情况就是任务等待事件时候指定了timeout
延时管理离不开上面的时钟节拍isr。下面先看vTaskDelay函数。它的功能就是把任务从就绪链表中拿下,加到xDelayedTaskList1或者xDelayedTaskList2上。
//参数:xTicksToDelay—延时的节拍数
void vTaskDelay
{
portTickType xTimeToWake;
signed portBASE_TYPE xAlreadyYielded = pdFALSE;
if0 )
{//禁止调度器
vTaskSuspendAll;
{
traceTASK_DELAY;
xTimeToWake = xTickCount + xTicksToDelay;
vListRemove & );
listSET_LIST_ITEM_VALUE, xTimeToWake );
//判断xTimeToWake是否溢出
if
{
vListInsert pxOverflowDelayedTaskLis
}
else
{
vListInsert pxDelayedTaskList, & );
}
}
//打开调度器
xAlreadyYielded = xTaskResumeAll;
}
if
{
taskYIELD;
}
}
这里巧妙的设计是vListInsert,我们调用它将任务插到pxDelayedTaskList/pxOverflowDelayedTaskLis
vTaskIncrementTickàprvCheckDelayedTasks
#defineprvCheckDelayedTasks
{
register tskTCB*pxTCB;
//得到延时任务链表上的第一个任务
whilelistGET_OWNER_OF_HEAD_ENTRY ) != NULL)
{
if ))
{
break;
}
vListRemove);
if
{
vListRemove);
}
//加到就绪链表
prvAddTaskToReadyQueue;
//然后再检查延时任务链表上的下一个任务是否到期。这是因为有可能多个任务的
}
}
FreeRTOS提供了另外一个延时函数vTaskDelayUntil,这个函数与vTaskDelay的不同之处在于:一个周期性任务可以利用它可以保证一个固定的常数执行频率,而vTaskDelay无法保证。原因如下:vTaskDelay指定的延时量是相对于当前调用vTaskDelay这个函数的时刻而言的,比如你传给vTaskDelay函数参数xTicksToDelay=100,意味这你这个任务将以调用vTaskDelay这个函数的时刻为零点,延时100个时钟节拍后被唤醒。
因为从任务被唤醒开始执行到下一次调用vTaskDelay这段时间不是个确定的常数,所以没办法通过调用vTaskDelay来实现这个功能。然而vTaskDelayUtil不一样,它指定的延时节拍数是相对于前一次调用vTaskDelayUtil而言的,这样就能保证比较精确的两次调用之间的时间是个确定的常数。
void vTaskDelayUntil
{
portTickType xTimeToWake;
portBASE_TYPE xAlreadyYielded, xShouldDelay =pdFALSE;
//禁止调度
vTaskSuspendAll;
{
xTimeToWake = *pxPreviousWakeTime +xTimeIncrement;
//自从上次调用vTaskDelayUtil,到现在xTickCount已经溢出
if
{
if || )
{
xShouldDelay = pdTRUE;
}
}
*pxPreviousWakeTime = xTimeToWake;
if
{//如果确实需要延时,下面就和vTaskDelay一样做延时
traceTASK_DELAY_UNTIL;
vListRemove & );
listSET_LIST_ITEM_VALUE, xTimeToWake );
if
{
vListInsert pxOverflowDelayedTaskLis
}
else
{
vListInsert pxDelayedTaskList, & );
}
}
}
xAlreadyYielded = xTaskResumeAll;
if
{
taskYIELD;
}
}