μC/OS中高优先级任务一旦处于就绪态总能获得CPU控制权的理解


μCOS的任务切换时间:1.任务创建时 2.任务挂起时 3.任务恢复 4.任务延时时 6.任务释放信号量时 7.任务释放互斥信号量时 8.任务请求消息邮箱时 9.任务释放消息队列时 10 中断退出时(OSINTEXIT()函数中) 

任务之前的切换应该就是利用时钟中断来实现,当OS运行完一个时钟片后会产生一个中断(定时器的中断)异常,PC指针立即跳转到异常向量表执行处理异常的代码,随后会导致OS执行一次任务调度。整个过程分析如下:

产生异常后执行的第一条指令

b     HandlerIRQ

跳转到HandlerIRQ,而HandlerIRQ是一条宏命令,定义如下

HandlerIRQ    HANDLER HandleIRQ

宏体如下:

MACRO

$HandlerLabel HANDLER $HandleLabel

$HandlerLabel

       sub  sp,sp,#4

       stmfd      sp!,{r0}                //后面代码需要使用R0寄存器,所以先将其入栈。

ldr     r0,=$HandleLabel    //此处是将HandeIRQ的地址赋给寄存器R0Handler中存放是中断服务函数汇编部分的入口地址,此汇编用来计算中断服务函数的入口地址,所有IRQ中断通用。HandeIRQ的赋值操作在后面。

       ldr     r0,[r0]                    //此处读取R0中的内容,即中断服务函数汇编部分的入口地址

       str     r0,[sp,#4]       

       ldmfd   sp!,{r0,pc}            //出栈,栈中R0的值重新赋给R0,中断服务函数汇编入口地址复制给PC。相当于跳转到汇编入口地址处。

       MEND

HandeIRQ中存放汇编入口地址的代码如下。

ldr   r0,=HandleIRQ               //HandeIRQ地址

       ldr   r1, =OS_CPU_IRQ_ISR              //OS_CPU_IRQ_ISR地址

       str   r1,[r0]                                 //OS_CPU_IRQ_ISR地址赋值给HandeIRQ

 

       执行完宏体的结果应该是跳转到OS_CPU_IRQ_ISR

OS_CPU_IRQ_ISR      

 

       STMFD   SP!, {R1-R3}                   //后面会皆用这三个寄存器,所以先将其入栈

       MOV     R1, SP                              //IRQ模式下的当前SP指针保存到R1

       ADD     SP, SP, #12             //调整SP,使其指向R1~R3入栈前的地址

       SUB     R2, LR, #4              //LR保存的是返回值的地址,但是不同的异常产生的LR不同。IRQ异常时,LR保存的是下一条将被执行的指令+4。所以此处需要LR-4。来调整。

       MRS     R3, SPSR                          ; Copy SPSR (Task CPSR)  

 

       MSR     CPSR_cxsf, #SVCMODE|NOINT   //切换处理器运行状态,SP也会更改为特权模式的堆栈指针

 

       下面代码的作用是将被中断的任务的相关寄存器保存到特权模式的堆栈下。

 

       STMFD   SP!, {R2}                        //R2中存放的是被中断任务的下一条将被执行的指令,将其入栈。

       STMFD   SP!, {R4-R12, LR}           ; Push task''s LR,R12-R4     

       LDMFD   R1!, {R4-R6}                   //R1此时是IRQ堆栈的栈顶指针,和下一条指令一起将被中断任务的R1~R3寄存器保存到特权模式的堆栈中

       STMFD   SP!, {R4-R6}                   ; Push Task''s R3-R1 to SVC stack

       STMFD   SP!, {R0}                     ; Push Task''s R0 to SVC stack    

       STMFD   SP!, {R3}                        ; Push task''s CPSR

       此时的特权模式堆栈结构应该是:

LR

R12

R11

R10

R9

R8

R7

R6

R5

R4

R3

R2

R1

R0

CPSR

       LDR     R0,=OSIntNesting        ;OSIntNesting++

       LDRB    R1,[R0]

       ADD     R1,R1,#1

       STRB    R1,[R0]                            //μC/OSOSInrNesting变量+1

      

       CMP     R1,#1                   ;if(OSIntNesting==1){

       BNE     %F1

        

       LDR     R4,=OSTCBCur          // ;OSTCBHighRdy->OSTCBStkPtr=SP;

       LDR     R5,[R4]                             //将当前的TCB指针赋值R5     

       STR     SP,[R5]                 //将被特权模式的栈顶指针保存到北中断任务的OSTCBStkPtr

1

       MSR    CPSR_c,#IRQMODE|NOINT    ;Change to IRQ mode to use IRQ stack to handle interrupt

      

       LDR     R0, =INTOFFSET

    LDR     R0, [R0]

      

    LDR     R1, IRQIsrVect                          //IRQ已经通过指令

//IRQIsrVect DCD HandleEINT0被赋值为HandleEINT0的地址

    MOV     LR, PC                    //此时的PC应该指向MSR那条指令,LDR这条恰好是跳转指令,这样跳转之后刚好返回MSR

    LDR     PC, [R1, R0, LSL #2]         //此处计算C语言的中断服务函数入口地址,然后跳转执行。    

    MSR              CPSR_c,#SVCMODE|NOINT   //中断服务函数执行完后返回

    BL          OSIntExit           //跳转OSIntExit执行,先插入分析OSIntExit

void  OSIntExit (void)

{

#if OS_CRITICAL_METHOD == 3                             

    OS_CPU_SR  cpu_sr = 0;

#endif

    if (OSRunning == OS_TRUE) {

        OS_ENTER_CRITICAL();

        if (OSIntNesting > 0) {                     

            OSIntNesting--;

        }

        if (OSIntNesting == 0) {                          

            if (OSLockNesting == 0) {                    

                OS_SchedNew();                                      //计算处于最高优先级的任务

                if (OSPrioHighRdy != OSPrioCur) {        //假如就绪态的任务优先级高于当前运行的任务优先级着将其调度执行

                    OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

#if OS_TASK_PROFILE_EN > 0

                    OSTCBHighRdy->OSTCBCtxSwCtr++;       

#endif

                    OSCtxSwCtr++;                        

                    OSIntCtxSw();                           //任务切换                        

                }

            }

        }

        OS_EXIT_CRITICAL();

    }

}

   

    LDMFD   SP!,{R4}              //将当前堆栈保存的数据弹出一个到R4,即将被中断的任务的CPSR保存到R4

    MSR              SPSR_cxsf,R4

    LDMFD   SP!,{R0-R12,LR,PC}^     出栈,继续执行被中断的任务。

OSIntOS_Sched类似但是不能互换使用。OSIntExit含有中断嵌套数的计算。在OSIntExit做任务切换使用的是OSIntCtxSw,而OS_Sched中使用的是OS_TASK_SW。根据如上分析,每个时钟片刻结束时候OS都会根据优先级重新调度一遍,所以说在μC/OS,只要高优先级的任务处于就绪态,就会立即抢占CPU。以为在这一个时钟片结束后,OS会执行一次调度。

相关内容

    暂无相关文章