μCOS-II中断处理(基于ARM处理器)

(GitHub - arm7star/uCOS-II)

1、任务上下文

μCOS-II V2.83的任务上下文保存在任务的栈里面,任务创建时,将任务的参数、运行地址(任务入口)、任务栈等信息保存在任务栈里面,OS_TCB->OSTCBStkPtr指向任务栈顶(任务创建及挂起时,OS_TCB->OSTCBStkPtr即指向任务上下文)。

ARM栈是递减栈(栈底地址高、栈顶地址低,地址由低到高为:栈顶->栈数据->栈底)!

1.1、 任务创建时任务栈设置

以下为os_cpu_c.c任务创建时任务栈初始化函数。

OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
    OS_STK *stk;


    opt      = opt;                         /* 'opt' is not used, prevent warning                      */
    stk      = ptos;                        /* Load stack pointer                                      */
    *(stk)   = (OS_STK)task;                /* Entry Point                                             */
    *(--stk) = (INT32U)0x14141414L;         /* R14 (LR) (init value will cause fault if ever used)     */
    *(--stk) = (INT32U)0x12121212L;         /* R12                                                     */
    *(--stk) = (INT32U)0x11111111L;         /* R11                                                     */
    *(--stk) = (INT32U)0x10101010L;         /* R10                                                     */
    *(--stk) = (INT32U)0x09090909L;         /* R9                                                      */
    *(--stk) = (INT32U)0x08080808L;         /* R8                                                      */
    *(--stk) = (INT32U)0x07070707L;         /* R7                                                      */
    *(--stk) = (INT32U)0x06060606L;         /* R6                                                      */
    *(--stk) = (INT32U)0x05050505L;         /* R5                                                      */
    *(--stk) = (INT32U)0x04040404L;         /* R4                                                      */
    *(--stk) = (INT32U)0x03030303L;         /* R3                                                      */
    *(--stk) = (INT32U)0x02020202L;         /* R2                                                      */
    *(--stk) = (INT32U)0x01010101L;         /* R1                                                      */
    *(--stk) = (INT32U)p_arg;               /* R0 : argument                                           */
    *(--stk) = (INT32U)0x00000013L;         /* CPSR  (Enable both IRQ and FIQ interrupts)              */

    return (stk);
}

从第一个*(--stk)看,是先将栈地址减一在存储数据,因此该栈是个满栈(即栈指针指向的地址有数据,前置递减可以看出栈是个递减栈,栈的增长是从高地址往低地址增长);入栈顺序依次是{task, r14-r0, cpsr},task在内存高地址,cpsr在内存低地址。

栈中几个关键的寄存器:r14(lr),这里设置的是一个无意义的值,因此这种情况下程序结束时返回地址会异常(其他嵌入式实时系统会设置返回地址指向一个任务退出函数);r0任务运行参数指针;cpsr任务运行时的cpsr,这里设置任务运行在SVC模式,另外任务运行时默认必须允许中断,否则中断处理器没法处理任务调度定时器中断信号。

寄存器在栈中的顺序主要与上下文保存及恢复有关。

1.2、 第一个任务的运行

第一个任务是由main函数执行的,main函数没有运行在任务上下文,操作系统运行之后也不会再返回到被中断的main函数继续执行,因此,第一个任务执行时没有保存main函数上下文的必要。

        .text

OSStartHighRdy:

#if (OS_TASK_SW_HOOK >= 1)
        BL      OS_TaskSwHook                                   // OSTaskSwHook();
#endif

        MSR     CPSR_c, #(I_BIT | F_BIT | ARM_MODE_SVC)         // Switch to SVC mode with IRQ and FIQ disabled

        LDR     R4, =OSRunning                                  // OSRunning = TRUE;
        MOV     R5, #1
        STRB    R5, [R4]

        LDR     R0, =OSTCBCur                                   // SWITCH TO HIGHEST PRIORITY TASK:
        LDR     R0, [R0]                                        //    Get stack pointer,
        LDR     SP, [R0]                                        //    Switch to the new stack,

        LDMFD   SP!, {R0}                                       //    Pop new task's CPSR,
        MSR     SPSR_cxsf, R0
        LDMFD   SP!, {R0-R12, LR, PC}^                          //    Pop new task's context.

