分享

FreeRTOS的任务调度算法优化实现

 心不留意外尘 2016-09-06

http://blog.csdn.net/wbwwf8685/article/details/51999668

2016

在前一篇博文实时操作系统的任务睡眠中提到,FreeRTOS在任务调度的流程中,会用一个循环遍历的方法查找最高优先级就绪态任务,这是一种较为简单的方案,有较大的优化空间。本文先对FreeRTOS的任务调度算法进行分析,接着借鉴了UCOSII系统的两级查表方法,对该流程进行优化,最后用两种方法测试同一测试用例,可以清楚的看到优化的效果。


FreeRTOS任务调度算法分析

FreeRTOS中默认将任务分为5个优先级(0~4,该值可以修改宏configMAX_PRIORITIES以配置),所以需要一个5个元素的数组pxReadyTaskList[configMAX_PRIORITIES],数组的每个元素表示对应优先级链表头结点,处于就绪态的具有相同优先级的任务会挂接到相同的链表上。其数据结构如下图所示:


实时操作系统都是运行最高优先级的任务,所以调度器的一项重要任务就是查找最高优先级就绪态任务。为此,FreeRTOS定义了一个全局变量uxTopReadyPriority,它记录了当前系统里最高优先级的就绪态任务。每当有新的任务从其它状态如睡眠转为就绪态时,系统就会判断,新任务的优先级是否高于uxTopReadyPriority,如果是的话,将uxTopReadyPriority修改为当前的任务优先级。


可是如果高优先级的任务从就绪转为睡眠,uxTopReadyPriority没有做相应的更新。而是在执行任务调度时,从第
uxTopReadyPriority项数组开始遍历,优先级从高到底,直到找到下一个就绪态的任务为止。
  1.    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )  
  2.   
  3. --uxTopReadyPriority;  
这种方法在系统最多只支持5个优先级的时候似乎效率很好,但是有个小漏洞。考虑一下,假设系统里只有两个任务,一个优先级是1,一个优先级是255。两个任务频繁的切换,这样,每次当255的任务从就绪态转为睡眠,调度器要查找到下一个可以执行的任务,需要循环255次!

快速查找最高优先级任务

基于前面的分析,我们知道,FreeRTOS在高优先级从就绪态移除时,并没有及时更新最高优先级变量uxTopReadyPriority,而是留到了下次调度时用遍历的方式再次确定下一个最高优先级。很明显,这种循环遍历的方法不是最优方案。参考之前的一篇博文ucosii实时操作系统的任务调度,我们可以引入几个全局变量,将系统里的就绪态任务以bit位图的形式保存起来并实时更新,在需要确定最高优先级时,只需要两步查表就可以得到结果,其查找时间是固定的,不随任务的多少,以及不受优先级的跨度的影响,非常高效。需要做的修改有以下几处:
1 增加查表所需的数据结构
  1. static    unsigned long       OSRdyGrp;  
  2. static    unsigned char       OSRdyTbl[32];  
  3. static    unsigned long  const  OSMapTbl[]   = {0x1, 0x2, 0x4, 0x8,   
  4.                                            0x10, 0x20, 0x40, 0x80,  
  5.                                            0x100,0x200, 0x400, 0x800,   
  6.                                            0x1000, 0x2000, 0x4000, 0x8000,  
  7.                                            0x10000, 0x20000, 0x40000, 0x80000,   
  8.                                            0x100000, 0x200000, 0x400000, 0x800000,  
  9.                                            0x1000000,0x2000000, 0x4000000, 0x8000000,   
  10.                                            0x10000000, 0x20000000, 0x40000000, 0x80000000};  
  11.   
  12. char  const  OSUnMapTbl[] = {  
  13.   0,  0,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  3,  
  14.   4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  
  15.   5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  
  16.   5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  
  17.   6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  
  18.   6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  
  19.   6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  
  20.   6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  
  21.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  22.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  23.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  24.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  25.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  26.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  27.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  
  28.   7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7  
  29. };  

需要注意,我们这里是假设系统支持0~255一共256个优先级,OSRdyGrp需要256/8 =32位的数值来对应于OSRdyTbl的32个表项,
因为FreeRTOS默认是优先级数值越大,优先级就越高,所以这里的OSUnMapTbl表格和UCOSII的不同,它的生成方法为:
  1. #include <stdio.h>  
  2.   
  3. int bit_map(int n)  
  4. {  
  5.     int i;  
  6.     for (i = 7;i >= 0;i--)  
  7.         if (n & (1 << i))  
  8.         break;  
  9.   
  10.     if (i < 0)  
  11.     return 0;  
  12.   
  13.     return i;  
  14. }  
  15.   
  16. int _tmain(int argc, _TCHAR* argv[])  
  17. {  
  18.     int n;  
  19.     int tab[256]={0};  
  20.     for (n=0;n<=0xff;n++)  
  21.     {  
  22.         tab[n] = bit_map(n);  
  23.     }  
  24.   
  25.     for(n=0;n<=0xff;n++)  
  26.     {  
  27.         if(n%0x10==0)  
  28.             printf("\n");  
  29.         printf("%3d," , tab[n]);  
  30.     }  
  31.     printf("\n");     
  32.     return 0;  
  33. }  

在vTaskStartScheduler里将ildetask的对应的bitmap置位
  1. OSRdyGrp |= 1;  
  2. OSRdyTbl[0] |= 1;  

