参考任哲的《嵌入式实时操作系统μC_OS-II原理及应用》,对UCOS的任务调度做了下整理。

时间:2021-02-02 19:51:04
参考任哲的《 嵌入式 实时操作系统μC_OS-II原理及应用》,对 UCOS 的任务调度做了下整理。
UCOS有两种调度器,一种是任务级的调度器,一种是中断级的调度器。任务级的调度器由OSSHED()来实现。而中断级的调度由函数 OSINTEXT() 来实现。
调度器把任务切换的工作分为两个步骤:第一步是获得待运行任务的TCB指针。第二步是进行断点数据的切换。调度器实施任务切换前的主要工作就是要获得待运行任务的任务控制块指针和当前任务的任务控制块指针。因为被中止任务的任务控制块指针存放在全局变量OSTCBCUR中,所以调度器这部分的工作主要就是获得待运行任务的任务控制块指针。
任务切换的工作靠 OSCTXSW() 来完成。
任务切换就是中止正在运行的任务(当前任务),转而去运行另外一个任务的操作。当然,这个任务应该是就绪任务中优先级最高的那个任务。
ARM中R13相当于51中的SP,在系统发生异常中断时,中断服务 程序 把要保存的寄存器的值压入R13指向的堆栈。在退出中断服务时,再把堆栈中的数据弹出来。
ARM中的R14又称为链接寄存器(LR),子程序调用时,处理器硬件自动把调用处的地址存放在R14中,在子程序执行完后,使用一条指令把R14的地址恢复给PC,这样就可以返回子程序的调用处。
OSCTXSW()切换任务时依次做以下7项工作:
1、        把被中止任务的断点指针保存在任务堆栈中。
   任务被中止时,断点指针被硬件自动存放在R14中,所以先要把R14的值压入堆栈。
2、        把CPU通用寄存器的内容保存到任务堆栈中
   把R0-R12的值压入被中止任务的堆栈
3、        把被中止任务的任务堆栈指针当前值保存到该任务的任务控制块的OSTCBSTKPTR中
   OSTCBSTKPTR定义在任务控制块结构的开始处,和任务控制块地址相同。指向任务堆栈栈顶。
4、        获得待运行任务的控制块地址
5、        使CPU通过任务控制块获得待运行任务的任务堆栈指针
6、        把待运行任务堆栈中通用寄存器的内容恢复到CPU的通用寄存器中
   把堆栈中保存的R0-R12值弹回到ARM的R0-R12
7、        使CPU获得待运行任务的断点指针(该指针是待运行任务在上一次被调度器中止运行时保留在任务堆栈中的)
   把堆栈中保存的断点指针值传送给LR和PC

任务切换和51子程序调用、中断的区别
51子程序掉用时,硬件自动保存断点地址,返回时,通过RET把断点地址弹回到PC中
任务切换时,硬件自动保存断点地址,然后把断点地址压入任务堆栈,然后得到另一个任务的任务堆栈指针,把新任务的任务堆栈中的断点地址弹给PC,完成任务切换。

个人理解的任务切换调度器:

ARM中的R13就是SP,指向当前任务的堆栈。

当有一次滴答定时器中断的时候,在被中止任务切换前,R13指向被中止任务(你给它分配的运行任务控制块堆栈)的栈顶,压入断点指针和CPU通用寄存器后,R13指向被中止任务的栈顶,然后把R13的值送给被中止任务的OSTTCBSTKPTR。再把待运行任务控制块的OSTTCBSTKPTR送给R13,这时,R13指向待运行任务的任务堆栈栈顶。把堆栈中的通用寄存器值和断点指针弹回给CPU后,R13就指向了待运行任务的堆栈顶,完成了任务切换。

注意:这跟中断级的调度器是不一样的,中断级别的是自动的入栈。任务切换--OSCtxSw()与OSIntCtxSw()在不同处理器是不一样的,对于STM32来说是一样的,而对于X86来说不一样。

中断:

任务在运行过程中,应内部或者外部异步事件的请求终止当前任务而去处理异步时间所要求的任务的过程叫做中断,应中断请求而运行的程序叫做中断服务子程序,中断服务子程序的入口地址叫做中断向量。UC/OS-II系统响应中断的过程是:系统接收到中断请求时,如果这时CPU处于中断允许状态,即中断时开放的,系统就会终止正在运行的当前任务,而按照中断向量的指向转而去运行服务子程序。需要特别注意的是对于可剥夺形的UC/OS-II内核来说,中断子程序运行结束之后,系统将会根据情况进行一次任务调度去运行优先级别最高的就绪任务,并不一定接着运行被中断的任务。同时中断是可以嵌套的,即高优先级别的中断源的中断请求可以中断低优先级别的中断服务程序的运行。为了记录中断嵌套的层数,UC/OS-II定义了一个全局变量OSIntNesting。

OSIntEnter()就是将全局变量OSIntNesting加1。OSIntNesting是中断嵌套层数的变量。μC/OS-II通过它确保在中断嵌套的时候,不进行任务调度。执行完用户的代码后,μC/OS-II调用OSIntExit(),一个与OSSched()很像的函数。在这个函数中,系统首先把OSIntNesting减1,然后判断是否中断嵌套。如果不是的话,并且当前任务不是最高优先级的任务,那么找到优先级最高的任务,执行OSIntCtxSw()这一出中断任务切换函数。因为,在这之前已经做好了压栈工作;在这个函数中,要进行R15~R4的出栈工作。而且,由于在之前调用函数的时候,可能已经有一些寄存器被压入了堆栈。所以要进行堆栈指针的调整,使得能够从正确的位置出栈。

区别:

任务切换函数:根据不同的CPU的情况这个函数一般都是用汇编语言写,一般思想都是:1、将CPU的通用寄存器压入堆栈(每一个任务都有自己的堆 栈);2、将堆栈指针SP赋值给任务控制块中的OSTCBStkPtr中;3、获得最高优先级的任务块;4、将最高任务快中的OSTCBStkPtr赋值 给堆栈指针SP;5、将新的任务块中的值出栈给CPU通用寄存器;6、中断返回IRET(RETI),使PC指向待运行任务。
    普通任务切换函数OSCtxSw()示意性代码:
    OSCtxSw(void)
    {
        压入堆栈;
        OSTCBCur->OSTCBStkPtr=SP;
        OSTCBCur=OSTCBHighRdy;
        OSPrioCur=OSPrioHighRdy;
        SP=OSTCBHighRdy->OSTCBStkPtr;
        出栈;
        IRET;

    }

中断级任务切换函数OSIntCtxSw()示意代码:
    OSIntCtxSw(void)
    {
         OSTCBCur=OSTCBHighRdy;
        OSPrioCur=OSPrioHighRdy;
        SP=OSTCBHighRdy->OSTCBStkPtr;
        出栈;
        RETI;
    }
    OSIntCtxSw(void)在调用之前,在进入中断的时候已经将任务的通用寄存器压入堆栈里面了,所以任务切换函数后面是相同的;
    IRET、RETI指令都是将在将堆栈中保存的地址取出,送给PC;唯一的区别就是RETI指令除了中断返回的作用之外还有将“优先级生效”触发器清零的功能;
    解释一下优先级生效的概念:根 据8051的结构特点,其中断系统中含有两个不可寻址的“优先级生效”触发器。一个用于指出CPU是否正在执行高优先级的中断服务程序,这个触发器为1 时,系统将屏蔽所有的中断请求;另一个则指出CPU是否正在执行低优先级中断服务程序,该触发器为1时,将阻止除高优先级以外的一切中断请求。