• UCOSII内核代码分析


    1 UCOSII定义的关键数据结构

      OS_EXT  INT8U             OSIntNesting;

      OSIntNesting用于判断当前系统是否正处于中断处理例程中。

      OS_EXT  INT8U             OSPrioCur;

      OSPrioCur表示当前进程的优先级。

      OS_EXT  INT8U             OSPrioHighRdy;

          OSPrioHighRdy表示最高优先级任务的优先级。

      OS_EXT  OS_PRIO           OSRdyGrp;

          OSRdyGrp主要来标记可运行任务优先级除去低位3位或4位后的group bit位。例如任务的优先级为65(假设超过了系统最大的优先级超过了63,那么OS_PRIO应该有16位),由于超过了63,所以这个任务的group bit位为2^(65>>4)=2^4,也就是OSRdyGrp的第5位(从低位开始算起)为1

      OS_EXT  OS_PRIO           OSRdyTbl[OS_RDY_TBL_SIZE]; 

      准备运行的任务标记,对应位为1表示任务可以运行,否则不能。

      OS_EXT  BOOLEAN           OSRunning; 

      标志内核是否正在运行。

      OS_EXT  INT8U             OSTaskCtr; 

      任务被创建的个数。

      OS_EXT  OS_TCB           *OSTCBCur;

      指向当前正在运行任务的TCB(任务控制块)。

      OS_EXT  OS_TCB           *OSTCBFreeList;

      空闲TCB块指针。

      OS_EXT  OS_TCB           *OSTCBHighRdy;

      指向最高优先级任务的TCB块。

      OS_EXT  OS_TCB           *OSTCBList;

      正在使用的TCB块的双向链表。

      OS_EXT  OS_TCB           *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u];

      每个优先级对应的已创建的TCB块。

      OS_EXT  OS_TCB            OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];

      系统静态分配的一个系统中所有的TCB块,包括正在使用的和空闲的。

    typedef struct os_tcb {
        OS_STK          *OSTCBStkPtr;        //指向任务的栈顶
        struct os_tcb   *OSTCBNext;             //指向TCB链表中的下一个TCB块
        struct os_tcb   *OSTCBPrev;             //指向TCB链表中的前一个TCB块
        INT32U           OSTCBDly;              //任务延迟时间
        INT8U            OSTCBStat;             //任务当前状态
        INT8U            OSTCBStatPend;         //任务悬挂状态
        INT8U            OSTCBPrio;             //任务优先级(0表示最高优先级)
        INT8U            OSTCBX;                //与任务优先级一致group bit位(一般是OSTCBPrio的低3位)
        INT8U            OSTCBY;                //与任务优先级一致的ready table(一般是OSTCBPrio高5位)
        OS_PRIO          OSTCBBitX;             //访问ready table的bit mask
        OS_PRIO          OSTCBBitY;             //访问ready group的bit mask
    } OS_TCB

      TCB块是任务的描述符,描述了任务的状态和运行时必须的信息。

    2 系统初始化

          在OSInit函数中初始化系统的各个数据结构,具体如下:

          1、调用函数OS_InitMisc初始化OSIntNestingOSTaskCtrOSRunning等一些变量。

      2、调用函数OS_InitRdyList初始化准备运行的OSRdyTbl表位都为空。

      3、调用函数OS_InitTCBListOSTCBPrioTbl表的数据都初始化为0,第i个控制块指向第i+1个控制块,并且都加入到OSTCBFreeList指向的链表中,把OSTCBList初始化为空链表。

      4、调用函数OS_InitTaskIdle来创建一个Idle任务,该任务是优先级最低的任务,任务号为65535,优先级为63

    3 创建启动任务

      然后我们自己调用OSTaskCreate函数创建我们自己的任务,具体如下:

      1、先判断系统当前是否处于中断,如果是,则返回,因为当系统处于中断时是不允许创建任务的。

      2、判断需要创建任务的优先级是否存在其它相同优先级的任务,如果存在,则返回,即相同优先级的任务只能有一个。

      3、调用函数OSTaskStkInit初始化任务的栈。

      4、调用函数OS_TCBInit初始化任务的TCB块。这个函数主要是从OSTCBFreeList链表获取空闲的TCB块并初始化这个控制块,然后将这个控制块从OSTCBFreeList链表中删除,并插入到OSTCBList链表中。

      如果现在内核正在运行,那么调用函数OS_Sched切换任务。

      OS_Sched函数主要用来切换任务,如果当前系统不处于中断例程中,就通过调用函数OS_SchedNew来获取当前已经准备好的最高优先级的任务(最高优先级主要通过OSRdyGrpOSRdyTbl来计算得到),然后调用函数OS_TASK_SW来将任务切换到当前优先级最高的任务。

      OS_TASK_SW函数则调用os_cpu_a.asm文件中的汇编函数OSCtxSw来触发PendSV异常来完成任务的切换,至于为什么要这样做可以参考前面讲的重点的PendSV

      PendSV异常的处理的伪代码如下:

    PendSVHandler:
        if (PSP != NULL) {//判断程序的堆栈指针是否为空,为空说明这是第一个任务
            //当调用PendSVHandler()时,
            //CPU 就会自动保存xPSR、PC、LR、R12、R0-R3 寄存器到堆栈
            //保存后,CUP 的栈SP指针会切换到使用主堆栈指针MSP上
            //我们只需检测进入栈指针PSP是否为NULL就知道是否进行任务切换
            //因此当我们第一次启动任务是,OSStartHighRdy()就把PSP设为NULL,
            //避免系统以为已经进行任务切换
          Save R4-R11 onto task stack;   //手动保存 R4-R11
          OSTCBCur->OSTCBStkPtr = SP;    //保存进入栈指针PSP到任务控制块
          //以便下次继续任务运行时继续使用原来的栈
        }     
        OSTaskSwHook();                  //此处便于我们使用钩子函数来拓展功能
        OSPrioCur   = OSPrioHighRdy;        //获取最高优先级就绪任务的优先级
      OSTCBCur  = OSTCBHighRdy;        //获取最高优先级就绪任务的任务控制块指针
      PSP        = OSTCBHighRdy->OSTCBStkPtr;   //保存进入栈指针
      Restore R4-R11 from new task stack;       //从新的栈恢复 R4-R11 寄存器
        Return from exception;                   //返回  

      PendSV异常处理代码很简单,就是保存现场,将当前程序的堆栈指针保存到TCB块中,并将下一个要运行任务的栈指针更新到PSP中,同时恢复下一个运行任务的现场。

    4 运行系统

      一切都已经准备就绪,那么我们就调用函数OSStart运行系统。该函数和任务切换函数OS_TASK_SW差不多,只不过它会调用函数OSStartHighRdycpuPSP初始化为0来运行我们系统中的第一个任务并将OSRunning标记为true来表示我们的内核开始运行了。

    5 任务切换时机

      从OS_Sched函数和OSStartHighRdy函数可以看出,它们总是选择当前可运行的最高优先级任务来运行,所以只有最高优先级的任务不让出cpu,低优先级的任务永远也不可能运行,所以发生任务切换的唯一时机就是高优先级的进行主动让出cpu。高优先级的任务可以调用函数OSTimeDlyHMSM来让自己延迟从而让出cpuOSTimeDlyHMSM本质上是调用函数OSTimeDly来实现让出cpu的操作。下面来看看这个函数的具体实现:

    void  OSTimeDly (INT32U ticks)
    {
        if (OSIntNesting > 0u) {                     //系统正处于中断中
            return;
        }
        if (OSLockNesting > 0u) {                    //调度器加锁了
            return;
        }
        if (ticks > 0u) {                            //延迟时间大于0
            OS_ENTER_CRITICAL();
            y            =  OSTCBCur->OSTCBY;        //获取当前优先级的高位
            OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;  //清空表中对应当前任务的bit位
            if (OSRdyTbl[y] == 0u) {
                OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
            }
            OSTCBCur->OSTCBDly = ticks;              //设置当前任务的延迟时间
            OS_EXIT_CRITICAL();
            OS_Sched();                              //切换任务
        }
    }

      这个函数其实就是将本任务在OSRdyTbl表和OSRdyGrp对应的标记位清0,使得后面的调度函数OS_Sched可以选择其它优先级的任务,即当前任务让出cpu。这个函数还做的事就是设置当前任务的延迟时间到任务的TCB块中,这个时间会在SysTick处理函数中每个节拍的减少,直到减为0时,会将这个任务对应的bit位加入到OSRdyTbl表和OSRdyGrp中来获取cpu

    6 任务调度算法

      UCOSII涉及的调度算法比较的简单,正如在任务切换时机里说的,只要高优先级的任务不让出cpu,低优先级的任务永远不可能得到cpu,调度器总是选择系统中已经就绪的最高优先级任务运行。问题就来了,调度器怎样确定到当前系统中任务的最高优先级是多少呢?当然最笨的方法是将OSTCBList链表都遍历一遍,这样肯定是可以的。但这样效率是很低的,特别是系统中就绪任务比较多的时候,它的时间复杂度位O(n)n为系统中就绪任务的个数。UCOSII采用了一种比较巧妙的方法实现在O(1)的复杂度获得这个最高优先级值。

      当一个任务就绪时,这个任务的优先级OSTCBPrio被拆分成两个部分,低4位的值存放在任务控制块的OSTCBX中,高4位存放在OSTCBY中。然后将2^OSTCBX的值存放在控制块的OSTCBBitX中,将2^OSTCBY存放在OSTCBBitY中。通过下面方法更新OSRdyTbl表和OSRdyGrp变量。

      OSRdyGrp |= ptcb->OSTCBBitY;

      OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

      OSRdyGrp存放了当前系统中所有就绪任务优先级高4位表示的bit位,OSRdyTbl表其实就是一个位图,用来标记系统中所有就绪任务的优先级,每一个优先级对应一个bit位。

    有了这些信息,怎样来获取当前就绪任务中最高优先级的值呢?系统维护了一张表:

    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*/

    };

      通过函数OS_SchedNew来获取最高的优先级值:

    static  void  OS_SchedNew (void)
    {
        INT8U     y;
        OS_PRIO  *ptbl;
        if ((OSRdyGrp & 0xFFu) != 0u) {
            y = OSUnMapTbl[OSRdyGrp & 0xFFu];
        } else {
            y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
        }
        ptbl = &OSRdyTbl[y];
        if ((*ptbl & 0xFFu) != 0u) {
            OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
        } else {
            OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
        }
    }

      这个函数先根据OSRdyGrp值这张表来获得最高优先级的高4位的值y,然后通过OSRdyTbl[y]来获得低4位的值x,这样最终的值就是y<<4 + x。为什么这样就可以得到最高优先级呢?现在来看一个例子:假设OSRdyGrp = 10,对应的二进制值为1010,那么就绪任务中优先级前4位最小的值为1,由于OSRdyGrp的二进制值第二位为1(这个可以通过前面计算OSRdyGrp的方法来理解)。然后通过这个1,我们可以知道最高优先级在OSRdyTbl对应的bit位在OSRdyTbl[1]中,然后通过OSRdyTbl[1]来查表OSUnMapTbl同样可以获得最高优先级的低4位的值。然后合并这两个值就可以获得最高优先级的值了。

  • 相关阅读:
    redis常见面试题
    nginx常见的面试题
    python学习笔记(15)pymysql数据库操作
    python中的if not
    python学习笔记(24)-类与对象
    python学习笔记(23)-异常处理
    python学习笔记(22)-os文件操作模块
    Maven---pom.xml配置详解
    maven+jmeter+jenkins集成
    适配器模式
  • 原文地址:https://www.cnblogs.com/chengxuyuancc/p/3455890.html
Copyright © 2020-2023  润新知