This repository has been archived by the owner on Mar 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jeremy
committed
May 28, 2022
1 parent
a8671e8
commit ee20d27
Showing
109 changed files
with
5,100 additions
and
1,382 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,11 @@ | ||
.idea | ||
Cargo.lock | ||
target | ||
os/src/link_app.S | ||
os/src/linker.ld | ||
os/last-* | ||
os/Cargo.lock | ||
user/build | ||
user/target/* | ||
user/.idea/* | ||
user/Cargo.lock | ||
easy-fs/Cargo.lock | ||
easy-fs/target/* | ||
easy-fs-fuse/Cargo.lock | ||
easy-fs-fuse/target/* | ||
tools/ | ||
pushall.sh | ||
Cargo.lock | ||
target | ||
user_c/bin/* | ||
.vscode | ||
.gdb_history | ||
os/src/link_app.S | ||
os/target | ||
simple-fat32/target | ||
simple-fat32/fat32.img | ||
simple-fat32/fat32.img.origin | ||
user_c/bin | ||
os.bin | ||
os.bin.burn | ||
.gdb_history | ||
*/Cargo.lock | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# 项目起源 | ||
|
||
RongOS 为了避免重新“造轮子”,因此在开发前期面临过一个选择,是基于 MIT 6.828课程的 xv6 教学系统,还是基于清华大学的 rCore-Tutorial-v3,对于这一点我们做了很多思考与比较。 | ||
|
||
主要由以下三点原因最终决定了我们选择 **rCore-Tutorial-v3** 作为基石。 | ||
|
||
1. **编程语言的选择** | ||
|
||
xv6 采用的是传统的 c 语言,而 rCore-Tutorial-v3 则是使用新型的 Rust。由于 c 语言因为历史原因导致其在一些方面并没有很好的处理机制,如指针、内存泄漏、并发漏洞等,而 Rust 语言首先具有与 c 语言一样的硬件控制能力,其次它大大强化了安全编程和抽象编程能力。从某种角度上看,新出现的 Rust 语言的核心目标是解决 C 的短板,取代 C 。所以用 Rust 写操作系统内核具有很好的开发和运行体验,这是 rCore-Tutorial-v3 采用 Rust 语言的原因。对于我们来说,出于好奇心与强烈的挑战欲望,我们更喜欢崭新的、带有独特机制的、以及目前被多家顶尖科技公司看好的 Rust 语言。 | ||
|
||
2. **社区支持** | ||
|
||
xv6 book 是一个pdf文件,最新更新时间为 2021年9月6日。rCore-Tutorial-Book-v3 以网站的形式部署在 github pages 上,相比下具有较好的可读性,知识点的寻找更为便捷。并且,rCore-Tutorial-v3(包括 rCore-Tutorial-Book-v3 ) 的更新更为频繁,在我们的项目开发期间,rCore-Tutorial-v3 一直在进行着频繁的小更新,完善功能,修正错误。最为重要的是,rCore-Tutorial-Book-v3 在每一章节下方设定了讨论区,对于不理解的地方可以随时提出,且回复效率极高。通过密切地与其他学习者、开发者进行讨论交流,我们在项目开发途中解决问题的难度大幅下降。 | ||
|
||
3. **自然语言环境** | ||
|
||
诚然,每个合格的计算机学子应该要具备阅读外文文献的能力。但我们对于自身的英语能力仍感到心有余而力不足。对于一个需要从头学到尾、时刻跟踪的项目来说,我们觉得采用母语的教程会大幅提高我们的学习与开发效率。 | ||
|
||
**非常感谢清华大学陈渝老师与吴一凡同学等开发者编写的 rCore-Tutorial-v3!** | ||
|
||
# 系统整体架构 | ||
|
||
<img src="images/system_architecture.png" alt="system_architecture" /> | ||
|
||
借鉴《Operating Systems: three easy pieces》的撰写想法(即虚拟化、并发、持久化),我们将 RongOS 也分成了三个主体部分以及各自的子模块: | ||
|
||
1. 进程管理 | ||
- 进程调度与进程切换 | ||
- 进程的创建与释放 | ||
- 进程资源管理 | ||
- 同步与互斥 | ||
2. 内存管理 | ||
- 虚拟文件系统 | ||
- FAT32 文件系统驱动 | ||
- 管道、设备文件 | ||
- 磁盘缓冲层 | ||
3. 文件系统 | ||
- 内存页面管理 | ||
- 小块内存管理 | ||
- 虚拟地址空间管理 | ||
- 缺页处理机制 | ||
|
||
# 项目特征 | ||
|
||
- Rust语言编写 | ||
- 多核操作系统 | ||
- 支持59条系统调用 | ||
- 高性能优化:内存弱一致性优化、lazy与CoW、文件系统双文件块缓存等优化等机制 | ||
- 信号机制:进程支持进程信号软中断。 | ||
- 支持C语言程序和Rust语言用户程序编写和运行(提供回归测试基础) | ||
- FAT32虚拟文件系统 | ||
- 混合调试工具:Monitor(结合静态宏打印以及动态gdb特性) | ||
- 详细项目文档、开发过程支持以及理解友好型代码构造和注释 | ||
|
||
# 项目文件描述 | ||
|
||
- bootloader: | ||
- doc: | ||
- fat32-fuse: | ||
- os: | ||
- simple-fat32: | ||
- user_c: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
# 虚拟文件系统 | ||
|
||
虚拟文件系统(Virtual File System,VFS)是一个内核软件层,在具体的文件系统之上抽象的一层,用来处理与文件系统相关的所有系统调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统,同时也为不同文件系统的通信提供了媒介。 | ||
|
||
![vfs](images/vfs.png) | ||
|
||
VFS 为了提供对不同底层文件系统的统一接口,需要有一个高度的抽象和建模,这就是 VFS 的核心设计——**统一文件模型**。目前的 Linux 系统的 VFS 都是源于 Unix 家族,因此这里所说的 VFS 对所有 Unix 家族的系统都适用。Unix 家族的 VFS 的文件模型定义了四种对象,这四种对象构建起了统一文件模型。 | ||
|
||
- Superblock:存储文件系统基本的元数据。如文件系统类型、大小、状态,以及其他元数据相关的信息(元元数据) | ||
- Index node(inode):保存一个文件相关的元数据。包括文件的所有者(用户、组)、访问时间、文件类型等,但不包括这个文件的名称。文件和目录均有具体的 inode 对应。 | ||
- Directory entry(dentry):保存了文件(目录)名称和具体的 inode 的对应关系,用来粘合二者,同时可以实现目录与其包含的文件之间的映射关系。另外也作为缓存的对象,缓存最近最常访问的文件或目录,提升系统性能。 | ||
- File:一组逻辑上相关联的数据,被一个进程打开并关联使用。 | ||
|
||
但相较于 Linux,RongOS 可以说是非常简单,因此我们的 VFS 层并没有完全按照 Unix 的文件模型,而是自己设计了一个 OSInode 结构用于进程访问。 | ||
|
||
```rust | ||
pub struct OSInode { | ||
readable: bool, | ||
writable: bool, | ||
inner: Mutex<OSInodeInner>, | ||
} | ||
|
||
pub struct OSInodeInner { | ||
offset: usize, | ||
inode: Arc<VFile>, | ||
} | ||
``` | ||
|
||
我们将读写属性放在外面,将文件实际内容包括文件当前偏移量放在了 OSInodeInner 中,这样做的好处是无需取得互斥锁即可先进行权限判断,可以提高系统整体性能。 | ||
|
||
注意,这里的 VFile 类型是由下一级文件系统提供(即后续会提到的 Simple-FAT32)。 | ||
|
||
为了实现文件系统相关的系统调用,我们为 OSInode 提供了以下接口: | ||
|
||
- new:新建一个 OSInode 对象,其中 inner.inode(即 VFile 对象)由形参传入。 | ||
- delete:删除文件,在文件系统中删除目录项且清空文件数据。 | ||
- read:读取文件内容保存到缓冲区中 | ||
- write:将缓冲区中的数据写入到文件中 | ||
- get_fstat:获取文件状态信息 | ||
- get_dirent:获取目录文件的目录项信息 | ||
|
||
在 inode.rs 文件中,还包含以下两个重要接口: | ||
|
||
- open:根据工作目录、文件路径、打开方式三个参数打开(或创建)一个文件,返回 OSInode 对象的引用 | ||
- chdir:修改进程当前的工作目录 | ||
|
||
# 文件系统初始化 | ||
|
||
在 inode.rs 中,有如下代码: | ||
|
||
```rust | ||
lazy_static! { | ||
pub static ref ROOT_INODE: Arc<VFile> = { | ||
let fat32_manager = FAT32Manager::open(BLOCK_DEVICE.clone()); | ||
Arc::new(create_root_vfile(&fat32_manager)) // 返回根目录 | ||
}; | ||
} | ||
``` | ||
|
||
这里使用到了 Rust 的 lazy_static 宏定义,它的作用是对全局变量初始化进行延迟,即首次使用时才进行初始化。 | ||
|
||
对 ROOT_INODE 的初始化分两步: | ||
|
||
1. 打开文件系统,获得文件系统管理器对象,其中会初始化 FAT32 文件系统。(详见 3-FAT32 ) | ||
2. 创建根目录的 VFile 对象,也即 ROOT_INODE,接下来内核所有的文件系统操作都是以它为基础。 | ||
|
||
# 一切皆是文件 | ||
|
||
在 UNIX 操作系统中,”一切皆文件“ (Everything is a file) 是一种重要的设计思想,这种设计思想继承于 Multics 操作系统的通用性文件的设计理念,并进行了进一步的简化。它将键盘、显示器、以磁盘为代表的存储介质、以串口为代表的通信设备等都抽象成了文件这一概念。 | ||
|
||
RongOS 中体现此思想的是位于 mod.rs 中的 File trait: | ||
|
||
```rust | ||
pub trait File: Send + Sync { | ||
fn readable(&self) -> bool; | ||
fn writable(&self) -> bool; | ||
fn read(&self, buf: UserBuffer) -> usize; | ||
fn write(&self, buf: UserBuffer) -> usize; | ||
fn get_fstat(&self, kstat: &mut Kstat); | ||
fn get_dirent(&self, dirent: &mut Dirent) -> isize; | ||
fn get_name(&self) -> String; | ||
} | ||
``` | ||
|
||
可以认为它定义了一个名叫 “File” 接口规范,只要是实现 File 这一接口的,都可以被拿来当做文件处理。因此,在我们的进程控制块中,对文件描述符表有如下定义: | ||
|
||
```rust | ||
pub struct TaskControlBlockInner { | ||
...... | ||
pub fd_table: Vec<Option<Arc<dyn File + Send + Sync>>>, | ||
...... | ||
} | ||
``` | ||
|
||
即文件描述符表中存储的表项为实现了 File、Send、Sync 这三个接口的类型,而不局限于我们之前定义的 OSInode 。 | ||
|
||
目前同样实现 File 接口的有标准输入/输出与管道,可以查看 pipe.rs 与 stdio.rs 中的代码。 | ||
|
||
**关于 dyn 类型不能转换为具体类型的问题** | ||
|
||
这是一个 Rust 语言特性的问题,由于 trait 的设计想法就是以一种抽象的方式定义共享的行为,因此从一个具体类型变为该 dyn 后就无法再使用自己”独特“的方法,这个问题困扰了我们很久。 | ||
|
||
在早期开发中,我们从文件描述符表中取出一个普通文件,我们理所当然地把它当成 OSInode 来使用,但是 Rust 认为它是 dyn File + Send + Sync 类型,因此不允许我们使用定义在 OSInode 中的方法,这会造成非常大的不便。 | ||
|
||
个人猜测 UltraOS 也遇见过这种问题,他们的做法是设计了一个文件枚举类进行区分处理: | ||
|
||
```rust | ||
// UltraOS's codes | ||
pub enum FileClass { | ||
File (Arc<OSInode>), | ||
Abstr (Arc<dyn File + Send + Sync>), | ||
} | ||
|
||
pub struct FileDescripter{ | ||
cloexec: bool, | ||
pub fclass: FileClass, | ||
} | ||
|
||
pub type FdTable = Vec<Option<FileDescripter>>; | ||
|
||
pub struct TaskControlBlockInner { | ||
······ | ||
pub fd_table: FdTable, | ||
······ | ||
} | ||
``` | ||
|
||
可以看出他们为了解决这个问题,相对于 rCore-Tutorial-v3 新增了很多类型。 | ||
|
||
我们最开始的想法是使用 unsafe 语句,用最原始的裸指针指向那片数据的地址,然后进行强制类型转换,但实施起来并没有那么简单(可能是因为我们对 Rust 的熟练度不够)最后我们放弃了这个方法。(其实这种不安全的做法也是 Rust 不推崇的) | ||
|
||
目前 RongOS 是做出了一些 ”妥协“,我们把为了实现系统调用的文件描述符表中的 OSInode 所需的部分方法(即 get_fstat 与 get_dirent)也定义在了 File trait 中,这会显得它臃肿,但我们觉得也还算合理,毕竟 pipe 文件也可以使用有状态信息。 | ||
|
||
至于 get_name 的设计,是因为我们发现在系统调用中其实只是为了使用 OSInode 所对应的 VFile 对象,而 VFile 提供的 find_vfile_bypath 可以根据文件路径返回 VFile 对象,所以我们先在 OSInode 中实现了 name 方法,然后在 File trait 添加了 get_name,以此完成 dyn File + Send + Sync 到 VFile 的转换。 | ||
|
||
这一做法即保留了”一切皆是文件“的思想,也大幅减少了相关系统调用的代码行数。 | ||
|
||
# 部分系统调用说明 | ||
|
||
**获取文件状态与目录项信息** | ||
|
||
sys_fstat 与 sys_getdents64 两个系统调用都是获取一些信息,两个所需的结构体与方法分别定义在 stat.rs 与 dirent.rs 中,其中使用了 \#[repr(C)] 把结构体按照 C 语言的标准进行调整顺序、大小和对齐,以支持 C 语言程序的使用。 | ||
|
||
另外,位于内存模块中的 UserBuffer 也为这两个系统调用提供了支持,它是用户地址空间中一段缓冲区的抽象,通过它提供的 write 方法,内核可以方便地在用户地址空间进行写入数据。 | ||
|
||
**挂载** | ||
|
||
由于目前系统中对挂载的需求不是很大,因此我们只设计了一个挂载表来记录挂载设备,它的定义如下: | ||
|
||
```rust | ||
lazy_static! { | ||
pub static ref MNT_TABLE: Arc<Mutex<MountTable>> = { | ||
let mnt_table = MountTable { mnt_list: Vec::new() }; | ||
Arc::new(Mutex::new(mnt_table)) | ||
}; | ||
} | ||
``` | ||
|
Oops, something went wrong.