OSStartHighRdy函数主要步骤是:

1、禁止中断;

2、OSRunning = TRUE设置操作系统运行状态(只有操作系统运行时,调度定时器才有必要调用);

3、恢复任务栈;

获取任务栈顶

        LDR     R0, =OSTCBCur // 获取任务控制块地址(汇编语言 "=变量名称"用于获取该变量的地址,等价于c语言的 r0 = &OSTCBCur)
        LDR     R0, [R0] // 获取OSTCBStkPtr的地址(r0 = OSTCBCur)
        LDR     SP, [R0] // 获取OSTCBStkPtr的值(即任务栈顶)(sp = OSTCBStkPtr->OSTCBStkPtr

任务栈恢复

        LDMFD   SP!, {R0} // 出栈(栈顶元素pop到r0,栈顶此时是任务创建时的cpsr)
        MSR     SPSR_cxsf, R0 // 任务的cpsr保存到spsr
        LDMFD   SP!, {R0-R12, LR, PC}^ // 寄存器出栈(1、ldmfd带有pc及^标志,指令会将spsr备份程序状态寄存器的值恢复到cpsr程序状态寄存器;2、r0-r12, lr, pc恢复,此次主要时将任务入口的参数恢复并将程序寄存器pc设置为task入口函数指针;3、ldmfd之前的cpu已经切换到SVC模式,任务也是运行在SVC模式,因此sp寄存器并没有在ldmfd恢复寄存器列表里面,ldmfd sp!, ... 本身就是在操作任务的栈,任务切换前后的sp是同一个sp)

2、中断处理

中断处理主要保存任务/中断上下文,恢复任务/中断上下文。中断处理汇编代码如下:

/*
 * IRQ interrupt
 */
        .align  5
irq:
        stmfd   sp!, {r1-r3}                                    // We will use r1-r3 as temporary registers

        mov    r1, sp                                           // sp -> r1
        add    sp, sp, #12                                      // Adjust IRQ stack pointer
        sub    r2, lr, #4                                       // Adjust pc for return address to task
        mrs    r3, spsr                                         // Copy spsr (task's cpsr)

        /*
         * save task's context onto old task's stack
         */
        msr    cpsr_cxsf, #(I_BIT | F_BIT | ARM_MODE_SVC)       // Change to SVC mode

        stmfd  sp!, {r2}                                        // Push task's pc
        stmfd  sp!, {r4-r12, lr}                                // Push task's lr, r12-r4

        ldmfd  r1!, {r4-r6}                                     // Load task's r1-r3 from IRQ stack
        stmfd  sp!, {r4-r6}                                     // Push task's r1-r3 to SVC stack
        stmfd  sp!, {r0}                                        // Push task's r0 to SVC stack
        stmfd  sp!, {r3}                                        // Push task's cpsr

        ldr    r0, =OSIntNesting                                // OSIntNesting++
        ldrb   r1, [r0]
        add    r1, r1, #1
        strb   r1, [r0]

        cmp    r1, #1                                           // if (OSIntNesting == 1) {
        bne    1f
        ldr    r4, =OSTCBCur                                    //     OSTCBCur->OSTCBStkPtr = sp;
        ldr    r5, [r4]
        str    sp, [r5]                                         // }

1:
        msr    cpsr_c, #(I_BIT | F_BIT | ARM_MODE_IRQ)          // Change to IRQ mode to use IRQ stack to handle interrupt
        bl     do_IRQ
        msr    cpsr_c, #(I_BIT | F_BIT | ARM_MODE_SVC)          // Change to SVC mode
        bl     OSIntExit                                        // Call OSIntExit

        ldmfd  sp!, {r4}                                        // Pop the task's cpsr
        msr    spsr_cxsf, r4
        ldmfd  sp!, {r0-r12, lr, pc}^                           // Pop new task's context

