任务调度

NUDT-OS是分时多任务系统,目前的实现采用简单的时间片轮转调度算法,用户线程有两种时机将会被调度:(1)时间片用完,由内核中断处理程序调度 (2)线程主动放弃CPU,这种情况发生在线程运行结束或者阻塞资源时。

线程是CPU调度的最小单位,NUDT-OS中存在用户线程和内核线程两种线程,内核线程为用户线程提供服务,理论上应该具有更高的优先级。内核必须统一调度内核线程和用户线程两种线程。目前采用的调度算法是当任意内核服务线程需要提供服务时,都优先执行内核线程,所有内核线程空闲时,执行用户线程。

这一节首先介绍线程上下文切换的代码片段,再给出几个典型线程调度时机。

一、调度代码片段

/// 执行用户态程序
pub fn start_user_loop() {
    loop {
        if has_ready_kthread() {
            // 有内核线程,调度其它内核线程
            yield_current_kthread();
            continue;
        }
        // 调度用户线程
        if set_current_thread() {
            // 运行当前线程
            run_current_thread();
            // 清理当前线程
            clear_current_thread();
            continue;
        }
    }
}

主内核线程统一调度内核线程和用户线程,当任意内核服务线程需要提供服务时,都优先执行内核线程,所有内核线程空闲时,执行用户线程。

.text
.global context_switch
context_switch: # (cur: &mut Context, nxt: &Context)
  # Save cur's registers
  mov rax, [rsp] # return address
  mov [rdi + 56], rax # 56 = offsetof(Context, rip)
  mov [rdi + 0], rsp
  mov [rdi + 8], rbx
  mov [rdi + 16], rbp
  mov [rdi + 24], r12
  mov [rdi + 32], r13
  mov [rdi + 40], r14
  mov [rdi + 48], r15
  # Restore nxt's registers
  mov rsp, [rsi + 0]
  mov rbx, [rsi + 8]
  mov rbp, [rsi + 16]
  mov r12, [rsi + 24]
  mov r13, [rsi + 32]
  mov r14, [rsi + 40]
  mov r15, [rsi + 48]
  mov rax, [rsi + 56] # restore return address
  mov [rsp], rax # for stack balance, must use mov instead of push
  ret

切换内核线程或用户线程时,都将线程的现场保存在自己的Context结构中,取出要切换线程的Context恢复寄存器跳转到响应指令地址去执行。

二、几个调度时机

2.1 时钟中断时调度

#[no_mangle]
pub extern "C" fn trap_handler(f: &'static mut TrapFrame) {
    match f.id {
        ... => {
            ...
        }
        // 时钟中断时调度
        TIMER => {
            pic::ack();
            *pic::TICKS.get() += 1;
            task::current_yield();
        }
        ...
    }
}

采用时间片轮转调度算法,每个时钟中断时调度切换线程。

2.2 阻塞系统资源时主动调度

impl File for Stdin {
    ...

    /// 从串口读取一个字符到buf中
    fn read(&self, buf: &mut [u8]) -> usize {
        loop {
            if let Some(c) = console::receive() {
                buf[0] = c as _;
                return 1;
            } else {
                // 当前不能立即读取,则当前线程主动放弃CPU
                task::current_yield();
            }
        }
    }

当线程阻塞系统资源时(不能立刻得到资源),主动调度放弃CPU。

2.3 线程退出时主动调度

/// 线程退出
pub fn exit(&mut self, exit_code: i32) -> ! {
    ...
    // 释放所有线程资源
    self.exit_code = exit_code;
    self.state = ThreadState::Zombie;
    THREAD_MANAGER.get().resched();
    unreachable!("task exited");
}