RT-Thread 在 Cortex-M3 架构中执行线程切换的一些实现细节
本文记录在研究 RT-Thread 源码过程中,对 RT-Thread 在 Cortex-M3 架构的MCU中实现线程切换的底层实现的一些理解。
RT-Thread 提供了一组接口函数用于执行上下文切换,如下:
void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to);
void rt_hw_context_switch_to(rt_ubase_t to);
void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to);
它们都是使用汇编实现的,其中作为参数的from和to分别是原线程
和目标线程的线程栈指针的地址,关键部分的操作基本是一致的:将原/目标
栈指针的地址临时保存到全局变量rt_interrupt_from_thread/rt_interrupt_to_thread
中,然后设置 PendSV 异常悬起标志,在 PendSV 异常处理流程(PendSV_Handler)
中最终完成上下文切换的操作。
rt_thread结构体中的成员sp在上下文切换时用于记录线程栈
指针和恢复上下文,其地址作为参数按需传递给以上的接口函数。
stack_frame结构体中的成员对应上下文切换时需要保存的一些
寄存器,如下:
struct exception_stack_frame
{
rt_uint32_t r0;
rt_uint32_t r1;
rt_uint32_t r2;
rt_uint32_t r3;
rt_uint32_t r12;
rt_uint32_t lr;
rt_uint32_t pc;
rt_uint32_t psr;
};
struct stack_frame
{
/* r4 ~ r11 register */
rt_uint32_t r4;
rt_uint32_t r5;
rt_uint32_t r6;
rt_uint32_t r7;
rt_uint32_t r8;
rt_uint32_t r9;
rt_uint32_t r10;
rt_uint32_t r11;
struct exception_stack_frame exception_stack_frame;
};
Cortex-M3 在进入异常处理流程时,由硬件自动保存r0 ~ r3,r12,
lr,pc和psr寄存器,也即以上exception_stack_frame结构体
所表示的寄存器,r4 ~ r11 需要手动保存。stack_frame中的exception_stack_frame
在内存中处于高地址,在进入PendSV_Handler流程时,它所
代表的寄存器已经自动入栈了,接着需要手动保存寄存器r4 ~ r11。
保存完毕后需要记录最新的栈指针;退出PendSV_Handler流程之前,
手动将存放在栈中低地址的 r4 ~ r11 出栈,再更新栈指针,保存在高地址部分
的寄存器会在PendSV_Handler 结束后由硬件自动出栈。
.global PendSV_Handler
.type PendSV_Handler, %function
PendSV_Handler:
/* disable interrupt to protect context switch */
MRS R2, PRIMASK
CPSID I
/* get rt_thread_switch_interrupt_flag */
LDR R0, =rt_thread_switch_interrupt_flag
LDR R1, [R0]
CBZ R1, pendsv_exit /* pendsv already handled */
/* clear rt_thread_switch_interrupt_flag to 0 */
MOV R1, #0
STR R1, [R0]
LDR R0, =rt_interrupt_from_thread
LDR R1, [R0]
CBZ R1, switch_to_thread /* skip register save at the first time */
MRS R1, PSP /* get from thread stack pointer */
STMFD R1!, {R4 - R11} /* push R4 - R11 register */
LDR R0, [R0]
STR R1, [R0] /* update from thread stack pointer */
switch_to_thread:
LDR R1, =rt_interrupt_to_thread
LDR R1, [R1]
LDR R1, [R1] /* load thread stack pointer */
LDMFD R1!, {R4 - R11} /* pop R4 - R11 register */
MSR PSP, R1 /* update stack pointer */
pendsv_exit:
/* restore interrupt */
MSR PRIMASK, R2
ORR LR, LR, #0x04
BX LR
在PendSV_Handler中,MRS R1, PSP指令取线程栈指针到r1,然后
在STMFD R1!, {R4 - R11}中将r4 ~ r11寄存器的内容保存到栈中,保存
完成后r1的内容会被更新为最新的线程栈指针值,将它保存到原线程记录
线程栈指针的位置(原线程rt_thread结构体中的sp);
LDMFD R1!, {R4 - R11} 指令对保存在线程栈中的r4 ~ r11寄存器进行
恢复,恢复结束后r1的内容会被更新为最新的线程栈指针值,用它来
更新线程栈指针,确保PendSV_Handler结束后exception_stack_frame
代表的寄存器内容能正确恢复;
在异常处理流程中lr寄存器有特殊作用,其bit2设置为1的作用是表示
从进程栈做出栈操作,异常返回后使用PSP。所以,PendSV_Handler
结束后,转去执行目标线程,并且使用它专属的线程栈了。