原创 freertos时间管理分析

2008-5-20 21:54 4588 8 14 分类: MCU/ 嵌入式


FreeRTOS时间管理分析



转载请注明出处:http://blog.ednchina.com/bluehacker



 



时间管理包括两个方面:系统节拍的维护,产生;以及任务延时管理。下面分别讨论下。



一.            
时钟节拍



操作系统总是需要个时钟节拍的,这个需要硬件支持。freertos同样需要一个time tick产生器,通常是用处理器的硬件定时器来实现这个功能。它周期性的产生定时中断,所谓的时钟节拍管理的核心就是这个定时中断的服务程序。freertos的时钟节拍isr中除去保存现场,灰度现场这些事情外,核心的工作就是调用vTaskIncrementTick()函数。



比如freescaleMCF5235系列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溢出,所以我们需要交换任务延时链表,系统定义了两个链表指针pxDelayedTaskListpxOverflowDelayedTaskList,其中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溢出了,那么我们就需要交换pxDelayedTaskListpxOverflowDelayedTaskList



 



二.       
任务延时管理



任务可能需要延时,两种情况,一种是任务被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中。






PARTNER CONTENT

文章评论6条评论)

登录后参与讨论

用户1354974 2008-9-10 17:07

博主辛苦了,写了这么多。 我现在也正在学习FreeRTOS,不过遇到很多困难,不知道博主有没有FreeRTOS的说明文档。 能不能发一份给我,谢谢。 邮箱:zigaohunan@163.com

yoyowind 2008-5-23 12:00

是的太不容易了,如果联系好了请在这里说一下吧,我现在有个MCF5272也没法调试。没有BDM怎么都没用,所以现在还用V2 CORE的52235等。

用户1361860 2008-5-23 09:00

晕倒,这两天我来联系下他们,看到底什么价格。想用coldfire不容易

yoyowind 2008-5-22 09:21

是的很贵,他的BDM和lisence都很贵。店大欺客这句话大家都晓得,呵呵。而freescale也就从来没指望在小公司小客户身上赚钱,他只重视大业务。所以个人想学很难。当然他也有一定的大学计划,我感觉并不是很成功。lisence的标准版的价格是995美圆,你可以参考一下。

用户1361860 2008-5-21 14:33

hi,我本来打算买现成的,很贵吗?我还真没料到。现在感觉freescale的市场策略很糟糕,CW和BDM这些东西搞那么贵干什么,这么大的公司难道还指望卖CW这些东西发财。在开发工具上面设门槛带来的恶果就是自己芯片在市场的占有率下降。一般个人或者小公司还是难以接受掏钱买这些东西的。

yoyowind 2008-5-21 09:12

你好bluehacker,你现在做的coldfire V4内核的调试工具是使用什么?直接买PE的吗?那很贵的。我现在自己做的BDM,只能调试V2内核不带外部总线的,V3,V4都没试过。想请教一下BDM怎么弄,谢谢。另外你给我的留言我已经回复到我的BLOG的评论里了。
相关推荐阅读
用户1361860 2012-06-28 23:44
Nicrosystem Freescale Kinetis教程---SDHC
这是研究生翻译的SDHC的中文文档,里面很多句子不通,但我现在没时间去修改了。先放出来,应该还是会有一点作用  ...
用户1361860 2012-06-26 12:39
Nicrosystem Freescale Kinetis教程--低功耗定时器
Freescale Kinetis内部集成了一个独特的低功耗定时器,它可以在系统处于低功耗模式下,仍然以极低功耗运行,可以用于在适当时候唤醒系统进入正常工作模式  ...
用户1361860 2012-06-24 22:11
Nicrosystem Freescale Kinetis教程----RTC实时时钟
Nicrosystem的飞思卡尔kinetis教程之片上RTC  ...
用户1361860 2012-06-22 10:21
TI C2000微控制器指南
这是官方的C2000的介绍,C2000做电机控制那是业界最好的。  ...
用户1361860 2012-06-20 23:52
Nicrosystem Freescale Kinetis教程--PIT定时器教程
这是PIT定时器的教程,PIT是 Kinetis支持的另一种定时器,相对于上一讲的flextimer要简单。 今晚赶到北京,到宾馆发一篇博客  ...
用户1361860 2012-06-19 13:15
Nicrosystem Freescale Kinetis教程--Flextimer教程
Kinetis的Flextimer定时器教程 kinetis集成了众多功能各异的定时器,flextimer是其中最为复杂的一个  ...
EE直播间
更多
我要评论
6
8
关闭 站长推荐上一条 /3 下一条