2.1、中断上下文保存

        stmfd   sp!, {r1-r3} // 通用寄存器r1-r3保存到IRQ栈里面(r1-r3将作为临时寄存器使用,即寄存器将被改变,sp = sp - 4 * 3)

        mov    r1, sp // r1保存IRQ模式的栈顶(中断前任务/中断处理程序r1-r3保存的地址)
        add    sp, sp, #12 // 恢复IRQ的栈指针(sp = sp + 4 * 3),保存的寄存器仍在内存中,只是移动了IRQ的栈指针而已,后续代码不会操作IRQ的sp值,此处如果不恢复的话将会造成IRQ模式栈内存的泄露!
        sub    r2, lr, #4 // 中断返回地址保存到r2中(中断时,硬件会将中断前的pc寄存器的值减去4然后保存到IRQ的lr寄存器里面,中断前的pc指向的是正在取指的指令地址,pc之前有正在指向的指令、正在译码的指令,pc - 8才是正在执行指令的地址,正在执行的指令被中断了,因此需要重新执行)
        mrs    r3, spsr // 中断前的cpsr寄存器的值保存在spsr里面,中断前的cpsr保存到r3

目前已经保存的寄存器有,r1指向的内存(r1-r3),IRQ寄存器r2(pc)、r3(cpsr)。

2.2、任务/中断处理上下文保存到任务栈

前一小结中的中断前的部分寄存器保存到了IRQ的栈里面,再中断重入的情况下,会造成保存的上下文被覆盖,因此IRQ栈里面只是临时保存了部分上下文数据而已,在再次允许中断前,需要将IRQ栈里面的数据保存到其他地方(任务的栈)。

        msr    cpsr_cxsf, #(I_BIT | F_BIT | ARM_MODE_SVC)  // 切换到SVC模式并禁止中断

        stmfd  sp!, {r2} // r2(pc)入栈(如同任务创建时一样,栈底保存任务运行地址),此处已经是SVC模式了,因为任务也运行在SVC模式,因此,此处操作的是任务的栈! 栈的内容为: pc(低地址到高地址)
        stmfd  sp!, {r4-r12, lr} // {r4-r12, lr}入栈,栈的内容为:r4-r12,lr,pc(低地址到高地址)

        ldmfd  r1!, {r4-r6} // r1指向IRQ栈保存上下文的地址,IRQ保存的栈数据(r1-r3)恢复到寄存器r4-r6
        stmfd  sp!, {r4-r6} // r4-r6(r1-r3)入栈,栈的内容为:r1-r12,lr,pc(低地址到高地址)
        stmfd  sp!, {r0} // r0入栈(r0为通用寄存器,在这之前的中断处理一直没有改变r0的值),栈的内容为:r0-r12,lr,pc(低地址到高地址)
        stmfd  sp!, {r3} // cpsr入栈(r3为通用寄存器,此前已经保存了中断前的cpsr),栈的内容为:cpsr,r0-r12,lr,pc(低地址到高地址)

至此,中断前任务/中断处理函数的所有寄存器都保存到了任务栈里面了!

2.3、任务栈设置

中断可能导致任务被重新调度,因此需要将任务上下文的地址保存到任务控制块里面,任务重新执行的时候才可能找到上下文。

        ldr    r0, =OSIntNesting // 中断嵌套计数器OSIntNesting加1
        ldrb   r1, [r0]
        add    r1, r1, #1
        strb   r1, [r0]

        cmp    r1, #1 // 判断中断嵌套计数器OSIntNesting是否为1,如果为1表示当前是第一次进入中断,即中断前是在执行操作系统的任务,需要更新被中断任务的栈指针OSTCBStkPtr
        bne    1f
        ldr    r4, =OSTCBCur // r4 = &OSTCBCur
        ldr    r5, [r4] // r5 = OSTCBCur
        str    sp, [r5] // OSTCBCur->OSTCBStkPtr = sp

