文件类型

在UNIX系统中,一个重要的设计哲学是一切皆文件,通过将硬件设备、普通文件、管道等多种对象都抽象为文件,OS只将他们作为字节流处理,从而大大简化了OS的设计逻辑。在Rust中,通过泛型编程模型可以方便地实现文件读写接口:

/// OS看到的文件抽象,只关心字节流的读写
pub trait File {
    fn readable(&self) -> bool;
    fn writable(&self) -> bool;
    fn read(&self, buf: &mut [u8]) -> usize;
    fn write(&self, buf: &[u8]) -> usize;
}

为多种对象分别实现File trait,在内核中便可以以统一的方式操作文件。本节简要说明已经实现的文件类型。

标准输入输出

/// 标准输入
pub struct Stdin;
/// 标准输出
pub struct Stdout;

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

impl File for Stdout {
    // 打印到串口(输出到主机屏幕)
    fn write(&self, buf: &[u8]) -> usize {
        if let Ok(str) = core::str::from_utf8(buf) {
            print!("{}", str);
            buf.len()
        } else {
            0
        }
    }
}

标准输入输出结构体没有实体,他们的readwrite接口都是从串口输入输出。在qemu环境下,串口就是我们宿主机上的终端。另外值得注意的是从串口读取时若不能立刻读取,则需要主动调度放弃CPU。

普通文件

impl File for OSInode {
    fn read(&self, buf: &mut [u8]) -> usize {
        let (offset, inode) = (self.offset.get(), self.inode.get());
        let n = inode.read_at(*offset, buf);
        *offset += n;
        n
    }

    fn write(&self, buf: &[u8]) -> usize {
        let (offset, inode) = (self.offset.get(), self.inode.get());
        let n = inode.write_at(*offset, buf);
        *offset += n;
        n
    }
}

对于普通文件,使用easy-fs提供的读写块缓存接口来实现。

管道

/// 管道的一端
pub struct Pipe {
    /// 是否是写端
    writable: bool,
    /// 缓冲区
    buf: Rc<Cell<PipeBuffer>>,
}

/// 管道缓冲区
pub struct PipeBuffer {
    /// 缓冲区
    buf: VecDeque<u8>,
    /// 写端的一个弱引用
    write_end: Weak<Pipe>,
}

管道其实就是一个缓冲区的封装,用于进程间通信。一个进程的输出可以通过管道重定向到另一个进程的输入。具体来说,每个进程创建时都默认打开了三个文件:

  • 标准输入
  • 标准输出
  • 标准错误

重定向时,将需要输出的进程的标准输出文件替换为一个管道文件的写端,输出写入到管道的缓冲器中;将需要输入进程的标准输入文件替换为管道文件的读端即可,从管道缓冲区读出字节。(这个过程实现在user_shell中)

impl File for Pipe {
    /// 从管道的缓冲区读取到buf中
    fn read(&self, buf: &mut [u8]) -> usize {
        assert!(self.readable());
        let mut buf = buf.into_iter();
        let mut n = 0;
        let pipe_buf = self.buf.get();
        loop {
            if pipe_buf.buf.is_empty() {
                // 管道对应的所有写端都已关闭
                if pipe_buf.write_end.upgrade().is_none() {
                    return n;
                }
                // 尚不能读取,当前线程主动放弃CPU
                task::current_yield();
            }
            // 将管道中的字节读出写入buf中
            while let Some(&x) = pipe_buf.buf.front() {
                if let Some(b) = buf.next() {
                    *b = x;
                    pipe_buf.buf.pop_front();
                    n += 1;
                } else {
                    return n;
                }
            }
        }
    }

    /// 拓展管道的缓冲区
    fn write(&self, buf: &[u8]) -> usize {
        assert!(self.writable());
        self.buf.get().buf.extend(buf.iter().copied());
        buf.len()
    }
}