Ucos 操作系统核心功能点重要两块,任务调度和上下切换,对于移植系统人员来说主要关注上下文的切换,上下文切换主要涉及当前映像的保存,主要涉及三动作
- 关闭中断,调整当前BASEPRI 可进入中断的级别。保存现场(就是cpu 中的寄存器入栈),开启中断
- 当前优先及级指向最高优先级,当前的OSTCBCurPtr 指向最高优先级(切任务控制块的指针)
- 从当前的任务栈中恢复数据到cpu 寄存器中,恢复psp 指针,恢复basepri 写0 ,不使能屏蔽高于某个等级的中断
- exc_return 机制,LR寄存器写LR=0XFFF_FFFD返回线程模式,进入异常模式,cm3 会自动设置lr 寄存器的值
对于ucos 任务,任务切换是在中断中进行上下文进行切换,也就是pendsv ,优先级最低,所以有必要了解下中断相关知识
中断行为
中断嵌套
这块可以自行在《cm3内核权威指南》了解
咬尾中断
由主程序进入中断模式,首先进行保存现场,也就是R0-R3 ,R12,R14,R15 XPSR,寄存器入栈,当退出之后自动弹出,考虑以下场景,一个低优先级ISR1的中断正在执行,此时一个高优先级中断被触发,cm3 会优先响应高优先级中断,此时cm3 保存的寄存器不会立马弹出,待回到ISR1,退出后一起弹出,减少一次出栈入栈动作 (保存到当前使用的任务堆栈,如果psp 就直接保存到psp,msp 就是到msp 堆栈)
中断退出,相关设置如下
OSTaskStkInit: 栈帧初始化,构造初始化栈帧,程序启动时候需要按照 中断保存现场压入 r0-r3 xpsr PC LR 寄存器值,以便在保证中断进行任务切换时候,自动出栈一致
试想下,如果在初始时候这几个值未按照,中断自动保存寄存器顺序,那么在任务创建完成后第一次进行任务调度,从当前任务控制块中恢复寄存器的值:
1、pendsv 手动控制部分 R0-R11
2 、自动弹栈部分 xpsr pc r14 r12 R0-R3
一致性得不到保证,必然导致 程序异常 pc 指针可能都飞掉了
OSTaskStkInit:
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
{
CPU_STK *p_stk;
(void)opt; /* 'opt' is not used, prevent warning */
p_stk = &p_stk_base[stk_size]; /* Load stack pointer */
/* Align the stack to 8-bytes. */
p_stk = (CPU_STK *)((CPU_STK)(p_stk) & 0xFFFFFFF8u);
/* Registers stacked as if auto-saved on exception */
*(--p_stk) = (CPU_STK)0x01000000u; /* xPSR */
*(--p_stk) = (CPU_STK)p_task; /* Entry Point */
*(--p_stk) = (CPU_STK)OS_TaskReturn; /* R14 (LR) */
*(--p_stk) = (CPU_STK)0x12121212u; /* R12 */
*(--p_stk) = (CPU_STK)0x03030303u; /* R3 */
*(--p_stk) = (CPU_STK)0x02020202u; /* R2 */
*(--p_stk) = (CPU_STK)p_stk_limit; /* R1 */
*(--p_stk) = (CPU_STK)p_arg; /* R0 : argument */
*(--p_stk) = (CPU_STK)0xFFFFFFFDuL; /* R14: EXEC_RETURN; See Note 5 */
/* Remaining registers saved on process stack */
*(--p_stk) = (CPU_STK)0x11111111uL; /* R11 */
*(--p_stk) = (CPU_STK)0x10101010uL; /* R10 */
*(--p_stk) = (CPU_STK)0x09090909uL; /* R9 */
*(--p_stk) = (CPU_STK)0x08080808uL; /* R8 */
*(--p_stk) = (CPU_STK)0x07070707uL; /* R7 */
*(--p_stk) = (CPU_STK)0x06060606uL; /* R6 */
*(--p_stk) = (CPU_STK)0x05050505uL; /* R5 */
*(--p_stk) = (CPU_STK)0x04040404uL; /* R4 */
return (p_stk);
}
任务上下文切换如下: 此代码为正点原子官方例程里面的代码,理论上不需要保存R14寄存器的值,CM3权威指南指出,R14寄存器的值 会根据进入异常前的堆栈和模式设置相应的值,测试过在保存堆栈 R4-R11,R14 去除R14,程序不能跑起来,把R14 换成R2 寄存器的可以跑起来,个人感觉这块保存动作为了保证栈帧一致性,如果把创建任务执行的 OSTaskStkInit 中栈帧初始化中R14入栈也去掉,任务切换函数中的(OS_CPU_PendSVHandler )R14相关寄存器操作都可以去除
以下代码,理论上应该可以去除,可以分析代码逻辑,理论上R14 最终是从栈里面恢复的
MOV R4, LR ; Save LR exc_return value
ORR LR, R4, #0x04
OS_CPU_PendSVHandler
CPSID I ; 关中断
MOV32 R2, OS_KA_BASEPRI_Boundary ; OS_KA_BASEPRI_Boundary 地址存到R2
LDR R1, [R2] ; 存储器地址R2 的值读取到R1,也就是R1=OS_KA_BASEPRI_Boundary
MSR BASEPRI, R1 ; 设置 BASEEPRI 寄存器 值等于64 ,优先级数字比这个大的都屏蔽掉,数字越低优先级越高
DSB
ISB
CPSIE I ;使能中断MRS R0, PSP ; R0指向psp
IF {FPU} != "SoftVFP"
; Push high vfp registers if the task is using the FPU context
TST R14, #0x10
IT EQ
VSTMDBEQ R0!, {S16-S31}
ENDIFSTMFD R0!, {R4-R11, R14} ; 保存R4-R11,R14 寄存器的值到栈
MOV32 R5, OSTCBCurPtr ; R5 存OSTCBCurPtr 地址
LDR R1, [R5] ; R1 是OSTCBCurPtr->sp 地址
STR R0, [R1] ; OSTCBCurPtr->sp = sp; At this point, entire context of process has been saved
MOV R4, LR ; Save LR exc_return value
BL OSTaskSwHook ; Call OSTaskSwHook() for FPU Push & PopMOV32 R0, OSPrioCur ; OSPrioCur = OSPrioHighRdy;
MOV32 R1, OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]MOV32 R1, OSTCBHighRdyPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R2, [R1]
STR R2, [R5]ORR LR, R4, #0x04 ; 线程模式置位,理论上来说不需要,因为pendsv 是优先级最低的
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
LDMFD R0!, {R4-R11, R14} ; 此地方又重新覆盖了LR 寄存器,不是特别明白
IF {FPU} != "SoftVFP"
; Pop the high vfp registers if the next task is using the FPU context
TST R14, #0x10
IT EQ
VLDMIAEQ R0!, {S16-S31}
ENDIFMSR PSP, R0 ; 设置psp 寄存器的值
MOV32 R2, #0 ; 不做特殊优先级屏蔽动作,所有中断都可以响应
CPSID I ; Cortex-M7 errata notice. See Note #5
MSR BASEPRI, R2
DSB
ISB
CPSIE I
BX LR ; Exception return will restore remaining contextALIGN ; Removes warning[A1581W]: added <no_padbytes> of padding at <address>
END
参考文档:
Cortex-M3权威指南Cn.pdf