在任务转为就绪态时要将对应的bitmap置位
  1. #define prvAddTaskToReadyQueue( pxTCB )                                                                                 \  
  2.     traceMOVED_TASK_TO_READY_STATE( pxTCB ) \  
  3.     if( ( pxTCB )->uxPriority > uxTopReadyPriority )    \  
  4.         uxTopReadyPriority = ( pxTCB )->uxPriority;      \  
  5.        OSRdyGrp |= OSMapTbl[(( pxTCB )->uxPriority >> 3)];\  
  6.        OSRdyTbl[(( pxTCB )->uxPriority >> 3)] |= OSMapTbl[(( pxTCB )->uxPriority & 7)];   \  
  7.     vListInsertEnd( ( xList * ) &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xGenericListItem ) )  

在任务从就绪态移除时将对应bitmap清掉
  1. void prvRemoveFromReadyQueue( tskTCB *   pxTCB)  
  2. {  
  3.      OSRdyTbl[(( pxTCB )->uxPriority >> 3)] &= ~OSMapTbl[(( pxTCB )->uxPriority & 7)];  
  4.      if (!OSRdyTbl[(( pxTCB )->uxPriority >> 3)])  
  5.             OSRdyGrp &= ~OSMapTbl[(( pxTCB )->uxPriority >> 3)];  
  6.   
  7.     vListRemove( &( pxTCB->xGenericListItem ) );  
  8. }  

最后就是任务调度时的查表了,在vTaskSwitchContext里
  1. if (listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ))  
  2. {  
  3.     OSRdyTbl[(( pxCurrentTCB )->uxPriority >> 3)] &= ~OSMapTbl[(( pxCurrentTCB )->uxPriority & 7)];  
  4.     if (!OSRdyTbl[(( pxCurrentTCB )->uxPriority >> 3)])  
  5.         OSRdyGrp &= ~OSMapTbl[(( pxCurrentTCB )->uxPriority >> 3)];  
  6.   
  7.     if (pxCurrentTCB->uxPriority == uxTopReadyPriority)  
  8.     {  
  9.          if (OSRdyGrp & 0xff000000)  
  10.                row =  24;  
  11.          else if (OSRdyGrp & 0x00ff0000)  
  12.                row =  16;  
  13.          else if (OSRdyGrp & 0x0000ff00)  
  14.                row =  8;  
  15.   
  16.          row =  row + OSUnMapTbl[((OSRdyGrp >> row) & 0x000000ff)];  
  17.          column =  OSUnMapTbl[OSRdyTbl[row]];  
  18.          uxTopReadyPriority =  (row << 3) + column;  
  19.     }  
  20. }  




实验结果对比

实验方法如下,在STM32F103VET6平台上,创建2个任务,一个优先级为255,它阻塞在一个消息队列上,每收到消息就做一点简单的计算处理,并再次进入阻塞;另一个任务优先级为1,它不停的想消息队列发送消息。这个实验目的就是频繁的切换任务,每循环0xffff次,打印一条消息

  1. static int a,b,c;  
  2. void do_something()  //一个简单而无实际意义的函数,只为说明做了一点事情  
  3. {  
  4.     a = b;b = c; c = a;  
  5. }  
  6. void Task1Func(void* p)  
  7. {  
  8.     static int cnt = 0,round = 0;  
  9.     int val = 0xfe;  
  10.     while (1)  
  11.     {  
  12.     do_something();  
  13.     if (cnt++ % 0xffff == 0)  
  14.         USART_OUT(USART1,"Task 1 round %d\n",++round);  
  15.     xQueueSendToBack(xQueue,&val,0xfff);  
  16.     }  
  17. }  
  18.   
  19. void Task2Func(void* p)  
  20. {  
  21.     static int cnt = 0,round = 0;  
  22.     int rec;  
  23.     while (1)  
  24.     {  
  25.     xQueueReceive(xQueue,&rec,( portTickType )0xffff);  
  26.     do_something();  
  27.     if (cnt++ % 0xffff == 0)  
  28.         USART_OUT(USART1,"Task 2 round %d\n",++round);  
  29.     }  
  30. }  
  1. xQueue = xQueueCreate(1,sizeof(int));  
  2. xTaskCreate(Task1Func,( const signed char * )"Task1",64,NULL,1,NULL);  
  3. xTaskCreate(Task2Func,( const signed char * )"Task2",64,NULL,255,NULL);  


先看原始的FreeRTOS查找算法的效率如何,


每循环0xffff次,需要大约9.5秒


再看修改为查表算法的运行结果:


每循环0xffff次,需要大约2.45秒。很显然,要高效很多,虽然这是个设计的很极端的实验,但也能说明问题。


看到这里,你可能在有个疑问,FreeRTOS作为一个十分优秀的开源实时操作系统,为何会采用一个如此简单低效的调度查优办法,而不像其它的RTOS大多都是采用查表的方法,以提高效率。楼主认为,FreeRTOS的设计者这样做是增加灵活性,可移植性,因为在FreeRTOSConfig.h文件中,有一个宏configMAX_PRIORITIES,它定义了系统支持的优先级数量,这个宏理论上可以改为大于1的任意数字。但是如果要采用查表的方法,就很难兼容了,例如,某个项目需求特殊,就非要需要支持1000个任务,那该如果查表?


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多