# 管理多道程序 - Open-Source-OS-Tutorial-Summer-of-Code-2022 文档

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

# 管理多道程序

而内核为了管理任务，需要维护任务信息，相关内容包括：

- 任务运行状态：未初始化、准备执行、正在执行、已退出

- 任务控制块：维护任务状态和任务上下文

- 任务相关系统调用：程序主动暂停 `sys_yield` 和主动退出 `sys_exit`

## yield 系统调用

上图描述了一种多道程序执行的典型情况。其中横轴为时间线，纵轴为正在执行的实体。 开始时，蓝色应用向外设提交了一个请求，外设随即开始工作， 但是它要一段时间后才能返回结果。蓝色应用于是调用 `sys_yield` 交出 CPU 使用权， 内核让绿色应用继续执行。一段时间后 CPU 切换回蓝色应用，发现外设仍未返回结果， 于是再次 `sys_yield` 。直到第二次切换回蓝色应用，外设才处理完请求，于是蓝色应用终于可以向下执行了。

我们还会遇到很多其他需要等待其完成才能继续向下执行的事件，调用 `sys_yield` 可以避免等待过程造成的资源浪费。

第三章新增系统调用（一）

    /// 功能：应用主动交出 CPU 所有权并切换到其他应用。
    /// 返回值：总是返回 0。
    /// syscall ID：124
    fn sys_yield() -> isize;

用户库对应的实现和封装：

    // user/src/syscall.rs

    pub fn sys_yield() -> isize {
        syscall(SYSCALL_YIELD, [0, 0, 0])
    }

    // user/src/lib.rs
    // yield 是 Rust 的关键字
    pub fn yield_() -> isize { sys_yield() }

下文介绍内核应如何实现该系统调用。

## 任务控制块与任务运行状态

任务运行状态暂包括如下几种：

    1// os/src/task/task.rs
    2
    3#[derive(Copy, Clone, PartialEq)]
    4pub enum TaskStatus {
    5    UnInit, // 未初始化
    6    Ready, // 准备运行
    7    Running, // 正在运行
    8    Exited, // 已退出
    9}

任务状态外和任务上下文一并保存在名为 任务控制块 (Task Control Block) 的数据结构中：

    1// os/src/task/task.rs
    2
    3#[derive(Copy, Clone)]
    4pub struct TaskControlBlock {
    5    pub task_status: TaskStatus,
    6    pub task_cx: TaskContext,
    7}

任务控制块非常重要。在内核中，它就是应用的管理单位。后面的章节我们还会不断向里面添加更多内容。

## 任务管理器

内核需要一个全局的任务管理器来管理这些任务控制块：

    // os/src/task/mod.rs

    pub struct TaskManager {
        num_app: usize,
        inner: UPSafeCell<TaskManagerInner>,
    }

    struct TaskManagerInner {
        tasks: [TaskControlBlock; MAX_APP_NUM],
        current_task: usize,
    }

这里用到了变量与常量分离的编程风格：字段 `num_app` 表示应用数目，它在 `TaskManager` 初始化后将保持不变； 而包裹在 `TaskManagerInner` 内的任务控制块数组 `tasks`，以及正在执行的应用编号 `current_task` 会在执行过程中变化。

初始化 `TaskManager` 的全局实例 `TASK_MANAGER`：

     1// os/src/task/mod.rs
     2
     3lazy_static! {
     4    pub static ref TASK_MANAGER: TaskManager = {
     5        let num_app = get_num_app();
     6        let mut tasks = [TaskControlBlock {
     7            task_cx: TaskContext::zero_init(),
     8            task_status: TaskStatus::UnInit,
     9        }; MAX_APP_NUM];
    10        for (i, t) in tasks.iter_mut().enumerate().take(num_app) {
    11            t.task_cx = TaskContext::goto_restore(init_app_cx(i));
    12            t.task_status = TaskStatus::Ready;
    13        }
    14        TaskManager {
    15            num_app,
    16            inner: unsafe {
    17                UPSafeCell::new(TaskManagerInner {
    18                    tasks,
    19                    current_task: 0,
    20                })
    21            },
    22        }
    23    };
    24}

- 第 5 行：调用 `loader` 子模块提供的 `get_num_app` 接口获取链接到内核的应用总数；

- 第 10~12 行：依次对每个任务控制块进行初始化，将其运行状态设置为 `Ready` ，并在它的内核栈栈顶压入一些初始化 上下文，然后更新它的 `task_cx` 。一些细节我们会稍后介绍。

- 从第 14 行开始：创建 `TaskManager` 实例并返回。

注解

关于 Rust 迭代器语法如 `iter_mut/(a..b)` ，及其方法如 `enumerate/map/find/take`，请参考 Rust 官方文档。

## 实现 sys_yield 和 sys_exit

`sys_yield` 的实现用到了 `task` 子模块提供的 `suspend_current_and_run_next` 接口，这个接口如字面含义，就是暂停当前的应用并切换到下个应用。

    // os/src/syscall/process.rs

    use crate::task::suspend_current_and_run_next;

    pub fn sys_yield() -> isize {
        suspend_current_and_run_next();
        0
    }

`sys_exit` 基于 `task` 子模块提供的 `exit_current_and_run_next` 接口，它的含义是退出当前的应用并切换到下个应用：

    // os/src/syscall/process.rs

    use crate::task::exit_current_and_run_next;

    pub fn sys_exit(exit_code: i32) -> ! {
        println!("[kernel] Application exited with code {}", exit_code);
        exit_current_and_run_next();
        panic!("Unreachable in sys_exit!");
    }

