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);

它们都是使用汇编实现的,其中作为参数的fromto分别是原线程 和目标线程的线程栈指针的地址,关键部分的操作基本是一致的:将原/目标 栈指针的地址临时保存到全局变量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 结束后,转去执行目标线程,并且使用它专属的线程栈了。