线程的结束

优质
小牛编辑
130浏览
2023-12-01

线程的结束

现有问题

当内核线程终止时,会发生什么?如果就按目前的实现,我们会发现线程所执行的函数末尾会触发 Exception::InstructionPageFault 而终止,其中访问的的地址 stval = 0

这是因为内核线程在执行完 entry_point 所指向的函数后会返回到 ra 指向的地址,而我们没有为其赋初值(初值为 0)。此时,程序就会尝试跳转到 0x0 地址,而显然它是不存在的。

解决办法

很自然的,我们希望能够让内核线程在结束时触发一个友善的中断(而不是一个看上去像是错误的缺页异常),然后被操作系统释放。我们可能会想到系统调用,但很可惜我们无法使用它,因为系统调用的本质是一个环境调用 ecall,而在内核线程(内核态)中进行的环境调用是用来与 M 态通信的。我们之前实现的 SBI 调用就是使用的 S 态 ecall

因此,我们设计一个折衷的解决办法:内核线程将自己标记为“已结束”,同时触发一个普通的异常 ebreak。此时操作系统观察到线程的标记,便将其终止。

os/src/main.rs

/// 内核线程需要调用这个函数来退出
fn kernel_thread_exit() {
    // 当前线程标记为结束
    PROCESSOR.lock().current_thread().as_ref().inner().dead = true;
    // 制造一个中断来交给操作系统处理
    unsafe { llvm_asm!("ebreak" :::: "volatile") };
}

然后,我们将这个函数作为内核线程的 ra,使得它执行的函数完成后便执行 kernel_thread_exit()

os/src/main.rs

/// 创建一个内核进程
pub fn create_kernel_thread(
    process: Arc<Process>,
    entry_point: usize,
    arguments: Option<&[usize]>,
) -> Arc<Thread> {
    // 创建线程
    let thread = Thread::new(process, entry_point, arguments).unwrap();
    // 设置线程的返回地址为 kernel_thread_exit
    thread.as_ref().inner().context.as_mut().unwrap()
        .set_ra(kernel_thread_exit as usize);
    thread
}