常用寄存器
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,%function
rt_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,%function
rt_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, %function
PendCV_Handler:
MRS R2,PRIMASK
CPSID I
LDR R0, =rt_thread_switch_interrupt_flag
LDR R1,[R0]
CBZ R1,pendsv_exit
MOV R1,#0
STR R1,[R0]
LDR R0,=rt_interrupt_from_thread
LDR R1,[R0]
CBZ R1,switch_to_thread
MRS R1,PSP
STMFD R1!,{R4-R11} //将from线程的数据寄存器R4-R11压栈
LDR R0,[R0]
STR R1,[R0]
switch_to_thread:
LDR R1,=rt_interrupt_to_thread
LDR R1,[R1]
LDR R1,[R1]
LDMFD R1!,{R4-R11} //将to线程寄存器弹出
MSR PSP,R1 //更新栈指针
pendsv_exit:
MSR PRIMASK,R2
ORR LR,LR,#0X4
BX LR
rt_hw_context_switch_to
.global rt_hw_context_switch_to
.type rt_hw_context_switch_to,%function
rt_hw_context_switch_to:
LDR R1,=rt_interrupt_to_thread
STR R0,[R1]
LDR R1,=rt_interrupt_from_thread
MOV R0,#0
STR R0,[R1]
LDR R1,=rt_thread_switch_interrupt_flag
MOV R0,#1
STR R0,[R1]
LDR R0,=SHPR3
LDR R1,=PENDSV_PRI_LOWEST
LDR.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_VTOR
LDR R0,[R0] //读取中断向量表的位置
LDR R0,[R0] //读取 SP初始值
NOP
MSR MSP,R0 ;将SP初始值赋给MSP
/* enable interrupts at processor level */
CPSIE F
CPSIE I