# 任务切换 - Open-Source-OS-Tutorial-Summer-of-Code-2022 文档

Hide navigation sidebar
Hide table of contents sidebar
Toggle table of contents sidebar

# 任务切换

本节我们将见识操作系统的核心机制—— 任务切换 ， 即应用在运行中主动或被动地交出 CPU 的使用权，内核可以选择另一个程序继续执行。 内核需要保证用户程序两次运行期间，任务上下文（如寄存器、栈等）保持一致。

## 任务切换的设计与实现

任务切换与上一章提及的 Trap 控制流切换相比，有如下异同：

- 与 Trap 切换不同，它不涉及特权级切换，部分由编译器完成；

- 与 Trap 切换相同，它对应用是透明的。

事实上，任务切换是来自两个不同应用在内核中的 Trap 控制流之间的切换。 当一个应用 Trap 到 S 态 OS 内核中进行进一步处理时， 其 Trap 控制流可以调用一个特殊的 `__switch` 函数。 在 `__switch` 返回之后，Trap 控制流将继续从调用该函数的位置继续向下执行。 而在调用 `__switch` 之后到返回前的这段时间里， 原 Trap 控制流 `A` 会先被暂停并被切换出去， CPU 转而运行另一个应用的 Trap 控制流 `B` 。 `__switch` 返回之后，原 Trap 控制流 `A` 才会从某一条 Trap 控制流 `C` 切换回来继续执行。

我们需要在 `__switch` 中保存 CPU 的某些寄存器，它们就是 任务上下文 (Task Context)。

下面我们给出 `__switch` 的实现：

     1# os/src/task/switch.S
     2
     3.altmacro
     4.macro SAVE_SN n
     5    sd s\n, (\n+2)*8(a0)
     6.endm
     7.macro LOAD_SN n
     8    ld s\n, (\n+2)*8(a1)
     9.endm
    10    .section .text
    11    .globl __switch
    12__switch:
    13    # __switch(
    14    #     current_task_cx_ptr: *mut TaskContext,
    15    #     next_task_cx_ptr: *const TaskContext
    16    # )
    17    # save kernel stack of current task
    18    sd sp, 8(a0)
    19    # save ra & s0~s11 of current execution
    20    sd ra, 0(a0)
    21    .set n, 0
    22    .rept 12
    23        SAVE_SN %n
    24        .set n, n + 1
    25    .endr
    26    # restore ra & s0~s11 of next execution
    27    ld ra, 0(a1)
    28    .set n, 0
    29    .rept 12
    30        LOAD_SN %n
    31        .set n, n + 1
    32    .endr
    33    # restore kernel stack of next task
    34    ld sp, 8(a1)
    35    ret

它的两个参数分别是当前和即将被切换到的 Trap 控制流的 `task_cx_ptr` ，从 RISC-V 调用规范可知，它们分别通过寄存器 `a0/a1` 传入。

内核先把 `current_task_cx_ptr` 中包含的寄存器值逐个保存，再把 `next_task_cx_ptr` 中包含的寄存器值逐个恢复。

`TaskContext` 里包含的寄存器有：

    1// os/src/task/context.rs
    2#[repr(C)]
    3pub struct TaskContext {
    4    ra: usize,
    5    sp: usize,
    6    s: [usize; 12],
    7}

`s0~s11` 是被调用者保存寄存器， `__switch` 是用汇编编写的，编译器不会帮我们处理这些寄存器。 保存 `ra` 很重要，它记录了 `__switch` 函数返回之后应该跳转到哪里继续执行。

我们将这段汇编代码 `__switch` 解释为一个 Rust 函数：

    1// os/src/task/switch.rs
    2
    3core::arch::global_asm!(include_str!("switch.S"));
    4
    5extern "C" {
    6    pub fn __switch(
    7        current_task_cx_ptr: *mut TaskContext,
    8        next_task_cx_ptr: *const TaskContext);
    9}

我们会调用该函数来完成切换功能，而不是直接跳转到符号 `__switch` 的地址。 因此在调用前后，编译器会帮我们保存和恢复调用者保存寄存器。