rCore OS 学习总结
本次训练营是 Rust 与操作系统的有机结合。我对 Rust 已经足够熟悉,因此侧重操作系统部分的第二阶段更加吸引我。
从一个裸机程序开始,随着需求的复杂化,我们需要逐步添加各种系统调用的实现,最终完成了一个麻雀虽小五脏俱全的操作系统。
1. 应用程序与基本执行环境
操作系统和应用一样都是软件程序。区别在于,操作系统需要几乎直接与硬件交互,不像普通应用程序可以使用标准库提供的各种功能,毕竟标准库构建在系统调用上,而系统调用又是由操作系统提供的。
在操作系统与硬件之间还有一层 SBI (Supervisor Binary Interface)。在后续的编程实验中,我们直接使用编译好的 RustSBI BootLoader 二进制文件。
RustSBI 规定了 OS 在内存中的位置,那么编译 OS 时需要调整链接器使其生成符合要求的内存布局。
2. 批处理程序
操作系统最主要的作用就是运行应用程序,而且往往是多于一个应用。一种最直接的想法是把操作系统和多个应用打包在一起,输入计算机后,依次执行每一个应用。
我们希望当应用出错时,操作系统可以继续执行剩余的其他任务。由此特权级机制被引入。
3. 多道程序与分时多任务
一个一个运行应用不够灵活,我们希望能够对多任务进行调度。那么必然就需要能够支持任务切换,即保存/加载上下文。
调度则主要有两种情况:一是任务主动让出处理器,sys_yield
;二是通过时钟中断定时触发任务的切换。
4. 地址空间
操作系统和应用全都直接访问物理地址通常是低效的。引入虚拟地址机制,让每个应用都拥有逻辑上连续的大量内存,可以使应用的编写更加灵活。
RISC-V 64 平台采用了 SV39 多级页表机制,通过 satp
CSR 来控制是否启用。
5. 进程及进程管理
此前,所有任务都是操作系统直接管理的。我们很自然的会希望可以由任务来创建子任务,并且可以管理更多物理/虚拟资源。于是引入进程的概念。
理解进程,最核心的就是相关的系统调用:fork
, exec
和 waitpid
。
6. 文件系统与I/O重定向
文件可代表很多种不同类型的I/O 资源。狭义的文件系统可以对持久存储设备 (Persistent Storage) I/O 资源进行管理。
这一节,我们第一次尝试将一组功能从内核中分离出来成为独立的库,交由内核引入使用。
7. 进程间通信
进程间交换数据大大加强了程序的能力。实现进程间通信的主要方式之一是管道。
8. 并发
操作系统通过不断切换任务实现并发,因为进程间资源是相对隔离的,这种并发容易实现但开销较大。
我们想要在进程内,共享资源的情况下实现低开销的并发,这就引入了线程的概念。
一个进程的多个线程共享资源,但需要能互斥地访问资源,避免数据不一致。为此,我们可以用锁、信号量、条件变量等同步原语。
实验感想
rCore 主要目标还是实现一个 Unix-like 的操作系统,编程实验也基本都是实现 Linux 最关键的一些系统调用。
而 Unix/Linux 本就是与 C 语言一体的。这些 C 风格的系统调用接口,用 Rust 实现起来总是很奇怪,有种削足适履的感觉。
我在思考,如果抛弃这些 C 风格的接口,从 Rust 出发重新设计接口,或许可以更好地利用 Rust 的众多优秀特性。