那么 `suspend_current_and_run_next` 和 `exit_current_and_run_next` 各是如何实现的呢？

    // os/src/task/mod.rs

    pub fn suspend_current_and_run_next() {
        TASK_MANAGER.mark_current_suspended();
        TASK_MANAGER.run_next_task();
    }

    pub fn exit_current_and_run_next() {
        TASK_MANAGER.mark_current_exited();
        TASK_MANAGER.run_next_task();
    }

它们都是先修改当前应用的运行状态，然后尝试切换到下一个应用。修改运行状态比较简单，实现如下：

    1// os/src/task/mod.rs
    2
    3impl TaskManager {
    4    fn mark_current_suspended(&self) {
    5        let mut inner = self.inner.exclusive_access();
    6        let current = inner.current_task;
    7        inner.tasks[current].task_status = TaskStatus::Ready;
    8    }
    9}

以 `mark_current_suspended` 为例。首先获得里层 `TaskManagerInner` 的可变引用，然后修改任务控制块数组 `tasks` 中当前任务的状态。

再看 `run_next_task` 的实现：

     1// os/src/task/mod.rs
     2
     3impl TaskManager {
     4    fn run_next_task(&self) {
     5        if let Some(next) = self.find_next_task() {
     6            let mut inner = self.inner.exclusive_access();
     7            let current = inner.current_task;
     8            inner.tasks[next].task_status = TaskStatus::Running;
     9            inner.current_task = next;
    10            let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
    11            let next_task_cx_ptr = &inner.tasks[next].task_cx as *const TaskContext;
    12            drop(inner);
    13            // before this, we should drop local variables that must be dropped manually
    14            unsafe {
    15                __switch(current_task_cx_ptr, next_task_cx_ptr);
    16            }
    17            // go back to user mode
    18        } else {
    19            panic!("All applications completed!");
    20        }
    21    }
    22
    23    fn find_next_task(&self) -> Option<usize> {
    24        let inner = self.inner.exclusive_access();
    25        let current = inner.current_task;
    26        (current + 1..current + self.num_app + 1)
    27            .map(|id| id % self.num_app)
    28            .find(|id| inner.tasks[*id].task_status == TaskStatus::Ready)
    29    }
    30}

`run_next_task` 会调用 `find_next_task` 方法尝试寻找一个运行状态为 `Ready` 的应用并获得其 ID 。 如果找不到， 说明所有应用都执行完了， `find_next_task` 将返回 `None` ，内核 panic 退出。 如果能够找到下一个可运行应用，我们就调用 `__switch` 切换任务。

切换任务之前，我们要手动 drop 掉我们获取到的 `TaskManagerInner` 可变引用。 因为函数还没有返回， `inner` 不会自动销毁。我们只有令 `TASK_MANAGER` 的 `inner` 字段回到未被借用的状态，下次任务切换时才能再借用。

我们可以总结一下应用的运行状态变化图：

## 第一次进入用户态

我们在第二章中介绍过 CPU 第一次从内核态进入用户态的方法，只需在内核栈上压入构造好的 Trap 上下文， 然后 `__restore` 即可。本章要在此基础上做一些扩展。

在初始化任务控制块时，我们是这样做的：

    // os/src/task/mod.rs

    for (i, t) in tasks.iter_mut().enumerate().take(num_app) {
        t.task_cx = TaskContext::goto_restore(init_app_cx(i));
        t.task_status = TaskStatus::Ready;
    }

`init_app_cx` 在 `loader` 子模块中定义，它向内核栈压入了一个 Trap 上下文，并返回压入 Trap 上下文后 `sp` 的值。 这个 Trap 上下文的构造方式与第二章相同。

`goto_restore` 保存传入的 `sp`，并将 `ra` 设置为 `__restore` 的入口地址，构造任务上下文后返回。这样，任务管理器中各个应用的任务上下文就得到了初始化。

    // os/src/task/context.rs

    impl TaskContext {
        pub fn goto_restore(kstack_ptr: usize) -> Self {
            extern "C" { fn __restore(); }
            Self {
                ra: __restore as usize,
                sp: kstack_ptr,
                s: [0; 12],
            }
        }
    }

在 `rust_main` 中我们调用 `task::run_first_task` 来执行第一个应用：

     1// os/src/task/mod.rs
     2
     3fn run_first_task(&self) -> ! {
     4    let mut inner = self.inner.exclusive_access();
     5    let task0 = &mut inner.tasks[0];
     6    task0.task_status = TaskStatus::Running;
     7    let next_task_cx_ptr = &task0.task_cx as *const TaskContext;
     8    drop(inner);
     9    let mut _unused = TaskContext::zero_init();
    10    // before this, we should drop local variables that must be dropped manually
    11    unsafe {
    12        __switch(&mut _unused as *mut TaskContext, next_task_cx_ptr);
    13    }
    14    panic!("unreachable in run_first_task!");
    15}

我们显式声明了一个 `_unused` 变量，并将它的地址作为第一个参数传给 `__switch` ， 声明此变量的意义仅仅是为了避免其他数据被覆盖。

在 `__switch` 中恢复 `sp` 后， `sp` 将指向 `init_app_cx` 构造的 Trap 上下文，后面就回到第二章的情况了。 此外， `__restore` 的实现需要做出变化：它 不再需要 在开头 `mv sp, a0` 了。因为在 `__switch` 之后，`sp` 就已经正确指向了我们需要的 Trap 上下文地址。