处理文件描述符

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

处理文件描述符

尽管很不像,但是在大多操作系统中,标准输入输出流 stdinstdout 虽然叫做「流」,但它们都有文件的接口。我们同样也会将它们实现成为文件。

但是不用担心,作为文件的许多功能,stdinstdout 都不会支持。我们只需要为其实现最简单的读写接口。

进程打开的文件

操作系统需要为进程维护一个进程打开的文件清单。其中,一定存在的是 stdin stdoutstderr。为了简便,我们只实现 stdinstdout,它们的文件描述符数值分别为 0 和 1。

stdout

输出流最为简单:每当遇到系统调用时,直接将缓冲区中的字符通过 SBI 调用打印出去。

stdin

输入流较为复杂:每当遇到系统调用时,通过中断或轮询方式获取字符:如果有,就进一步获取;如果没有就等待。直到收到约定长度的字符串才返回。

外部中断

对于用户程序而言,外部输入是随时主动读取的数据。但是事实上外部输入通常时间短暂且不会等待,需要操作系统立即处理并缓冲下来,再等待程序进行读取。所以,每一个键盘按键对于操作系统而言都是一次短暂的中断。

而在之前的实验中操作系统不会因为一个按键就崩溃,是因为 OpenSBI 默认会关闭各种外部中断。但是现在我们需要将其打开,来接受按键信息。

os/src/interrupt/handler.rs

/// 初始化中断处理
///
/// 把中断入口 `__interrupt` 写入 `stvec` 中,并且开启中断使能
pub fn init() {
    unsafe {
        extern "C" {
            /// `interrupt.asm` 中的中断入口
            fn __interrupt();
        }
        // 使用 Direct 模式,将中断入口设置为 `__interrupt`
        stvec::write(__interrupt as usize, stvec::TrapMode::Direct);

        // 开启外部中断使能
        sie::set_sext();

        // 在 OpenSBI 中开启外部中断
        *PhysicalAddress(0x0c00_2080).deref_kernel() = 1 << 10;
        // 在 OpenSBI 中开启串口
        *PhysicalAddress(0x1000_0004).deref_kernel() = 0x0bu8;
        *PhysicalAddress(0x1000_0001).deref_kernel() = 0x01u8;
    }
}

这里,我们需要按照 OpenSBI 的接口在指定的地址进行配置。好在这些地址都在文件系统映射的空间内,就不需要再为其单独建立内存映射了。开启中断使能后,任何一个按键都会导致程序进入 unimplemented! 的区域。

实现输入流

输入流则需要配有一个缓冲区,我们可以用 alloc::collections::VecDeque 来实现。在遇到键盘中断时,调用 sbi_call 来获取字符并加入到缓冲区中。当遇到系统调用 sys_read 时,再相应从缓冲区中取出一定数量的字符。

那么,如果遇到了 sys_read 系统调用,而缓冲区并没有数据可以读取,应该如何让线程进行等待,而又不浪费 CPU 资源呢?