uc/os-ii任务调度(一)

来源:互联网 发布:罗杰疑案知乎 编辑:程序博客网 时间:2024/05/20 20:19

多任务操作系统的核心工作就是任务调度。所谓调度,就是通过一个算法在多个任务中确定该运行的任务,做这项工作的函数就叫做调度器。μC/OS-II进行任务调度的思想是 “近似地每时每刻总是让优先级最高的就绪任务处于运行状态” 。为了保证这一点,μC/OS-II在系统或用户任务调用系统函数及执行中断服务程序结束时总是调用调度器,来确定应该运行的任务并运行它 。
μC/OS-Ⅱ的调度器主要有两个功能:一是确定进入就绪态的任务中哪个优先级最高;二是进行任务切换。调度有两种方式:任务级的调度是由OSSched()函数完成的;中断级的调度是由OSIntExt()函数完成的。

  • 下面先分析任务级的调度函数OSSched()(基于ARM Cortex-M3)。
void  OS_Sched (void){#if OS_CRITICAL_METHOD == 3                            /* Allocate storage for CPU status register     */    OS_CPU_SR  cpu_sr = 0;#endif    OS_ENTER_CRITICAL();    if (OSIntNesting == 0) {                           /* Schedule only if all ISRs done and ...       */        if (OSLockNesting == 0) {                      /* ... scheduler is not locked                  */            OS_SchedNew();            if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];#if OS_TASK_PROFILE_EN > 0                OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */#endif                OSCtxSwCtr++;                          /* Increment context switch counter             */                OS_TASK_SW();                          /* Perform a context switch                     */            }        }    }    OS_EXIT_CRITICAL();}

(1) 采用第三种方法开关中断。OSSched()函数的所有代码都属临界区代码。为了防止在处理的过程中有中断打入而将某些任务转入就绪状态,中断必须关闭。
(2) 若调度是禁止的(即调度上锁),或当前处于中断服务子程序中,任务的调度是不允许的,任务调度函数OSSched()将退出。调度锁定嵌套计数器OSLockNesting用于跟踪调度上锁次数,当OSLockNesting>0时,表明调度是禁止的;只有OSLockNesting=0时,调度才是允许的,也称为调度开锁。中断嵌套计数器OSIntNesting用于跟踪中断嵌套层数,只要一进入中断服务,就有OSIntNesting>0;只有所有中断都退出时,才有OSIntNesting = 0
(3) 若调度是开放的且程序不处于中断服务之中,则调用OS_SchedNew()函数找出准备就绪且优先级最高的任务。
(4) 若准备就绪的最高优先级任务不是当前正在运行的任务,就要进行任务切换。为实现任务切换,必须切换任务控制块,将OSTCBHighRdy指向优先级最高的那个任务控制块OS_TCB。并且计数器OSCtxSwCtr加1,以跟踪任务切换次数。
(5)任务切换是通过一个宏调用OS_TASK_SW()来完成的。任务切换很简单,实际上就是人为地模拟一次中断过程。它分两步完成,首先将被挂起的任务的CPU寄存器推入堆栈,然后将准备就绪的最高优先级任务的寄存器值从任务栈中恢复到寄存器中。在μC/OS-Ⅱ中,任务的栈结构就是模拟中断栈结构,所有CPU寄存器都保存在栈中,切换任务就像中断返回,不同的是:中断返回弹出的是被中断任务的CPU寄存器值,而任务切换弹出的是准备就绪的最高优先级任务的CPU寄存器值,这样准备就绪的高优先级任务就可以运行了。

以下这里重点分析一下OS_TASK_SW()函数在ARM Cortex-M3处理器上的实现,OS_TASK_SW()是汇编函数OSCtxSw()的宏定义

#define  OS_TASK_SW()         OSCtxSw()

如下是OSCtxSw()的汇编源码,该段代码的作用是触发一次PendSV异。任务切换实际上是在PendSV的异常服务程序中完成的。

OSCtxSw    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)    LDR     R1, =NVIC_PENDSVSET    STR     R1, [R0]    BX      LR

PendSV的异常服务程序汇编源码如下