2.4、中断退出

(中断处理的c函数略过,中断处理c函数不会切换上下文,执行完成后会回到汇编代码!)

        bl     OSIntExit // 调用中断退出函数(操作系统调度定时器更新可能会唤醒某些等待超时的任务,当前任务优先级可能不再是最高优先级了)

// OSIntExit没有执行任务切换(中断嵌套/当前任务优先级最高),此处代码将继续执行,后续代码仅恢复被中断的任务/中断处理函数(do_IRQ)

        ldmfd  sp!, {r4} // r4 = cpsr
        msr    spsr_cxsf, r4 // spsr = cpsr
        ldmfd  sp!, {r0-r12, lr, pc}^ // 从SVC栈恢复寄存器

3、任务抢占

中断处理函数除了调度定时器计数导致任务时间片用完、等到超时任务定时器超时外,其他硬件中断处理也可能使某个睡眠任务被唤醒,因此当前任务可能被高优先级任务抢占。OSIntExit在中断退出时进行抢占检查,判断是否有更高优先级任务。

3.1、OSIntExit中断退出

主要是对中断嵌套计数器OSIntNesting减1,如果是中断嵌套,则返回中断汇编代码继续处理,直到所有中断退出,如果中断都处理完成(OSIntNesting为0),则调用OS_SchedNew更新就绪任务的最高优先级OSPrioHighRdy。

如果被中断的任务的优先级最高,则返回中断汇编代码,中断汇编代码负责恢复中断上下文;

如果有更高优先级的任务就绪,则更新OSTCBHighRdy、OSTCBHighRdy->OSTCBCtxSwCtr、OSCtxSwCtr,并调用OSIntCtxSw执行任务切换,OSIntCtxSw不同于任务主动睡眠的切换,OSIntCtxSw函数调用前已经保存了被切换出去的任务的上下文,因此OSIntCtxSw仅恢复下一任务的上下文即可。

void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif



    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0) {                            /* Prevent OSIntNesting from wrapping       */
            OSIntNesting--;
        }
        if (OSIntNesting == 0) {                           /* Reschedule only if all ISRs complete ... */
            if (OSLockNesting == 0) {                      /* ... and 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++;                          /* Keep track of the number of ctx switches */
                    OSIntCtxSw();                          /* Perform interrupt level ctx switch       */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
}

3.2、OSIntCtxSw任务上下文恢复

前面已经讲了,中断上下文已经保存,OSIntCtxSw仅恢复下一任务上下文。

OSIntCtxSw主要执行以下任务:

1、设置OSPrioCur、OSTCBCur,这两个值用在不同的地方,OSPrioCur主要用于与最高就绪任务优先级比较,OSTCBCur主要用于任务上下文的切换及任务统计;

2、获取下一任务的sp(栈顶保存了任务的上下文),恢复任务上下文。

OSIntCtxSw:
        LDR     R0, =OSPrioHighRdy                              // OSPrioCur = OSPrioHighRdy;
        LDR     R1, =OSPrioCur
        LDRB    R0, [R0]
        STRB    R0, [R1]

        LDR     R0, =OSTCBHighRdy                               // OSTCBCur  = OSTCBHighRdy;
        LDR     R1, =OSTCBCur
        LDR     R0, [R0]
        STR     R0, [R1]

        LDR     R0, =OSTCBHighRdy                               // SP = OSTCBHighRdy->task_stack;
        LDR     R0, [R0]
        LDR     SP, [R0]

        LDMFD   SP!, {R0}                                       // RESTORE NEW TASK'S CONTEXT:
        MSR     SPSR_cxsf, R0                                   //    Pop new task's CPSR,
        LDMFD   SP!, {R0-R12, LR, PC}^                          //    Pop new task's context.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值