• 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务调度策略(三)


    if (ticks > 0u) {                            /* 0 means no delay!                                  */
            OS_ENTER_CRITICAL();
            y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */
            OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
            if (OSRdyTbl[y] == 0u) {
                OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
            }
            OSTCBCur->OSTCBDly = ticks;              /* Load ticks in TCB                                  */
            OS_EXIT_CRITICAL();
            OS_Sched();                              /* Find next task to run!                             */
        }

    依然是这一部分,接下来的重点是这个函数:OS_Sched()

    这个函数实在是太重要了,因此我不得不慎重。

    首先看一下官方的注释:

    *********************************************************************************************************
    * SCHEDULER
    *
    * Description: This function is called by other uC/OS-II services to determine whether a new, high
    * priority task has been made ready to run. This function is invoked by TASK level code
    * and is not used to reschedule tasks from ISRs (see OSIntExit() for ISR rescheduling).
    *
    *********************************************************************************************************

    从上面的说明可以看出,这个函数的作用,主要是用来调度当前已经进入了就绪状态的最高优先级任务,然后切换进去。

    函数定义如下:

     1 void  OS_Sched (void)
     2 {
     3 #if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */
     4     OS_CPU_SR  cpu_sr = 0u;
     5 #endif
     6 
     9     OS_ENTER_CRITICAL();
    10     if (OSIntNesting == 0u) {                          /* Schedule only if all ISRs done and ...       */
    11         if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */
    12             OS_SchedNew();
    13             OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
    14             if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
    15 #if OS_TASK_PROFILE_EN > 0u
    16                 OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
    17 #endif
    18                 OSCtxSwCtr++;                          /* Increment context switch counter             */
    19                 OS_TASK_SW();                          /* Perform a context switch                     */
    20             }
    21         }
    22     }
    23     OS_EXIT_CRITICAL();
    24 }

       关于任务调度的部分肯定是原子操作,不允许任何中断存在,因此必须要关闭中断。

       其次,任务调度不能发生在中断中以及任务调度器上锁的情况,因此必须加以判定。

      上面的内容比较简单,去掉那些判断和宏开关,我们需要关注的重点主要在以下的部分:

    1             OS_SchedNew();
    2             OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
    3             if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
    5                 OS_TASK_SW();                          /* Perform a context switch                     */
    6             }

      首先看这个函数:OS_SchedNew()

      函数定义如下:

     1 static  void  OS_SchedNew (void)
     2 {
     3 #if OS_LOWEST_PRIO <= 63u                        /* See if we support up to 64 tasks                   */
     4     INT8U   y;
     5 
     6 
     7     y             = OSUnMapTbl[OSRdyGrp];
     8     OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
     9 #else                                            /* We support up to 256 tasks                         */
    10     INT8U     y;
    11     OS_PRIO  *ptbl;
    12 
    13 
    14     if ((OSRdyGrp & 0xFFu) != 0u) {
    15         y = OSUnMapTbl[OSRdyGrp & 0xFFu];
    16     } else {
    17         y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
    18     }
    19     ptbl = &OSRdyTbl[y];
    20     if ((*ptbl & 0xFFu) != 0u) {
    21         OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
    22     } else {
    23         OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
    24     }
    25 #endif
    26 }

    因为我们的系统最高支持64个任务,所以去掉那些我们不需要关注的地方,函数定义简化如下:

    static  void  OS_SchedNew (void)
    {
        INT8U   y;
    
        y  = OSUnMapTbl[OSRdyGrp];
        OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
    }

    看着好像很简单,整个函数就两句话,但是,只要能把这两句话给弄明白了,关于调度的东西基本上就都没问题了。

    关于变量OSRdyGrp与数组OSRdyTbl[]的意义,相信各位都已经十分理解,分别代表组就绪状态和任务就绪状态,那么新出来的这个数组OSUnMapTbl[]又代表什么呢?它和任务就绪表有什么关系?

    跟踪OSUnMapTbl数组的定义可以发现,这是一个常数数组,它里面的内容是只读的,定义如下:

    INT8U  const  OSUnMapTbl[256] = {
        0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F                   */
        5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F                   */
        6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F                   */
        5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F                   */
        7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F                   */
        5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF                   */
        6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF                   */
        5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF                   */
        4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  /* 0xF0 to 0xFF                   */
    };

    这个表还不小,一眼看去脑袋都大了,它到底是个什么玩意儿?别慌,听我慢慢讲解……

    依然举个例子,现在我们的系统只有两个任务(0和12),当前的任务优先级是0,然后这个任务进入了延时,这个时候根据前面了解的东西:

            y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */
            OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;

    这个优先级为0的任务已经被设置为了未就绪状态,也就是把它的就绪表清空了,对应的OSRdyTbl[0]肯定是0,由于只有两个任务,因此对应的OSRdyGrp的最后一个bit位,也肯定是0,。

    然后我们还有一个优先级为12的任务已经准备就绪, 那么代码执行到了这里,任务的OSRdyTbl[1]必然等于0x10,组号OSRdyGrp必然等于0x02,把2带进这个常数表,得到的结果是:1

    这个“1”有什么意义?

        y             = OSUnMapTbl[OSRdyGrp];
        OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

    结论:这两句代码真正的功能,就是从系统中,把当前已经就绪了的任务里,优先级最高的那个任务给找出来,而这个任务也就是我接下来要切换进去的那一个

    假设现在我们系统中只有两个任务,一个优先级为0(未就绪),一个优先级为12(就绪),现在我们就来看看,他到底是怎么把12这个数据给找出来的。

    上面说了,当一个任务的优先级是确定数的时候,他的组号、组内坐席号,偏移量等都是确定的。

    当只有任务12就绪时,这个时候组号OSRdyGrp必然等于0x02,那么把它带入那个常数表中,得到结果y为1。

    在把1带入数组OSRdyTbl[1]中,等到结果的结果是0x10(参考上一节),把0x10带入常数表,得到的结果OSUnMapTbl[OSRdyTbl[y]] == 4

    这个y等于1,把它向左移动3个bit,得到的结果是8(二进制00001000)……最后的结果8 + 4 = 12

    没想到它真的把我需要的优先级给算出来了,到底是怎么做到的?

    -----------------------------------------------------------------------------------------------------------------------

    其实,UCOSII使用的这种方法被叫查表法,根据一定的规律,直接计算出当前优先级最高的那个任务号。

    那个看似莫名其妙的常数表OSUnMapTbl,其实它所代表的意思,是0~255个数字中,1所在的最低位:

      比如1,二进制00000001,在它的最低位出现了1,那么带入常数表一查,发现OSUnMapTbl[1] = 0,也就是第0位出现了1。

      比如2,二进制00000010,在它的次低位出现了1,那么带入常数表一查,发现OSUnMapTbl[2] = 1,也就是第1位出现了1。

      比如3,二进制00000011,在它的最低位出现了1,那么带入常数表一查,发现OSUnMapTbl[3] = 0,也就是第0位出现了1。

      ……

      比如12,二进制00001100,在它的第2位出现了1,那么带入常数表一查,发现OSUnMapTbl[12] = 2,也就是第2位出现了1。

      比如63,二进制011111111,在它的最低位出现了1,那么带入常数表一查,发现OSUnMapTbl[3] = 0,也就是第0位出现了1。

    因为有了这个表,算法上才可能做到无论有多少个任务进入了就绪的状态,我都能轻轻松松地取出优先级最高的那一个,根据变量OSRdyGrp的状态,我可以找到那些组有就绪的任务,

    如果第0组和第3组内都有就绪的任务,那么OSRdyGrp肯定等于0x05,但是我根本不用关心第3组的状态,忽略即可,因为第0组明显优先程度更大,我只需要继续前往第0组内寻找便可。

    进入第0组内部后,假如优先级为1和优先级为5的任务都就绪了,变量OSRdyTbl[0] == 0x22(二进制00100010),我肯定要执行优先级为1的任务,那么我就直接把这个数据带进那个常数表中去查寻,看看最低位出现1的位置,也就是就绪的任务到底是哪一个,

    只要找到了它,别的任务就算是就绪状态,我也不用管了……查表法便是基于这个原理。

        y             = OSUnMapTbl[OSRdyGrp];
        OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

    再看一下这两句代码,第一句代码的意思是:找到就绪任务中优先级最高的组号,比如1组和3组都就绪了,我需要的结果是:1。

    第二句代码中的这句话OSUnMapTbl[OSRdyTbl[y]]的意思是:找到在这个组中,优先级最高的任务的坐席号,也就是偏移量,比如任务12和任务13都就绪了,我需要的结果是:4(代表任务12的偏移)。

    整个第二个代码的意思是:把组号和组内坐席号组合起来,形成最后的任务优先级。

    如果想不明白的话,可以参考上一章:

           ptcb->OSTCBY             = (INT8U)(prio >> 3u);
           ptcb->OSTCBX             = (INT8U)(prio & 0x07u);

    这两句话的意思,在建立任务的时候,把一个好好的优先级给拆开,现在终于是把它们给重新合上了。

    现在回过头看,OS_SchedNew这个函数的作用是什么?

    很明显,它的作用就是寻找到,在当前已经就绪的任务中,优先级最高的那一个任务。

    题外话,思考一个问题,为什么要用查表法呢?

    如果是自己来做这个策略,有没有其他的方法?

    当然有,如果是我来做,或许可以建立一个大表,里面装有所有任务的就绪状态,然后写一个for循环,每次进行任务切换的时候,从低到高依次判断,如果任务状态位bit是1,那么就证明这个任务是就绪了的,立即跳出去进行任务切换,如果任务状态bit是0,那就证明这个任务没有就绪,继续进行下一个判断,我想这样肯定更容易理解一些。

    不过这样做有一个问题,如果当前我就绪的任务优先级是0,那么在第一个循环就能找到任务,然后任务切换,时间比查表法块很多,如果我就绪的任务是255呢?那么我可能就需要循环255次才能找到就绪的任务,那么时间肯定会很长。

    用这种方法会导致寻找就绪任务需要的时间完全不能确定,有时候短,有时候长,然而这对于一个系统而言,最怕的就是这种不确定因素。

    查表法和循环法就完全不同了,它虽然死板一些,但不管当前系统有多少任务,不管当前有多少任务是处于就绪状态,它每次计算出最高优先级的任务的时间是一定的,这种确定性对于系统很重要。

    待续……

  • 相关阅读:
    中文编码问题
    TCP网络参数优化
    I/O的整体介绍
    TCP/IP
    Java序列化技术
    WEB请求过程(http解析,浏览器缓存机制,域名解析,cdn分发)
    λ(lambda)表达式
    HeapByteBuffer和DirectByteBuffer以及回收DirectByteBuffer
    锁的优化和注意事项
    java的NIO和AIO
  • 原文地址:https://www.cnblogs.com/han-bing/p/8883818.html
Copyright © 2020-2023  润新知