-
裸机和多任务
1.1. 裸机程序的执行流程
首先介绍下裸机程序执行的一般流程, 裸机程序按照顺序执行 , 当执行到某处被中断打断时,执行中断服务程序前会保存现场,退出中断时会恢复现场,这样程序就会接着从回断前的位置开始执行。1.2. 多任务程序的执行流程
现有两个任务A和任务B, 任务A在执行时被中断打断,执行中断服务程序前保存任务A被打断处的现场, 然后执行中断服务程序,执行完后会怎么样呢? 如果我们不处理的话,cpu会恢复任务A的现场回到任务A继续执行。我们想要的结果是执行任务B,显然,此时恢复任务B的现场,那么就会执行任务B。这就是由A任务切换到B任务的过程。
2.代码解读
2.1 pendsv中断
以cortex-m3内核为例,通过写内核寄存器主动触发pendsv中断来执行任务调度
OSCtxSw
LDR R0, =NVIC_INT_CTRL ; 将NVIC控制寄存器的值加载到R0
LDR R1, =NVIC_PENDSVSET ; 将要设置的值加载到R1
STR R1, [R0] ; 写NVIC控制寄存器
BX LR
OSIntCtxSw和OSCtxSw的实现方式一样,这里不再赘述。前者在中断中调用,后者在任务中调用。这里要指出的是cortex-m3的寄存器(xPSR,PC,LR,R12,R3-R0)由硬件压栈, 剩余的寄存器(R4-R11)由软件压栈。如果是cortex-m4内核,还需要将浮点运算的相关寄存器入栈 。
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ;
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
2.2 首次启动任务
OSStartHighRdy
CPSID I ; 关中断
MOV32 R0, NVIC_SYSPRI14 ; 设置PendSV中断的优先级为最低
MOV32 R1, NVIC_PENDSV_PRI
STRB R1, [R0]
MOV32 R0, OS_CPU_ExceptStkBase ; 设置主堆栈栈顶指针MSP
LDR R1, [R0]
MSR MSP, R1
MOV32 R0, OSPrioCur ; 设置当前任务的优先级为任务就绪表中的最高优先级
MOV32 R1, OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
MOV32 R5, OSTCBCurPtr ; 设置当前任务控制块指针为任务就绪表中优先级最高的任务
MOV32 R1, OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R5]
LDR R0, [R2] ; 设置用户线程指针PSP为当前任务的栈顶指针
MSR PSP, R0
; 设置CONTROL寄存器,线程模式下的用户代码使用PSP,异常服务例程使用MSP
; 这两个堆栈指针的切换是全自动的,进入中断或异常时切换到MSP,退出中断或异常进入用户线程时切换到PSP。由CM3的硬件自动完成
MRS R0, CONTROL
ORR R0, R0, #2
; CONTROL[0]=0表示线程模式下拥有特权级(能够访问SCS系统控制空间,该空间包含了配置寄存器组以及调试组件的寄存器组)
MSR CONTROL, R0
ISB ; 指令同步隔离
LDMFD SP!, {R4-R11} ; 按照后进先出原则,从当前任务的栈顶弹出寄存器
LDMFD SP!, {R0-R3}
LDMFD SP!, {R12, LR}
LDMFD SP!, {R1, R2}
CPSIE I ; 开中断
BX R1 ; 跳转到当前任务开始执行
2.3 任务切换过程
OS_CPU_PendSVHandler是pendsv中断的入口。此函数是任务切换的核心。
OS_CPU_PendSVHandler
CPSID I ; 关中断,将当前任务的R4-R11压入它的栈
MRS R0, PSP ; R0 = PSP, 将R0指向当前任务的栈顶地址
STMFD R0!, {R4-R11} ; 将当前任务的R4-R11压入它的栈
MOV32 R5, OSTCBCurPtr ; 加载任务控制块指针到R5
LDR R6, [R5] ; 加载任务控制块记录的栈顶指针到R6
STR R0, [R6] ; 上述将R4-R11压栈后,栈顶地址R0发生了变化,所以更新R0到当前任务控制块
; 执行到此处,当前任务的上下文已经全部保存到了它的栈
MOV R4, LR ; 保存中断异常返回值到R4
BL OSTaskSwHook ; 执行任务切换钩子函数
MOV32 R0, OSPrioCur ; 把就绪任务的最高优先级作为当前任务的优先级
MOV32 R1, OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
MOV32 R1, OSTCBHighRdyPtr;把任务就绪表中优先级最高的任务赋给当前任务
LDR R2, [R1]
STR R2, [R5]
ORR LR, R4, #0xF4 ; exc_return的bit2设为1,保证异常退出时,从线程堆栈PSP做出栈操作,退出后使用PSP
LDR R0, [R2] ; 取出切换后的当前任务的栈顶指针
LDMFD R0!, {R4-R11} ; 弹出R4-R11寄存器
MSR PSP, R0 ; 重新设置线程栈顶指针PSP为切换后的任务的栈顶指针
CPSIE I ; 开中断
BX LR ; 退出异常, 进入线程模式
; 异常退出时,由硬件把xPSR,PC,LR,R12,R3-R0弹出到正在使用的堆栈PSP
END