OS_CPU_PendSVHandler    CPSID   I                                                   ; Prevent interruption during context switch    MRS     R0, PSP                                             ; PSP is process stack pointer    CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time    SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack    STM     R0, {R4-R11}    LDR     R1, =OSTCBCur                                       ; OSTCBCur->OSTCBStkPtr = SP;    LDR     R1, [R1]    STR     R0, [R1]                                            ; R0 is SP of process being switched out                                                                ; At this point, entire context of process has been savedOS_CPU_PendSVHandler_nosave    PUSH    {R14}                                               ; Save LR exc_return value    LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();    BLX     R0    POP     {R14}    LDR     R0, =OSPrioCur                                      ; OSPrioCur = OSPrioHighRdy;    LDR     R1, =OSPrioHighRdy    LDRB    R2, [R1]    STRB    R2, [R0]    LDR     R0, =OSTCBCur                                       ; OSTCBCur  = OSTCBHighRdy;    LDR     R1, =OSTCBHighRdy    LDR     R2, [R1]    STR     R2, [R0]    LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;    LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stack    ADDS    R0, R0, #0x20    MSR     PSP, R0                                             ; Load PSP with new process SP    ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack    CPSIE   I    BX      LR                                                  ; Exception return will restore remaining context    END

uC/OS-II采用软中断指令触发PendSV异常来实现任务的切换。
1. 获取当前任务的堆栈指针PSP。如果PSP=0,判断为第一次任务切换,跳至OS_CPU_PendSVHandler_nosave;如果PSP!=0,继续执行步骤2。如果任务第一次运行,则PSP=0这里写代码片;如果不是第一运行,则PSP=ptos-0x20。因为CM3在进入PendSV异常服务函数时会自动将xPSR、PC、LR、R12、R3--R0依次保存金PSP=ptos所指向的堆栈。上述寄存器入栈后PSP=ptos-#0x20
2. 将R11--R4压入当前任务堆栈。PSP=ptos-#0x40
3. 将PSP保存到当前任务控制块中OSTCBCur->OSTCBStkPtr = PSP
以下为OS_CPU_PendSVHandler_nosave
4. 调用钩子函数OSTaskSwHook();
5. 获得待运行任务的任务控制块OSTCBCur = OSTCBHighRdy;
6. PSP指向待运行任务中保存的堆栈地址PSP=STCBHighRdy->OSTCBStkPtr;执行后PSP=ptos-0x40;
7. 将R4--R11依次出栈。PSP=ptos-#0x20
8. 异常返回,返回时将R0--R3、R12、LR、PC、xPSR依次出栈。

======= 任务级任务调度就分析这么多吧!!!========
下面分析程序如何开始运行第一个任务,其中包括PendSV异常优先级设定的汇编代码,PendSV优先级设置为最低0xFF。
uC/OS-II一般在OSStart()前先创建一个启动任务,但是这个任务并没有运行,因为OSRunning == OS_FALSE。执行OSStart()触发第一次任务调度让系统有任务跑起来。

void  OSStart (void){    if (OSRunning == OS_FALSE) {        OS_SchedNew();                               /* Find highest priority's task priority number   */        OSPrioCur     = OSPrioHighRdy;        OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run    */        OSTCBCur      = OSTCBHighRdy;        OSStartHighRdy();                            /* Execute target specific code to start task     */    }}

执行OSStartHighRdy(); 相当于一次任务调度。
1. 设置PendSV异常优先级为最低0xFF。
2. 初始化PSP=0;,标志第一次执行任务调度。
3. 设置OSRunning = TRUE
4. 触发一次PendSV异常,执行PendSV异常服务程序OS_CPU_PendSVHandler

OSStartHighRdy    LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority    LDR     R1, =NVIC_PENDSV_PRI    STRB    R1, [R0]    MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call    MSR     PSP, R0    LDR     R0, =OSRunning                                      ; OSRunning = TRUE    MOVS    R1, #1    STRB    R1, [R0]    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)    LDR     R1, =NVIC_PENDSVSET    STR     R1, [R0]    CPSIE   I                                                   ; Enable interrupts at processor levelOSStartHang    B       OSStartHang                                         ; Should never get here
0 0
原创粉丝点击