好久没有更新博客了,今天想学习一下嵌入式实时操作系统的上下文切换,浅谈一下自己的学习成果,如果有理解不正确的,欢迎各位大神批评指正。下面就以arm-contexM4为例,来介绍一下嵌入式实时操作系统是如何进行上下文切换的。
所谓上下文切换,其实质就是保存当前线程的地址,压栈,然后对目标线程进行出栈,跳到目标线程执行,在arm-contexM4中上下文切换都是基于pendSv中断进行的,具体源码如下所示:
.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 */
/*检查线程切换是否已经处理过,如果为0,则转到pendsv_exit*/
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 */
/*清pendSV处理过标志为0*/
MOV r1, #0x00
STR r1, [r0]
/*判断来源线程堆栈指针是否为0,如果为0,则直接跳到目标线程切换,如果不为0,则保存from线程上下文*/
LDR r0, =rt_interrupt_from_thread
LDR r1, [r0]
CBZ r1, switch_to_thread /* skip register save at the first time */
/*保存from线程堆栈指针*/
MRS r1, psp /* get from thread stack pointer */
/*对浮点数支持的处理*/
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
TST lr, #0x10 /* if(!EXC_RETURN[4]) */
VSTMDBEQ r1!, {d8 - d15} /* push FPU register s16~s31 */
#endif
/*连续压栈form线程堆栈,将R4到R11压入堆栈,先入R11*/
STMFD r1!, {r4 - r11} /* push r4 - r11 register */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
MOV r4, #0x00 /* flag = 0 */
TST lr, #0x10 /* if(!EXC_RETURN[4]) */
MOVEQ r4, #0x01 /* flag = 1 */
STMFD r1!, {r4} /* push flag */
#endif
/*保存堆栈指针*/
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 */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
LDMFD r1!, {r3} /* pop flag */
#endif
/*连续出栈,恢复to线程的R4-R11寄存器值*/
LDMFD r1!, {r4 - r11} /* pop r4 - r11 register */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
CMP r3, #0 /* if(flag_r3 != 0) */
VLDMIANE r1!, {d8 - d15} /* pop FPU register s16~s31 */
#endif
/*更新to线程的堆栈指针*/
MSR psp, r1 /* update stack pointer */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
ORR lr, lr, #0x10 /* lr |= (1 << 4), clean FPCA. */
CMP r3, #0 /* if(flag_r3 != 0) */
BICNE lr, lr, #0x10 /* lr &= ~(1 << 4), set FPCA. */
#endif
pendsv_exit:
/* restore interrupt */
MSR PRIMASK, r2
ORR lr, lr, #0x04
/*退出异常处理函数*/
BX lr
那么pendSV中断是如何触发的呢?其实是调度算法获取当前就绪队列中最高优先级的线程后调用上下文切换,触发pendSV中断的,其代码如下:
/*没有来源的线程上下文切换,通常用于创建线程后第一次调度器启动时调用*/
.global rt_hw_context_switch_to
.type rt_hw_context_switch_to, %function
rt_hw_context_switch_to:
/*R0:目标线程的堆栈指针(线程栈顶指针),将目标线程的堆栈指针存放在rt_interrupt_to_thread中*/
LDR r1, =rt_interrupt_to_thread
STR r0, [r1]
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
/* CLEAR CONTROL.FPCA */
MRS r2, CONTROL /* read */
BIC r2, #0x04 /* modify */
MSR CONTROL, r2 /* write-back */
#endif
/* set from thread to 0 */
/*将来源线程的堆栈设置为0,不保存原始线程堆栈*/
LDR r1, =rt_interrupt_from_thread
MOV r0, #0x0
STR r0, [r1]
/* set interrupt flag to 1 */
/*设置在中断里进行线程切换的标志为1,此标志在pendSV中断切换线程后置0*/
LDR r1, =rt_thread_switch_interrupt_flag
MOV r0, #1
STR r0, [r1]
/* set the PendSV and SysTick exception priority */
/*设置PendSV和SysTick中断为最低优先级*/
LDR r0, =NVIC_SYSPRI2
LDR r1, =NVIC_PENDSV_PRI
LDR.W r2, [r0,#0x00] /* read */
ORR r1,r1,r2 /* modify */
STR r1, [r0] /* write-back */
/*触发PendSv中断进行线程切换*/
LDR r0, =NVIC_INT_CTRL /* trigger the PendSV exception (causes context switch) */
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
/* restore MSP */
/*放弃从芯片启动到第一次上下文切换之前的堆栈内容,将MSP设置为启动时的值*/
LDR r0, =SCB_VTOR
LDR r0, [r0]
LDR r0, [r0]
NOP
MSR msp, r0
/*使能全局中断,确保pendSv能进行线程切换*/
/* enable interrupts at processor level */
CPSIE F
CPSIE I
第二种就是从一个当前线程切换到另外一个线程
rt_hw_context_switch_interrupt:
rt_hw_context_switch:
/* set rt_thread_switch_interrupt_flag to 1 */
LDR r2, =rt_thread_switch_interrupt_flag
LDR r3, [r2]
CMP r3, #1
BEQ _reswitch
MOV r3, #1
STR r3, [r2]
LDR r2, =rt_interrupt_from_thread /* set rt_interrupt_from_thread */
STR r0, [r2]
_reswitch:
LDR r2, =rt_interrupt_to_thread /* set rt_interrupt_to_thread */
STR r1, [r2]
LDR r0, =NVIC_INT_CTRL /* trigger the PendSV exception (causes context switch) */
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
BX LR