任务管理和调度策略

任务(Task)

任务可谓是 VxWorks 的核心概念,类似于 Linux 系统中的内核线程,是系统中最基本的独立执行单元。任务运行在特权模式(Supervisor Mode),相互之间没有地址空间隔离,整个系统共用同一套虚拟地址空间(暂时不考虑 RTP 的情况)。由于任务直接运行在特权模式,可以直接用函数调用的方法使用 VxWorks API,不需要系统调用。VxWorks 支持多任务,而且是抢占式多任务,因此一个任务不需要主动让出 CPU,系统会在必要的时刻强制停止当前任务的执行,而切换到一个新的任务。

任务切换、陷阱

系统支持多任务,那就需要实现任务切换。任务切换就是从一个任务切换到另一个任务的过程,这个过程往往是借助 CPU 的陷阱机制实现的。

陷阱(Trap)在不同体系架构下可能有不同的名字,例如 x86 下的中断和异常都可以称作陷阱。所谓陷阱,是一种异步的事件,当陷阱发生的时候,CPU 可以暂停目前正在运行的代码,保存状态,跳转到陷阱处理程序执行,完成之后恢复状态,继续执行之前被打断的程序。具体来说就是:

  1. 陷阱发生,CPU 将指令指针(PC)、栈指针(SP)的值保存在栈上(保存状态,这一步由 CPU 自动进行)
  2. 根据陷阱的向量号,跳转到陷阱处理程序的入口点开始执行(执行 ISR,仍然 CPU 自动进行)
  3. ISR 执行完毕之后,软件执行陷阱返回指令,CPU 从栈上恢复出 PC 和 SP 的值(恢复状态)
  4. 继续执行之前被打断的程序

VxWorks 区分了两种不同的陷阱,即中断和异常。二者的主要区别在于来源不同,中断的来源往往外部设备,例如时钟、键盘等;异常则是 CPU 内部产生的,例如除零、违反特权级、分页异常等。中断和异常的处理程序都有自己的栈,当陷阱发生时,首先保存状态到当前栈,然后切换到相应的中断栈或异常栈,执行处理函数,最后切换回执行栈并恢复状态。

由于陷阱前和陷阱后,PC 与 SP 的取值完全相同,因此完全感觉不到任何中断。但是有一点,保存 PC/SP 和恢复 PC/SP 都是通过栈进行的,如果我们在处理陷阱的过程中改变了栈,就会导致陷阱返回的时候,恢复的不是陷阱之前被中断的程序,这就是任务切换的基本思路。

执行栈、异常栈、中断栈

如果了解一些操作系统的概念,应该会知道,每个线程都拥有自己的栈。但是在 VxWorks 中,存在着三种栈:

  • 执行栈(Execution Stack),表示用于执行正常代码,进行函数调用、保存局部变量的栈。每个任务都拥有自己专属的执行栈。
  • 异常栈(Exception Stack),当异常发生的时候,ISR 会切换到这个栈,并执行相应的执行。理论上,异常处理程序完全可以在执行栈上运行,但是 VxWorks 还是给异常单独分配了一个栈。异常栈也是每个任务一份,因此当一个任务正在执行异常处理代码的时候,也是可以进行任务切换的。
  • 中断栈(Interrupt Stack),当中断发生的时候,ISR 会切换到这个栈,并执行相应的中断处理函数。中断栈并不是每个任务一份,而是每个 CPU 一份,因此中断上下文不属于任何任务。每次从中断返回的时候,有可能进行调度和任务切换。

每个任务都拥有自己的执行栈和异常栈,当一个任务处于运行状态时,它有可能正在运行栈上执行业务代码,也有可能在异常栈上处理异常,这两种状态都属于运行状态。运行状态下,如果产生中断,任务的当前上下文(PC、SP 以及其他寄存器)会被保存到当前栈(运行栈或者异常栈)上,切换到中断栈,执行中断处理代码。

在中断即将返回的时候,VxWorks 会检查目前是否需要执行调度和任务切换(RoundRobin 情况)。例如中断之前正在运行任务 A,中断之后需要切换到任务 B,那么我们在完成中断返回前,并不会从中断栈切换回任务 A 的运行/异常栈,而是切换到任务 B 的运行/异常栈。

调度(Scheduling)

调度算法