这篇文章主要分析在arm cortex-a系列芯片上,上下文切换的分析,通过这篇文章,对大多数的cortex-a系列芯片的上下文切换有一定的了解。
我们知道,在进行上下文切换的时候,需要保存现场,线程线程切回来时要恢复现场,同样的处理中断的时候,也是这个逻辑,还有就是切换后不用返回的情况。所以我们需要从三个方面去分析这个问题:
1.任务切换后不返回(rt_hw_context_switch_to)
2.用户上下文线程切换(rt_hw_context_switch)
3.中断上下文线程切换(rt_hw_context_switch_interrupt)
栈的寄存器从栈顶依次向下为pc, lr, r0-r12, cpsr。
该函数将目标线程的栈顶指针载入到sp,线程在栈中保存的寄存器自顶向下依次为pc, lr, r0-r12, cpsr。首先恢复目标线程的cpsr,然后恢复换入线程的上下文并执行。代码注释如下:
rt_hw_context_switch_to:
ldr sp, [r0] @ 将目标线程的栈顶指针载入到sp
ldmfd sp!, {r4} @ 恢复目标线程的cpsr
msr spsr_cxsf, r4
ldmfd sp!, {r0-r12, lr, pc}^ @ 恢复换入线程的上下文并执行
ldmfd的寻址方式是事后递增方式(Increase After),所以我们将sp指针自增后,读到r4寄存器,然后通过msr写到cpsr寄存器中。然后再依次恢复其他的寄存器。
该函数在rt_system_scheduler_start函数最后被调用,也就是当系统初始化完成后,然后进行调度第一个最高优先级并已经就绪的任务。
而这次的初始化函数就不需要返回了。
该函数将当前线程的上下文压入栈中,根据当前Thumb状态调整返回时状态寄存器,并保存在栈中。线程在栈中保存的寄存器自顶向下依次为pc, lr, r0-r12, cpsr,栈顶指针sp保存参数1所指向的线程控制块中。随后从参数2指向的线程控制块中载入目标线程的栈顶指针到sp,从目标线程的栈中恢复线程上下文并执行。代码注释如下:
rt_hw_context_switch:
stmfd sp!, {lr} @ 线程恢复执行时的PC指入栈
stmfd sp!, {r0-r12, lr} @ 将当前线程的上下文压入栈中
mrs r4, cpsr
tst lr, #0x01
orrne r4, r4, #0x20 @ 根据当前Thumb状态调整返回时状态寄存器
stmfd sp!, {r4} @ 并保存在栈中
str sp, [r0] @ 栈顶指针sp保存参数1所指向的线程控制块中
ldr sp, [r1] @ 从参数2指向的线程控制块中载入目标线程的栈顶指针到sp
ldmfd sp!, {r4} @ 恢复目标线程的cpsr
msr spsr_cxsf, r4
ldmfd sp!, {r0-r12, lr, pc}^ @ 从目标线程的栈中恢复线程上下文并执行
这个函数就是将当前的栈现场寄存器保存,然后恢复下个寄存器的现场,典型的就是进行线程调度。
该函数在中断上下文中进行线程切换,为了不影响当前中断的执行,该例程将换入和换出线程的栈顶指针分别存放在全局变量rt_interrupt_from_thread和rt_interrupt_to_thread中,并设置rt_thread_switch_interrupt_flag为1;若该变量值已经设为1,说明之前已经准备好线程切换了,此时只需设置新的换入线程即可。等到中断处理完毕后再进行线程切换。代码注释如下:
rt_hw_context_switch_interrupt:
ldr r2, =rt_thread_switch_interrupt_flag
ldr r3, [r2]
cmp r3, #1 @ 判断rt_thread_switch_interrupt_flag是否为1
beq _reswitch @ 若该变量值已经设为1,说明之前已经准备好线程切换了,此时只需设置新的换入线程即可。
ldr ip, =rt_interrupt_from_thread @ 将换入线程的栈顶指针存放在全局变量rt_interrupt_from_thread
mov r3, #1 @ 设置rt_thread_switch_interrupt_flag为1
str r0, [ip]
str r3, [r2]
_reswitch:
ldr r2, =rt_interrupt_to_thread @ 将换出线程的栈顶指针存放在全局变量rt_interrupt_to_thread
str r1, [r2]
bx lr @ 中断返回
流程图如下所示:
中断处理过程中并不执行线程切换,只是通过函数rt_hw_context_switch_interrupt设置相关的全局变量。当中断处理完毕后,检查rt_thread_switch_interrupt_flag变量,若值不为1则正常返回;若值为1,则进行线程切换,实际的切换工作由中断入口函数中rt_hw_context_switch_interrupt_do部分来完成。代码注释如下:
rt_hw_context_switch_interrupt_do:
mov r1, #0 @ 设置rt_thread_switch_interrupt_flag为0
str r1, [r0]
mov r1, sp @ 中断上下文栈顶地址保存到r1
add sp, sp, #4*4
ldmfd sp!, {r4-r12,lr}@ 将中断中压栈的寄存器r4-r12和lr弹出
mrs r0, spsr @ 获取中断线程的cpsr
sub r2, lr, #4 @ 计算中断线程的PC
msr cpsr_c, #I_Bit|F_Bit|Mode_SVC @ 切换至SVC并禁止中断
stmfd sp!, {r2}
stmfd sp!, {r4-r12,lr}
ldmfd r1, {r1-r4}
stmfd sp!, {r1-r4}
stmfd sp!, {r0} @ 在新栈上保存中断线程的上下文
ldr r4, =rt_interrupt_from_thread
ldr r5, [r4]
str sp, [r5] @ 保存栈顶指针到换出线程的线程控制块
ldr r6, =rt_interrupt_to_thread
ldr r6, [r6]
ldr sp, [r6] @ 从新线程的线程控制块中载入栈顶指针到sp
ldmfd sp!, {r4} @ 弹出新线程的cpsr到spsr
msr spsr_cxsf, r4
ldmfd sp!, {r0-r12,lr,pc}^ @ 弹出线程上下文,切换至新线程执行
通过以上的分析,大概叙述了rt-thread的上下文切换时栈的运行情况,同理可以看cortex-m系列的上下文切换流程。