跳转到内容

分时多任务概述

本章在多道程序的基础上,我们将进一步实现分时多任务操作系统。

我们在上一章中已经成功实现了多道程序的交替执行,但这种协作式的调度方式存在着无法抢占、任务可能长期独占 CPU 的问题。在本章中,我们将引入时钟中断与时间片轮转调度算法,实现真正的分时多任务系统,保证系统的公平性与响应性。

本章的核心目标包括:

  1. 支持多个用户程序同时驻留内存,以轮转方式调度执行;
  2. 通过时钟中断实现抢占式调度,防止任意一个任务独占 CPU;
  3. 提供 yield 系统调用,允许任务主动让出 CPU(协作式调度);
  4. 维护每个任务的任务控制块(TCB),记录任务状态与上下文。

为了更好地理解本章的改进,我们将分时多任务系统与上一章实现的多道程序进行对比:

特性多道程序(第二章)分时多任务(第三章)
切换时机任务主动 I/O 等待或退出时钟中断强制切换
调度方式协作式 (Cooperative)协作式 + 抢占式 (Preemptive)
任务感知任务不关心调度任务可调用 yield 主动让出
公平性差(任务可能饥饿)好(基于时间片轮转)

第三章区别于第二章的关键在于分时(Time Sharing)。操作系统将 CPU 时间划分为若干个时间片(Time Slice),每个任务轮流获取一个时间片进行执行。当时间片用尽时,操作系统会强制剥夺当前任务的 CPU 使用权,切换到下一个就绪任务。

时间片的长度是调度算法的重要参数。在代码中,我们通过设置时钟中断的触发间隔来定义时间片:

// src/timer.rs
// Set the next timer interrupt
pub fn set_next_trigger() {
    set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
}

在第三章中,我们需要更精细地管理任务的状态。我们在代码中引入了 TaskControlBlock(任务控制块,简称 TCB)结构体,作为任务在内核中的抽象表示。

// src/task/task.rs
pub struct TaskControlBlock {
    /// The task status in it's lifecycle
    pub task_status: TaskStatus,
    /// The task context
    pub task_cx: TaskContext,
}

TCB 将任务的所有相关信息集中管理,包括任务的运行状态(task_status)和任务上下文(task_cx)。

我们将任务的生命周期划分为以下四个阶段:

             init_app_cx()
  ┌─────────────────────────────┐
  │                             ▼
UnInit ────────────────────►  Ready
                              │   ▲
        run_first/next_task() │   │ mark_current_suspended()
                              ▼   │
                             Running

          mark_current_exited() │

                              Exited

TaskStatus 枚举类型定义了任务可能处于的状态:

pub enum TaskStatus {
    UnInit,
    Ready,
    Running,
    Exited,
}
状态含义
UnInit未初始化。任务控制块已分配,但尚未完成初始化。
Ready就绪态。任务已准备好运行,正在等待调度器分配 CPU。
Running运行态。任务正在 CPU 上执行。
Exited退出态。任务已执行完毕或出错退出,不再参与调度。

在我们的分时操作系统中,有两种方式可以触发调度权的交接:协作式调度抢占式调度。这两种机制都是通过操作系统中统一的 suspend_current_and_run_next 接口来实现上下文的切换。

yield 系统调用,通知内核将自己挂起,主动让出 CPU 给下一个处于就绪状态的任务执行。

 ecall
  → trap_handler (UserEnvCall)
    → syscall(SYSCALL_YIELD)
      → sys_yield()
        → suspend_current_and_run_next()
          ├─ mark_current_suspended()   ← 核心挂起逻辑
          └─ run_next_task()

如果某个任务进入死循环或死锁且不主动让出 CPU,系统将面临停滞危险。因此在分时多任务中,操作系统依赖硬件时钟中断(Timer Interrupt)周期性地打断当前任务,强行夺回控制权。

通过设置固定长度的时间片,一旦定时倒计时结束,便会产生一次时钟中断触发 Trap,避免单个程序霸占系统。

时钟中断触发
  → trap_handler (SupervisorTimer)
    → set_next_trigger()              ← 重新设置下一次中断
    → suspend_current_and_run_next()
          ├─ mark_current_suspended()   ← 核心挂起逻辑
          └─ run_next_task()

重点内容如下:

  1. 时钟中断的配置与接收:理解 RISC-V 中断的触发规律,掌握操作系统如何通过定时器夺回 CPU 掌控权。
  2. 核心数据结构抽象 (TaskControlBlock):理解任务控制块的数据形态与生命周期中的状态流转 (UnInit, Ready, Running, Exited)。
  3. 上下文切换机制:解析 __switch 汇编如何保存运行态和栈指针,怎样从前一个任务平滑进入到下一个任务。
  4. 系统核心调度器逻辑:学习基于时间的简单轮转调度策略 (find_next_taskrun_next_task 的设计)。