文件类型
在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
}
}
}
标准输入输出结构体没有实体,他们的read
和write
接口都是从串口输入输出。在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()
}
}