任务调度

线程是CPU调度的最小单位,NUDT-OS中存在用户线程和内核线程两种线程,内核线程为用户线程提供服务,理论上应该具有更高的优先级。

内核必须统一调度内核线程和用户线程两种线程。目前采用的调度算法是当任意内核服务线程需要提供服务时,都优先执行内核线程,所有内核线程空闲时(除了内核主线程),执行用户线程。

NUDT-OS是分时多任务系统,目前的实现采用简单的时间片轮转调度算法,时钟中断时,用尽时间片的用户线程和内核线程都会被轮转。

另外,用户线程阻塞系统调用或内核线程等待其他线程时也会主动出让CPU(通过协程实现)。

这一节给出内核的调度方式

主调度流程

kernel/src/task/scheduler.rs
/// 调度用户线程和内核线程
pub fn main_loop() {
    println!("[Kernel] Starting main loop...");
    loop {
        // 优先运行内核线程
        let kthread = Scheduler::get_first_kthread();
        if kthread.is_some() {
            // 将CPU交给服务线程或执行器
            let kthread = kthread.unwrap();
            let current_kthread = CURRENT_KTHREAD.get().as_ref().unwrap().clone();
            // 修改当前内核线程
            *CURRENT_KTHREAD.get_mut() = Some(kthread.clone());
            // 主线程入队
            KTHREAD_DEQUE.get_mut().push_back(current_kthread.clone());
            current_kthread.switch_to(kthread);
        } else {
            let uthread = Scheduler::get_first_uthread();
            // 运行用户线程
            if uthread.is_some() {
                let uthread = uthread.unwrap();
                // 修改当前线程
                *CURRENT_THREAD.get_mut() = Some(uthread.clone());
                // 持续运行用户线程直到其被挂起
                while uthread.state() == ThreadState::Runnable {
                    uthread.run_until_trap();
                    // 处理中断或系统调用
                    handle_user_trap(uthread.clone(), &uthread.user_context());
                }
                // 此时线程已被挂起
                clear_current_thread();
            }
        }
    }
}

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

我们之前提到run_until_trap()会持续运行用户线程直到发生中断或系统调用,在其返回时,我们手动调用中断或系统调用处理程序。

kernel/src/task/scheduler.rs
/// 中断/系统调用处理函数
pub fn handle_trap(
    tf: Option<&mut TrapFrame>,
    thread: Option<Arc<Thread>>,
    context: Option<&UserContext>,
) {
    // 用户态的中断或系统调用
    ...

    match trap_num {
        ...
        // 时钟中断,轮转用户线程或内核线程
        TIMER => {
            pic::ack();
            *pic::TICKS.get() += 1;
            // 用户时钟
            if let Some(thread) = thread {
                // 时间片轮转
                thread.try_set_state(ThreadState::Suspended);
            // 内核时钟
            } else if let Some(_tf) = tf {
                // 当前内核线程主动调度
                yield_current_kthread();
            } else {
                panic!("Should never happen!");
            }
        }
        _ => {
            ...
        }
    }
}

在中断处理中若线程的时间片用完,则改变其状态为Suspended,系统调用中若阻塞不能立即完成服务,则改变其状态为Waiting,直到协程执行完毕再改变线程状态为Ready。这时main_loop主循环就会调度另一个内核线程或用户线程执行。

kernel/src/task/scheduler.rs
/// 当前内核线程放弃CPU,调度下一个就绪内核线程
pub fn yield_current_kthread() {
    let current_kthread = CURRENT_KTHREAD.get().as_ref().unwrap().clone();
    let kthread = Scheduler::get_first_kthread();
    if let Some(kthread) = kthread {
        KTHREAD_DEQUE.get_mut().push_back(current_kthread.clone());
        // 修改全局变量
        *CURRENT_KTHREAD.get_mut() = Some(kthread.clone());
        current_kthread.switch_to(kthread);
    }
}

yield_current_kthread()从全局内核线程队列中找出第一个需要调度的内核线程并调度其执行。