第一节 基本概念 - 任务与调度
在现代操作系统中,多任务支持是基本功能。对我们的内核来说,所支撑的应用可以开启多任务,同时执行多条相对独立的逻辑流程;内核内部同样可以开启多任务,并发完成一些维护性的工作。无论在哪一个层面,合理的多任务调度都可以带来整体效率上的提升。
在多任务方面,任务与调度是两个核心的概念。
任务是被调度的对象,它具有独立的工作逻辑。
调度是资源不足时,协调每个请求对资源使用的方法。通常,每个任务都在尽力争取获得更多的计算资源 - 其实就是 CPU 时间,但是 CPU 无论从数量还是算力常常是处于相对不足的状态的,这就需要协调各个任务之间对有限 CPU 资源的分配。协调的好,系统整体效率有保证;协调的不好,系统效率下降甚至卡死。
在 ArceOS 的语境下,任务等价于线程。也就是说,每个任务拥有独立的执行流和独立的栈,但是它们没有独立的地址空间,而是共享唯一的内核地址空间。相应的对于调度,我们也需要更多的去参考线程调度方面的历史经验。
任务的核心构成要素包括:执行流、栈、环境上下文和状态。先来说状态,有四个基本的调度状态:

调度过程中,需要随时跟踪和更新任务的状态,作为对任务有效管理的基础。
下面以一个典型任务的生命周期为例,说明任务各个构成要素分别的职责作用:
上图是一个典型任务在整个生命周期过程中,将会(可能会)遇到的几种情况。
- 任务首次被创建时,status 默认是 Ready 状态,entry 为任务执行的入口,创建单独的由 kstack 指向的栈,还需要预留一块空间作为任务上下文 context,用于在任务被调度出去时,保存该任务的运行环境,以便在调度回来时恢复现场。
- 任务被调度运行,status 改为 Running 状态,有一个特殊的指针 CurrentTask 指向它,表示它就是当前正在被执行的任务。任务一旦启动之后,entry 就不再有用,寄存器 pc 始终指向 CPU 当前正在执行的指令,寄存器 sp 则指向 kstack 栈的栈顶位置。
- 任务在运行过程中可能随时被调度出去,使得 CPU 可以执行其它任务。调度出去的原因有两种:一是它必须等待某种资源可用,此时任务被设置为 Blocked 状态,让出执行权;二是它主动(调用 yield)或被动(时间片耗尽)让出执行权。无论何种原因,当前任务的上下文必须被首先被保存,即保护现场,等到该任务再次被调度时,把上下文再恢复出来,让任务从上次的断点处继续运行。后面我们将会看到,所谓任务的上下文,就是一组维持任务运行状态的必要的寄存器。
- 任务在运行中,还会遇到另外一种涉及运行环境保存与恢复的情况,即触发异常或遇到外部中断时。此时系统应该挂起当前任务,转而去执行异常/中断的例程,处理完成后再回到任务断点处恢复执行。这期间内核处于一个特殊的运行上下文中,我们称为异常中断上下文,它会叠加到当前任务的任务上下文之上。具体来说,当前任务的一组寄存器状态会作为 TrapFrame 被保存在任务的栈上。
- 任务运行完成后,它的某些资源不能被自己回收,所以内核安排了一个内部任务 GcTask,专门负责进行此项工作。
XXX
根据上面的说明,我们先把任务相关的结构定义出来,首先是 Task 和 CurrentTask 定义在 axtask/src/task.rs:
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum TaskState {
Running = 1,
Ready = 2,
Blocked = 3,
Exited = 4,
}
struct TaskStack {
ptr: NonNull<u8>,
layout: Layout,
}
pub struct Task {
entry: Option<*mut dyn FnOnce()>,
state: AtomicU8,
kstack: Option<TaskStack>,
ctx: UnsafeCell<TaskContext>,
}
unsafe impl Send for Task {}
unsafe impl Sync for Task {}
pub struct CurrentTask(ManuallyDrop<AxTaskRef>);
pub fn current() -> CurrentTask {
CurrentTask::get()
}
而 TaskContext 因为与体系结构相关,定义在 axhal/src/riscv64/context.rs 中:
#[repr(C)]
#[derive(Debug, Default)]
pub struct TaskContext {
pub ra: usize, // return address (x1)
pub sp: usize, // stack pointer (x2)
pub s0: usize, // x8-x9
pub s1: usize,
pub s2: usize, // x18-x27
pub s3: usize,
pub s4: usize,
pub s5: usize,
pub s6: usize,
pub s7: usize,
pub s8: usize,
pub s9: usize,
pub s10: usize,
pub s11: usize,
}
XXX