关于ARM架构下ucos2任务切换函数OSCtxSw源码分析

来源:互联网 发布:泰牛程序员怎么样 编辑:程序博客网 时间:2024/06/01 10:00

关于ARM架构下ucos2任务切换函数OSCtxSw源码分析

看了很多博文和权威资料,终于搞清楚了ARM的任务切换机制,下面我就引用一些资料来解释 OSCtxSw  函数究竟是如何保护现场和完成任务切换的。

 

http://www.keil.com/dd/docs/datashts/arm/cortex_m3/r1p1/ddi0337e_cortex_m3_r1p1_trm.pdf

 

首先,我们看下《Cortex-M3权威指南.pdf》中第38页有关通用寄存器组和特殊功能寄存器组的内容,存储堆栈指针SP的寄存器R13有两个,但是同一时间只能有一个被看到,这也就是所谓的“Banked”寄存器:

 

 

以下切换代码取自上面网址提供的ARM手册5-26 (取材于keil u4的帮助资料

;Example Context Switch (Assumes Thread is already on PSP)

 MRS r12, PSP ; Recover PSP into R12 ①

 STMDB r12!, {r4-r11, LR} ; Push non-stack registers ②

 LDR r0, =OldPSPValue ; Get pointer to old Thread Control Block ③

 STR r12, [r0] ; Store SP into Thread Control Block ④

LDR r0, =NewPSPValue ; Get pointer to new Thread Control Block ⑤

 LDR r12, [r0] ; Acquire new Process SP ⑥

LDMIA r12!, {r4-r11, LR} ; Restore non-stacked registers ⑦

 MSR PSP, r12 ; Set PSP to R12 ⑧

 BX lr ; Return back to Thread ⑨

 

①显然第一行汇编码,我们是将SP堆栈地址保存到R12寄存器中,即sp地址->R12.

②这里因为R12中已经是sp栈地址,因此我们是将R4-R11, LR,依次按递减4方式压入栈中,

假如此时sp栈地址为4000H,则栈空间如下所示,R12后的“!”号表示存储完成后同时更新R12寄存器,因此,经过递减后,当前R12值为3964H.

 

 

栈空间

 

R12

 

4000H

 

 

LR

3996H

 

 

R11

3992H

 

 

R10

3988H

 

 

R9

3984H

 

 

R8

3980H

 

 

R7

3976H

 

 

R6

3972H

 

 

R5

3968H

R12’

R4

3964H

③然后我们将要保存老的堆栈地址OldPSPValue的寄存器的地址,加载到R0中。

④将此时R12中老的堆栈地址OldPSPValue保存到R0所指的该寄存器中,到此,我们就已经保护现场完毕,接下来我们需要切换新的现场。

⑤继续将保存有新的堆栈地址NewPSPValue 的寄存器地址,加载到R0中。

⑥将新的堆栈地址加载到R12中。

⑦弹栈操作,将原来某个现场的信息弹出到R4-R11, LR中,方式与之前压栈刚好相反,不再赘述。

⑧将此时R12中的新堆栈地址返回给PSP

⑨跳转并返回继续执行。

以上写得比较啰嗦,不知道大家是否明白了,如果有不清楚汇编指令的,请参见我的附件【ARM汇编指令手册.pdf】和《Cortex-M3权威指南.pdf》赚点小分,还请见谅,呵呵。

 

堆栈的初始化函数需要根据不同的处理器进行设置,关于堆栈函数的处理首先要明白几点:

1.在《Cortex-M3权威指南.pdf》的第35页有如下内容:

 

接下来我再分析下,为什么ucos-ii中的OSCtxSw代码,似乎没有以上冗长的保护现场的代码,而是一段很简短的代码,以下是STM32平台下,ucos-ii中的源码:

OSCtxSw

PUSH    {R4, R5}

        LDR     R4, =NVIC_INT_CTRL   ;触发PendSV异常 (causes context switch)

        LDR     R5, =NVIC_PENDSVSET

        STR     R5, [R4]

POP     {R4, R5}

        BX      LR

 

OSIntCtxSw

PUSH    {R4, R5}

        LDR     R4, =NVIC_INT_CTRL   ;触发PendSV异常 (causes context switch)

        LDR     R5, =NVIC_PENDSVSET

        STR     R5, [R4]

POP     {R4, R5}

        BX      LR

        NOP

其中NVIC_INT_CTRL的值即0xE000ED04(中断控制及状态器ICSR地址,见权威指南P131),而NVIC_PENDSVSET的值为0x10000000即第28PENDSVSET1,悬起PendSV中断,因此这段代码的功能其实就是在不破坏R4R5内容的前提下,触发PendSV中断,其实就是利用中断来实现保护现场的功能,那么,为什么uCOS保护现场要兜这么大一个圈子呢,后面我会引入权威指南中的相关内容,解析这样做的好处,下面我们先来看看在PendSV中断中我们做了什么,以下仍然是STM32平台下,ucos-ii中的源码:

PendSV_Handler

    CPSID   I                       ; 首先我们在任务切换过程中关闭中断

MRS     R0, PSP                 ;保存线程栈PSP内容到R0

CBZ     R0, PendSV_Handler_Nosave ; 如果之前没有任务执行那么,我们跳过

;现场保护这个过程

    SUBS    R0, R0, #0x20            ; 这里有人可能有疑问了,为什么要减32,这是 ;搞什么?其实之前我也提过了进入异常服务例程    ;时,自动压栈了R0-R3,R12,LR,PSR,PC刚好8个寄    ;存器,共占8*4个字节的地址空间。我们将要保存    ;R4-R11就接在这后面,所以需要偏移。

    STM     R0, {R4-R11}

 

LDR     R1, =OSTCBCur     ; 你会发现OSTCBCur是从C代码中IMPORT引入的,

;这里就是那个指针变量的地址

    LDR     R1, [R1] ;取得指针OSTCBCur所指的OSTCB结构首地址

    STR     R0, [R1]             ; OSTCB结构的第一个成员就是OSTCBStkPtr , 将 ;保存有我们的R4-R11内容的栈地址保存到 ;OSTCBStkPtr 中

PendSV_Handler_Nosave

PUSH    {R14}              ; 这段我们只是为了调用一下OSTaskSwHook函数

;这个函数是开放给我们钩取线程切换过程的

    LDR     R0, =OSTaskSwHook   ; OSTaskSwHook();

    BLX     R0

    POP     {R14}

 

    LDR     R0, =OSPrioCur       ;这里我们开始切换到新的任务的内容,包含优先 ;级和OSTCB结构的切换

    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

    LDM     R0, {R4-R11}    ;将R4-R11从栈弹出

    ADDS    R0, R0, #0x20 ;加回我们偏移的地址,这样在执行BX跳转指令 ;时,硬件能从正确的栈地址自动弹出 ;R0-R3,R12,LR,PSR,PC的内容

    MSR     PSP, R0           ; 将R0中的栈地址送到PSP线程栈

    ORR     LR, LR, #0x04           ; 这句是确保后面执行BX命令时使用的是线程栈 ;PSP,而不是主堆栈MSP

    CPSIE   I ;开总中断

    BX      LR                    ; 执行中断返回,此时R0-R3,R12,LR,PSR,PC自动 ;出栈

 

这里我要补充说一下关于“ORR     LR, LR, #0x04 ”的内容,异常返回(BX跳转)时,硬件会根据LR的内容,POP相应的堆栈,这句保证了从PSP堆栈POPR0等寄存器,以下引用一篇博文中的内容来说明原因http://blog.chinaunix.net/uid-26817832-id-3162243.html

产生异常时,两个值我们需要,一个是pc,一个是LR,通过LR找到栈

1、 如果LR=0xFFFFFFF9说明产生异常的时候使用的是MSP

2、 如果LR=0xFFFFFFFD明产生异常的时候使用的是PSP

由此我们看出变化的恰是0x04,其实我们并不关心此时LR的值,我们只是做个标记,保证返回时,能从PSP出栈,出栈后LR会自动获取到正确的值,并覆盖我们这个LR值。

好了,说完,以上的内容,现在我将引用《Cortex-M3权威指南.pdf》中P123页给出的为什么这么做的原因。

 

 

 

个人感觉权威指南这几张图说得十分透彻和直观了,我就不啰嗦了,此外当然还有一个原因,就是间接保存PC寄存器的内容了,因为部分ARM不支持直接访问PC寄存器的内容。

希望以上我说了这么多废话,引用这么多资料,有把我的理解解释清楚,因为这些也只是我学习的一些笔记,如果有理解不对的地方,还请大虾门指出,谢谢了。