常用寄存器
PRIMASK寄存器
为1位宽的中断屏蔽寄存器。在置位时,它会阻止不可屏蔽中断(NMI)和HardFault异常之外的所有异常(包括中断)。
实际上,它是将当前异常优先级提升为0,这也是可编程异常/中断的最高优先级。
FAULTMASK寄存器
FAULTMASK与PRIMASK相类似,但同时它能屏蔽HardFault异常,它实际上是将异常优先级提升到了-1。
程序状态寄存器(xPSR)
xPSR包含:
- 应用PSR(APSR)
- 执行PSR(EPSR)
- 中断PSR(IPSR)
注:GE 在 Cortex-M4 等 ARMv7E-M 处理器中存在,在 Cortex-M3 处理器中则不可用。
- N:负标志
- Z:零标志
- C:进位(或者非借位)标志
- V:溢出标志
- Q:包含标志
- GE:大于或等于标志
- ICI/IT:中断继续指令位
- T:Thumb状态,总是1,清除此位会引起错误异常
- 异常变化:表示处理器正在处理的异常
中断向量表
Cortex-M系列处理器的中断向量表位于0x00000000,单Cortex-M3/4系列提供了SCB_VTOR,所以中断向量表的位置位于0x00000000+SCB_VTOR。
异常相关指令
- CPSIE I:使能中断(清除PRIMASK)
- CPSID I:禁止中断(设置PRIMASK),NMI和HardFault不受影响
- CPSIE F:使能中断(清除FAULTMASK)
- CPSID F:禁止中断(设置FAULTMASK),NMI不受影响
移植过程
在嵌入式领域有多种不同CPU架构,例如Cortex-M,ARM920T、MIPS、RISC-V等等。
为了使RT-Thread能够在不同CPU架构的芯片上运行,RT-Thread提供了一个libcpu抽象层来适配不同的CPU架构。
libcpu层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。
libcpu抽象层向下提供了一套统一的CPU架构移植接口,这部分接口包含了全局中断开关函数,线程上下文切换函数,时钟节拍的配置和中断函数、Cache等等内容。
libcpu移植相关API,要对CPU进行移植只需要实现这些接口
关闭全局中断
/*rt_base_t rt_hw_interrupt_disable(void);*/.global rt_hw_interrupt_disable
.type rt_hw_interrupt_disable,%functionrt_hw_interrupt_disable:MRS R0,PRIMASK ;将PRIMASK关中断前的状态存入R0,并作为函数返回值返回CPSID I ;关闭中断BX LR
/*void rt_hw_interrupt_enable(rt_base_t level); level是调用关中断函数时的返回值,代表关中断前PRIMASK的值*/
.global rt_hw_interrupt_enable
.type rt_hw_interrupt_enable,%functionrt_hw_interrupt_enable:MSR PRIMASK,R0 ;将level写入PRIMASK寄存器,恢复关中断前的状态BX LR
实现线程栈初始化
在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init()函数会调用栈初始化函数rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。
上下文在栈里的排布如图:
rt_uint8_t *rt_hw_stack_init(void *tentry,void *parameter,rt_uint8_t *stack_addr,void *texit)
{struct stack_frame *stack_frame;rt_uint8_t *stk;unsigned long i;stk = stack_addr + sizeof(rt_uint32_t);stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);stk -= sizeof(struct stack_frame);stack_frame = (struct stack_frame *)stk;/* init all register */for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++){((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;}/* 根据 ARM APCS 调用标准,将第一个参数保存在 r0 寄存器 */stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; stack_frame->exception_stack_frame.r1 = 0; /* r1 */stack_frame->exception_stack_frame.r2 = 0; /* r2 */stack_frame->exception_stack_frame.r3 = 0; /* r3 *//* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */stack_frame->exception_stack_frame.r12 = 0; //将线程退出函数的地址保存在lr寄存器stack_frame->exception_stack_frame.lr = (unsigned long) texit;//将线程入口函数的地址保存在pc寄存器stack_frame->exception_stack_frame.pc = (unsigned long) tentry;/* 设置 psr 的值为 0x01000000L,表示默认切换过去是 Thumb 模式 */stack_frame->exception_stack_frame.psr = 0x01000000L; /* PSR */return stk;
}
实现上下文切换
在Cortex-M里面上下文切换都是统一使用PendSV异常来完成。
为了能适应不同的CPU架构,RT-Thread的libcpu抽象层需要实现三个线程相关的函数:
- rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。
- rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。
- rt_hw_context_switch_intrrupt():在中断环境下,从当前线程切换到目标线程。
PendSV_Handler
产生PendSV异常时,Cortex-M系列处理器硬件会自动将from线程的PSR、PC、LR、R12、R3-R0压栈,因此在PendSV_Handler中,我们需要把from线程的R11-R4压栈,并把to线程的R11-R4弹出。修改PSP为to线程的栈地址,在退出PendSV中断时,硬件会自动弹出R3-R0、R12、LR、PC、PSR寄存器。
/*R0->保存from线程的栈,R1->保存to线程的栈*/
/*PSR PC LR...被放入from线程的栈*/
.global PendSV_Handler
.type PendSV_Handler, %functionPendCV_Handler:MRS R2,PRIMASKCPSID ILDR R0, =rt_thread_switch_interrupt_flagLDR R1,[R0]CBZ R1,pendsv_exit MOV R1,#0STR R1,[R0]LDR R0,=rt_interrupt_from_threadLDR R1,[R0]CBZ R1,switch_to_threadMRS R1,PSPSTMFD R1!,{R4-R11} //将from线程的数据寄存器R4-R11压栈LDR R0,[R0] STR R1,[R0]switch_to_thread:LDR R1,=rt_interrupt_to_threadLDR R1,[R1]LDR R1,[R1]LDMFD R1!,{R4-R11} //将to线程寄存器弹出MSR PSP,R1 //更新栈指针pendsv_exit:MSR PRIMASK,R2ORR LR,LR,#0X4BX LR
rt_hw_context_switch_to
.global rt_hw_context_switch_to
.type rt_hw_context_switch_to,%functionrt_hw_context_switch_to:LDR R1,=rt_interrupt_to_threadSTR R0,[R1]LDR R1,=rt_interrupt_from_threadMOV R0,#0STR R0,[R1]LDR R1,=rt_thread_switch_interrupt_flagMOV R0,#1STR R0,[R1]LDR R0,=SHPR3LDR R1,=PENDSV_PRI_LOWESTLDR.W R2,[R0,#0]ORR R1, R1, R2 /* modify */STR R1, [R0] /* write-back */LDR R0, =ICSR /* trigger the PendSV exception (causes context switch) */LDR R1, =PENDSVSET_BIT ;0x10000000,ICSR 的第 28 位为 PendSV set-pending bit.STR R1, [R0] ;Writing 1 to this bit is the only way to set the PendSV exception state to pending.//恢复MSP LDR R0,=SCB_VTORLDR R0,[R0] //读取中断向量表的位置LDR R0,[R0] //读取 SP初始值NOPMSR MSP,R0 ;将SP初始值赋给MSP/* enable interrupts at processor level */CPSIE FCPSIE I