上下文切换要完成两个操作:保存被中止的线程的上下文;恢复待切入的线程的上下文。在cm3中,上下文切换函数如下:
;函数原型: void__asm_switch_context(struct thread_vm*new_vm,struct thread_vm*old_vm);
;-----------------------------------------------------------------------------
__asm_switch_context PROC
EXPORT __asm_switch_context
mrs r4,xpsr
orr r4,#0x01000000 ;xpsr的T标志读不出来,得手工置位。
push {r4} ;保存xpsr
push {lr} ;保存PC,从其他线程切换回来时,相当于原线程调用
;本函数返回,故用lr替代pc
push {r0-r3,r12,lr} ;保存r0-r3,r12,lr
push {r4-r11}
str sp,[r1] ;保存旧上下文栈指针到old_vm->stack
ldr sp,[r0] ;取得新上下文指针
bl int_restore_asyn_signal ;对应done函数开头的 int_save_asyn_signal 调用
svc 0
ENDP
__asm_switch_context函数完成的只是保存上下文,而新线程上下文的切入是在0号svc调用中完成的,0号svc调用的代码如下:
add r0,#32 ;R0保存的是psp指针值
ldmfd r0!,{r4-r11} ;手工弹出r4-r11
msr psp,r0 ;psp指向待切入的上下文
bx lr ;返回,实际弹出的将是带切入上下文
在线程栈中,最底下的8个字是执行svc 0时自动压栈的,我们并不需要,因此第一句直接加32,跳过这8个字。此时,r0指向的是待切入的线程的上下文了。
随后是手工弹出r4-r11,而r0-r3、r12、lr、pc、xpsr则是从svc返回时走法律程序自动恢复,因为此时psp已经指向待切入的线程的上下文,故svc将直接返回到新线程,至此,上下文切换完成。
这跟arm7版本不一样,在arm7版本中,是直接手工恢复新线程的上下文,具体可参阅djyos for 44b0的代码。那为什么cm3版本需要这么严格的程序,动用svc才能实现切换呢?问题就出在ICI位上,cm3为了保证中断响应速度,对需要很长执行时间的多寄存器加载指令ldm和多寄存器存储指令stm以及IT指令,可在执行过程中中断,利用ICI为保存执行进度,中断恢复时从被中断处继续执行该指令。然而ICI位是只读的,因此,手工恢复寄存器是不可能恢复这些位的,只有从真正的异常返回才能根据ICI位的状态继续被中断的指令执行。
有人要问了,上下文切换不是通过调用__asm_switch_context函数实现的吗?调用该函数时,不可能正在执行ldm或stm或IT指令啊?是的,没错,但是,保存上下文除了主动调用上下文切换函数外,还可能是中断。如果在中断服务例程中使得高优先级的线程就绪,中断返回将直接返回到高优先级线程,被中断打断的低优先级线程的上下文就被保留了,直到该线程变成最高优先级线程才被切回。中断在任何时候都有可能发生,当然也可能在正在执行stm、ldm或IT指令时发生,所以,中断保存的上下文,必须模拟异常返回才能确保恢复执行被中断的半截子指令。
3、启动多事件调度
在初始化完成后,切入第一个线程,标志着多事件调度的开始。cm3版本中,初始化程序工作在handler状态,使用msp作为栈指针,而事件在特权状态的thread模式处理,使用psp为栈指针。因此,启动多事件调度要完成以下工作:
a、 切换到特权模式的thread状态。
b、 切入第一个需要处理的事件的线程的上下文。
c、 因初始化函数的任务已经终结,无需保存其上下文。 |