11.2. 命令行——输入(信号量)

本章代码对应 commit :1e329bb3c5ad4d58a74837697d5e08ac4904d0bd

用户 shell

// in usr/rust/src/bin/shell.rs

#![no_std]
#![no_main]

#[macro_use]
extern crate rust;

use rust::io::getc;

const LF: u8 = 0x0au8;
const CR: u8 = 0x0du8;

// IMPORTANT: Must define main() like this
#[no_mangle]
pub fn main() -> i32 {
    println!("Rust user shell");
    loop {
        let c = getc();
        match c {
            LF | CR => {
                print!("{}", LF as char);
                print!("{}", CR as char)
            }
            _ => print!("{}", c as char)
        }
    }
}

目前用户程序只做一件事:循环判断是否有字符输入,如果是回车符号或者换行符号,换行;如果是普通字符就直接打印。

getc 只需简单的向 os 发起一个 syscall 即可。执行 make 编译用户程序,剩下的就是 os 的工作了。

处理 sys_read

首先,我们梳理一下处理 sys_read 的流程:

  1. 由用户程序通过 ecall 产生一个 sys_call ,通过传递的参数判断产生了一个 sys_read 请求。

  2. 判断缓冲区中是否有字符。

    • 如果有,则返回该字符。

    • 如果没有,则休眠该程序,等待产生键盘中断。

可以预见我们的 syscall 将越来越多,将 syscall 的实现全堆在一个函数里实在是太不优雅了,所以我们需要简化 interrupt::syscall ,并创建 syscall.rs

每一步看起来都很简单,接下来要做的是通过 crate::fs::stdio::STDIN 中读取缓冲区字符。

缓冲区

所谓缓冲区其实就是一个数组,我们通过识别键盘中断,每产生一次键盘中断,就将按下的字符压入缓冲区中:

对于一次 pop 操作,如果缓冲区中存在字符,则将其返回;如果不存在,则将该线程睡眠(不再加入调度)。

如果产生键盘中断(有字符输入),则唤醒休眠队列中的第一个线程,这里我们通过信号量对这些事务进行管理。为了更清晰的理解这里的 push 和 pop 操作,我们先看一下在 interrupt.rs 中是如何调用他们的:

简单的信号量

这里的 yield_now 会将当前运行的线程状态置为 Status::Sleeping ,在 ThreadPool.retrieve 中会对线程的状态进行判断,如果是 Status::Ready 或者 Status::Running ,则会将其放入调度队列中,等待下一次调度。否则该线程将不再被调度(除非被 Condvar 重新加入)。

执行 make run ,现在我们的命令行已经可以输入文本了,下一个目标是动态执行用户程序。

Last updated

Was this helpful?