当 UCOS 决定运行另一个任务时,则需要保存当前任务的上下文,其中主要包括 CPU 相关寄存器,以及当前的任务的栈,并恢复新任务的上下文和重新开始执行任务。这个过程称为上下文切换。
上下文切换增加了内存开销。CPU 的寄存器越多,内存开销越大。执行上下文切换的时间也主要是 CPU 寄存器的保存与恢复。
本文我们主要借助于一个虚构的 CPU 来讨论上下文切换的一般过程。此虚构 CPU 包含 16 个整数寄存器 (R0 - R15),一个独立的 ISR 栈指针、一个独立的状态寄存器。每个寄存器均为 32 位,并且每个寄存器都能存储地址或数据。程序计数器(指令指针寄存器)是 R15,且有两个独立的栈指针 R14 和 R14',其中 R14 表示任务栈指针(task stack pointer TSP),R14' 表示 ISR 栈指针(ISR stack pointer ISP)。当响应一个中断时,CPU 自动切换到 ISP。注意在 ISR 中是可以访问任务栈的(例如:在 ISR 中可以先任务栈中 push 和 pop 元素),中断栈也是可以从任务中访问的。
在 UCOS 中,栈的当前帧总是看起像中断刚刚发生的,处理器所有的寄存器都保存于此。任务一旦创建就处于继续状态,显然当前任务的上下文是有软件进行预初始化的。
任务栈指针指向存在任务栈中的最后一个寄存器。PC(program counter) 指针 和 SR(status register)最早保存到栈中的。实际上,在响应中断处理程序时, CPU 自动保存 PC 和 SR 到任务栈中,然后在中断处理程序中保存剩余的寄存器。需要注意的是, SP(stack pointer 或
R14)实际并不是保存在任务栈中,而是保存在任务的 TCB 中。
中断栈指针指向的中断栈的栈顶,是一个不同于任务栈的内存区域。当一个 ISR 执行时,处理器用 R14' 作为 ISR 调用的函数栈。
UCOS 中一共存在两种类型的上下文切换:一是从任务中切换,二是从 ISR 中切换。任务级别切换是通过 OSCtxSw() 实现,其实际上是通过调用宏函数 OS_TASK_SW() 完成。ISR上下文切换是 OSIntCtxSw() 实现,此函数是汇编函数,在 os_cpu_a.asm 中。
1 OSCtxSw()
在任务级调度(OSSched())时 OSCtxSw() 将被调用用于切换当前任务到一个高优先级的任务。下图是调用 OSCtxSw() 之前 UCOS 的变量和数据结构状态。
(1) OSTCBCurPtr 指针指向当前正在执行的任务,且此任务调用 OSSched()。
(2)OSSched() 找到即将运行的新任务,并将 OSTCBHighRdyPtr 指向其 OS_TCB。
(3)OSTCBHighRdyPtr->StkPtr 指针指向新任务的任务栈
(4)当 UCOS 创建或挂起一个任务时,总是会保留中断发生时的这个栈帧,多有的 CPU 寄存器保存于此,这保留了任务重新开始执行时应有状态。
(5)OSSChed() 调用后,TSP 所指向的位置,依赖于如何调用 OSCtxSw() ,TSP 可以指向 OSCtxSw() 的返回地址。
下图表达了 UCOS 调用 OSCtxSw() 执行任务上下文切换的过程。
(1)OSCtxSw() 首先保存当前任务的 SR 和 PC 到任务栈中,保存顺序依赖于 CPU 在中断发生时对于栈帧的要求。这里,假设先保存 SR,再保存 PC,然后保存剩余寄存器。
(2)OSCtxSw() 保存寄存器栈指针到 TCB 的 StkPtr 中,开始上下文切换。简言之, OSTCBCurPtr->StkPtr = R14。
(3)OSCtxSw() 然后加载待执行任务的任务栈指针到 R14 中,即 R14 = OSTCBHighRdyPtr->StkPtr。
(4)最后, OSCtxSw() 恢复 CPU 的寄存器为新任务栈中的寄存器帧。SR 和 PC 通常时 CPU 在退出 ISR 时自动完成加载。
2 OSIntCtxSw()
OSIntCtxSw()(参见 os_cpu_a.asm)在 ISR 级别的调度函数 OSIntExit() 中调用,用于切换到一个高优先级的就绪任务。下图是说明了 OSIntCtxSw() 调用前 UCOS 的变量和结构的状态。
图中假设 UCOS 在 ISR开始时已经保存好了 CPU 寄存器(中断管理中将会详述)。由于这个原因,注意到 OSTCBCurPtr->StkPtr 的值是一个正在被挂起的任务任务栈指针(图中左边)。OSIntCtxSw() 不用承担保存被挂起任务(当前任务)的 CPU 寄存器的工作,因为这个已经在中断管理部分完成。
下图表示了 OSIntCtxSw() 完成一半的上下文切换,其工作正好是 OSCtxSw() 的一半。
(1)OSIntCtxSw() 加载 CPU SP 寄存器为新任务的任务栈顶,即 R14=OSTCBHighRdyPtr->StkPtr。
(2)OSIntCtxSw() 然后恢复 CPU 寄存器的值。