本文是参加2025春夏季开源操作系统训练营时对第二阶段文档第7章做的笔记。
文档:https://learningos.cn/rCore-Camp-Guide-2025S
完整版文档:https://rcore-os.cn/rCore-Tutorial-Book-v3
本章将基于文件描述符实现父子进程之间的通信机制——管道。支持进程间的I/O重定向功能,即让一个进程的输出成为另外一个进程的输入。把标准I/O和管道这三种输入输出都统一在 文件 这个抽象之中。
在类UNIX操作系统中,一切皆文件,操作系统可以通过文件描述符在当前进程的文件描述符表中找到某个文件,无需关心文件具体的类型,只要知道它一定实现了 File
Trait 的 read/write
方法即可。通过基于文件接口的标准输入和输出,一个进程可以根据不同的输入产生对应的输出。
管道是一种进程间通信机制,通过直接编程或在shell程序的帮助下轻松地把不同进程的输入和输出对接起来。可以将管道看成一个有一定缓冲区大小的字节队列,读和写两端需要通过不同的文件描述符来访问。
为了实现管道,新增了 sys_pipe
和 sys_close
系统调用,sys_pipe
为当前进程打开一个管道,sys_close
将进程控制块中的文件描述符表对应的一项改为 None
代表它已经空闲;当一个管道的所有读端文件/写端文件都被关闭之后,管道占用的资源会被回收。
管道的一端被抽象为 Pipe
:
1 | // os/src/fs/pipe.rs |
PipeRingBuffer 即管道的本体,是一个循环字节队列:
1 | pub struct PipeRingBuffer { |
对管道的读写通过对这个循环队列操作即可实现。
使用 C 语言开发 Linux 应用时,可以使用标准库提供的 argc/argv
来获取命令行参数:
1 | int main(int argc, char* argv[]) { |
为了支持这个功能,需要修改 sys_exec
系统调用,因为命令行参数是 shell 通过 exec 运行子进程实现的。
在shell程序 user_shell
中,一旦接收到一个回车,就会开始执行。但是现在 line
还可能包含一些命令行参数,因此需要将 line
用空格进行分割,然后将处理后的参数压入用户栈,以下为新增的部分:
1 | // os/src/task/task.rs |
args
中的字符串压入到用户栈中,在用户栈上预留空间之后逐字节进行复制。user_sp
以 8 字节对齐。a0/a1
寄存器,让 a0
表示命令行参数的个数,a1
表示参数的起始地址。应用第一次进入用户态的时候,放在 Trap 上下文 a0/a1 两个寄存器中的内容可以被用户库中的入口函数 _start 以参数的形式接受。
I/O 重定向很好解决,修改对应的文件描述符即可,新增了一个系统调用 sys_dup
,用于将进程中一个已经打开的文件复制一份,分配到一个新的文件描述符中。在shell程序 user_shell
分割命令行参数的时候,检查是否存在 <
或 >
,如果存在的话记录匹配到的输入文件名或输出文件名到字符串 input
或 output
中,然后在 folk 之后再替换对应的文件。