cortex-m3
模式及特权
cortex-m3执行任务有两种模式,Thread mode & Handler mode。
正常任务在thread mode 下执行
异常和中断在handler mode 下执行
handler mode 始终在 privilege 下执行,使用 MSP (即主栈指针)
thread mode 使用 privilege 还是 UNprivilege、使用 MSP 还是 PSP 由内核寄存器 control 决定。
对于有操作系统的程序,操作系统相关的任务始终在 handler mode 下执行,调用 SVC 进入。
用户任务始终在 thread mode 下执行(除 privilege 操作使用 SVC 进入 handler mode 处理)。
栈空间
MSP、PSP双栈的优点:
用户任务栈使用 PSP 访问,每个任务有自己的堆栈空间,创建任务时分配的空间
系统任务使用 MSP 访问,系统初始化时建立的堆栈空间,启动文件分配的空间
用户任务栈不会影响到系统任务栈。
编译器编译程序时,
先将需要使用的寄存器入栈,再操作寄存器执行任务,
待程序执行完毕再出栈,所以不用担心中断会破坏程序。
系统异常和中断嵌套时,硬件自动入栈 8个内核寄存器:
xPSR、PC、LR、R12、R3、R2、R1、R0
入栈使用的栈指针为被中断的任务所使用的栈指针,
一般有操作系统时,用户任务使用 PSP,系统任务使用 MSP。
R4-R11需要手动入栈
RTX4 HAL_CM3.c 源码分析
SVC_Handler
SVC中断处理程序
/*-------------------------- SVC_Handler ------------------------------------*/
__asm void SVC_Handler (void) {
PRESERVE8 //stack 8-byte alignment
#ifdef IFX_XMC4XXX
EXPORT SVC_Handler_Veneer
SVC_Handler_Veneer
#endif
IMPORT SVC_Count //引用SVC_Count
IMPORT SVC_Table //引用SVC_Table
IMPORT rt_stk_check //引用rt_stk_check
//======= 读取SVC 异常编号,系统任务使用 0 号
MRS R0,PSP ; Read PSP //PSP --> R0
LDR R1,[R0,#24] ; Read Saved PC from Stack //[R0+24] --> R1,即自动入栈的 PC --> R1
LDRB R1,[R1,#-2] ; Load SVC Number //[R1-2] --> R1,即PC回退2字节找到 SVC 号 --> R1
CBNZ R1,SVC_User //R1 != 0 ==> SVC_User, 0 为系统任务,其他为用户任务
//======= 硬件自动入栈的参数加载到 R0-R3,R12, 其中 R12 是异常任务入口
LDM R0,{R0-R3,R12} ; Read R0-R3,R12 from stack //[R0...] --> R0-R3,R12
BLX R12 ; Call SVC Function //调用系统任务(如果是初始化任务会修改 PSP 到 idle task 栈地址+32,模拟自动入栈)
//======= 保存 PSP 到 R12,比较 run & new,不需要任务切换就跳转到 SVC_Exit
MRS R12,PSP ; Read PSP //PSP --> R12
LDR R3,=__cpp(&os_tsk) //&os_tsk --> R3
LDM R3,{R1,R2} ; os_tsk.run, os_tsk.new //[R3...] --> R1,R2 ,R1 = run,R2 = new
CMP R1,R2 //R1-R2, update APSR
BEQ SVC_Exit ; no task switch //R1 == R2, ==> SVC_Exit (即没有切换任务)
//======= 如果 run 为 0,跳转到 SVC_Restore
CBZ R1,SVC_Restore ; Runtask deleted? //R1 == 0, ==> SVC_Restore,删除了正在执行前任务,就不需要保存环境了
//======= 保存旧任务环境,同时检查栈溢出
PUSH {R2,R3} //R2,R3 --> [MSP]
MOV R3,#1 //1 --> R3
STRB R3,[R1,#TCB_RETUPD] ; os_tsk.run->ret_upd = 1 //R3 --> [R1+TCB_RETUPD],更新返回值标记,主动进入 SVC 切换任务,等返回时需要提供返回值,如延时、信号量
STMDB R12!,{R4-R11} ; Save Old context //R4-R11 --> [R12...], 上文中有 PSP --> R12
STR R12,[R1,#TCB_TSTACK] ; Update os_tsk.run->tsk_stack //R12 --> [R1+TCB_TSTACK],更新任务 PSP 值供以后使用
BL rt_stk_check ; Check for Stack overflow //调用 rt_stk_check 检查栈溢出
POP {R2,R3} //[MSP] --> R2,R3
SVC_Restore //======= 恢复新任务环境
STR R2,[R3] ; os_tsk.run = os_tsk.new //R2 --> [R3],更新结构体 os_tsk,新任务 new 放到 run 位置
LDR R12,[R2,#TCB_TSTACK] ; os_tsk.new->tsk_stack //[R2+TCB_TSTACK] --> R12
LDMIA R12!,{R4-R11} ; Restore New Context //[R12...] --> R4-R11
LDRB R3,[R2,#TCB_RETUPD] ; Update ret_val? //[R2+TCB_RETUPD] --> R3
MSR PSP,R12 ; Write PSP //R12(PSP) --> PSP
//======= 更新任务返回值
CBZ R3,SVC_Return //R3 == 0, ==> SVC_Return
LDRB R0,[R2,#TCB_RETVAL] ; Write os_tsk.new->ret_val //任务返回值(如延时完成、有信号量) --> R0
//======= 更新 R0 到栈
SVC_Exit
STR R0,[R12] ; Function return value //R0 --> [R12],此时 R12 == PSP,指向栈R0位置
//======= 从 handler 返回 thread,并使用 PSP 栈
SVC_Return
MVN LR,#:NOT:0xFFFFFFFD ; set EXC_RETURN value //0xFFFFFFFD --> LR,表示返回到 thread mode,之后使用 PSP
#ifdef IFX_XMC4XXX
PUSH {LR}
POP {PC}
#else
BX LR //return to LR指向的位置
#endif
/*------------------- User SVC ------------------------------*/
SVC_User
PUSH {R4,LR} ; Save Registers //LR,R4 --> 栈
LDR R2,=SVC_Count //SVC_Count --> R2 ,另一个文件里面,SVC_Count是一个地址标记
LDR R2,[R2] //[R2] --> R2
CMP R1,R2 //R1-R2, --> APSR
BHI SVC_Done ; Overflow //C=1&Z=0 ==> SVC_Done
LDR R4,=SVC_Table-4 //SVC_Table-4 --> R4
LDR R4,[R4,R1,LSL #2] ; Load SVC Function Address //[R4 + R1<<2] --> R4
LDM R0,{R0-R3,R12} ; Read R0-R3,R12 from stack //[R0...] --> R0-R3,R12
BLX R4 ; Call SVC Function //调用 R4所指向的位置
MRS R12,PSP //PSP --> R12
STM R12,{R0-R3} ; Function return values //R0-R3 --> [R12...]
SVC_Done
POP {R4,PC} ; RETI //栈 --> PC,R4
ALIGN //填充对齐
}
PendSV_Handler
PendSV中断处理程序
/*-------------------------- PendSV_Handler ---------------------------------*/
__asm void PendSV_Handler (void) {
PRESERVE8
#ifdef IFX_XMC4XXX
EXPORT PendSV_Handler_Veneer
PendSV_Handler_Veneer
#endif
BL __cpp(rt_pop_req) //调用 rt_pop_req
Sys_Switch
LDR R3,=__cpp(&os_tsk) //os_tsk 地址 --> R3
LDM R3,{R1,R2} ; os_tsk.run, os_tsk.new //run,new --> R1,R2
CMP R1,R2 //R1-R2, update APSR
BEQ Sys_Exit //run == new, ==> Sys_Exit
//======= 保存 run 的环境,所谓环境即运行时内核寄存器的值
PUSH {R2,R3} //R2,R3 --> [MSP]
MOV R3,#0 //0 --> R3
STRB R3,[R1,#TCB_RETUPD] ; os_tsk.run->ret_upd = 0 //R3 --> [R1+TCB_RETUPD],切换任务不需要更新返回值
MRS R12,PSP ; Read PSP //PSP --> R12
STMDB R12!,{R4-R11} ; Save Old context //R4-R11 --> [R12],手动 PSP 压栈
STR R12,[R1,#TCB_TSTACK] ; Update os_tsk.run->tsk_stack //R12 --> [R1+TCB_TSTACK]
BL rt_stk_check ; Check for Stack overflow //栈溢出检查
POP {R2,R3} //[MSP] --> R2,R3
STR R2,[R3] ; os_tsk.run = os_tsk.new //new --> run,更新结构体 os_tsk
//======= 加载 new 的环境
LDR R12,[R2,#TCB_TSTACK] ; os_tsk.new->tsk_stack //[R2+TCB_TSTACK] --> R12
LDMIA R12!,{R4-R11} ; Restore New Context //[R12...] --> R4-R11,手动 POP 出栈
MSR PSP,R12 ; Write PSP //R12 --> PSP
//======= 更新任务返回值
LDRB R3,[R2,#TCB_RETUPD] ; Update ret_val? //[R2+TCB_RETUPD] --> R3
CBZ R3,Sys_Exit //R3 == 0, ==> Sys_Exit
LDRB R3,[R2,#TCB_RETVAL] ; Write os_tsk.new->ret_val //[R2+TCB_RETVAL] --> R3
STR R3,[R12] //R3 --> [R12],此时的R12 为 PSP 的位置,指向栈 R0
Sys_Exit
MVN LR,#:NOT:0xFFFFFFFD ; set EXC_RETURN value //从 handler 返回 thread 并使用 PSP 的指令 0xFFFFFFFD --> LR
#ifdef IFX_XMC4XXX
PUSH {LR}
POP {PC}
#else
BX LR ; Return to Thread Mode //跳转到 LR
#endif
ALIGN
}
SysTick_Handler
SysTick中断处理程序
/*-------------------------- SysTick_Handler --------------------------------*/
__asm void SysTick_Handler (void) {
PRESERVE8 //stack 8-byte alignment
#ifdef IFX_XMC4XXX
EXPORT SysTick_Handler_Veneer
SysTick_Handler_Veneer
#endif
BL __cpp(rt_systick) //call systick
B Sys_Switch //jump to Sys_Switch
ALIGN //padding & align to boundary
}
/*-- rt_System.c ------------ rt_systick ------------------------------------*/
void rt_systick (void) {
/* Check for system clock update, suspend running task. */
P_TCB next;
os_tsk.run->state = READY; //将正在执行的任务状态从run改为ready
rt_put_rdy_first (os_tsk.run); //将上述程序放到ready链表第一位
/* Check Round Robin timeout. */
rt_chk_robin (); //时间片模式下的任务的切换
/* Update delays. */
os_time++; //系统时间累加
rt_dec_dly (); //延时等待任务延时减一,如果延时变为零,改变状态到ready并加入ready链表,并给出返回值
/* Check the user timers. */
rt_tmr_tick (); //定时任务延时减一,并处理定时任务
/* Switch back to highest ready task */
next = rt_get_first (&os_rdy); //获取ready链表中的第一个任务
rt_switch_req (next); //将上述任务状态改为run
}
os_set_env
系统环境设置,此处为设置特权级,同时设置 thread mode 使用 PSP
/*--------------------------- os_set_env ------------------------------------*/
__asm void os_set_env (void) {
/* Switch to Unprivileged/Privileged Thread mode, use PSP. */
MOV R0,SP ; PSP = MSP // MSP = SP --> R0,内核复位时使用的是 MSP
MSR PSP,R0 // R0 --> PSP (MSR 特殊寄存器指令)
LDR R0,=__cpp(&os_flags) // &os_flags --> R0
LDRB R0,[R0] // [R0] --> R0
LSLS R0,#31 // 逻辑左移31位并更新PSR,即 ((R0<<31) == 0)?
MOVNE R0,#0x02 ; Privileged Thread mode, use PSP // APSR.Z != 0, 2-->R0
MOVEQ R0,#0x03 ; Unprivileged Thread mode, use PSP // APSR.Z == 0, 3-->R0
MSR CONTROL,R0 // R0 --> CONTROL
BX LR // 返回 LR 所指的位置并切换指令集
ALIGN // 填充对齐
}
rt_init_stack
初始化应用堆栈
/*--------------------------- rt_init_stack ---------------------------------*/
void rt_init_stack (P_TCB p_TCB, FUNCP task_body) {
/* Prepare TCB and saved context for a first time start of a task. */
U32 *stk,i,size;
/* Prepare a complete interrupt frame for first task start */
size = p_TCB->priv_stack >> 2; //判断任务栈是系统分配还是用户指定
if (size == 0) {
size = (U16)os_stackinfo >> 2; //系统分配任务栈4-byte对齐,方便32位指针操作
}
/* Write to the top of stack. */
stk = &p_TCB->stack[size]; //stk 指向栈尾边界
/* Auto correct to 8-byte ARM stack alignment. */
if ((U32)stk & 0x04) { //stk 8-byte对齐
stk--;
}
stk -= 16; //stk向前跳跃16个双字,用于任务切换时保存内核寄存器信息
/* Default xPSR and initial PC */
stk[15] = INITIAL_xPSR; //PSR 状态寄存器,这里是状态寄存器的地址
stk[14] = (U32)task_body; //PC 程序计数器寄存器,存储任务地址
/* Clear R1-R12,LR registers. */
for (i = 0; i < 14; i++) { //应该是R0-R12,LR, 共14个寄存器,赋初值0
stk[i] = 0;
}
/* Assign a void pointer to R0. */
stk[8] = (U32)p_TCB->msg; //stk[8] 存储 R0 寄存器,函数用 R0 寄存器存放形参
/* Initial Task stack pointer. */
p_TCB->tsk_stack = (U32)stk; //将stk位置作为任务栈地址,前半部分用于子程序调用,后半部分任务切换
/* Task entry point. */
p_TCB->ptask = task_body; //uVision debugger 用
/* Set a magic word for checking of stack overflow. */
p_TCB->stack[0] = MAGIC_WORD; //在栈首地址赋值一个数,用于检查栈溢出
}
头尾8-byte boundary
-----------------------------------------------------------------------------
| | |
stack 任务栈 tsk_stack R4-R11,R0-R3,R12,LR,PC,xPSR |
| | |
-----------------------------------------------------------------------------