-
Notifications
You must be signed in to change notification settings - Fork 387
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #510 from caozhanhao/master
2024 秋冬季开源操作系统训练营第一二阶段总结-曹展豪
- Loading branch information
Showing
1 changed file
with
158 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,158 @@ | ||
--- | ||
title: 2024 秋冬季开源操作系统训练营第一二阶段总结-曹展豪 | ||
date: 2024-11-07 21:51:53 | ||
tags: | ||
- author: caozhanhao | ||
- repo: https://github.com/LearningOS/2024a-rcore-caozhanhao | ||
--- | ||
|
||
# 前言 | ||
刚开学就被学长推荐做rCore, 一开始是参加了校内工作室举办的[光点计划Ⅱ](https://uestc-os-camp.clovy.top/#/),做到后面还剩下些时间就来参加了这个训练营,确实学到了很多东西,感觉这几个月是技术提升最快的一段时间。 | ||
|
||
<!-- more --> | ||
|
||
详细的博客在[这里](https://mkfs.tech/index.php/archives/30/)。 | ||
|
||
# 🦀 第一阶段 - Rust | ||
由于我之前有一点 C++ 基础,这一阶段的入门没有那么困难。Rust 中相当多的概念是与 C++ 互通的,如 RAII, move等等。在这一阶段了解到了 Rust 很多优秀的方面,比如: | ||
- borrow checker (带来了更好的安全性,但是也提升了学习曲线) | ||
- macro (过程宏很强大) | ||
- cargo (太方便了,包管理比 C++ 完善多了) | ||
- rustdoc (标准化了第三方库的文档管理,不管是写文档还是看文档都舒服了很多) | ||
|
||
## 相关资料 | ||
学习中确实遇到了很多困惑,以下是一些资料的整理 | ||
|
||
### All | ||
- [Rust语言圣经(Rust Course)](https://course.rs/) | ||
- [CPP工程师的Rust迁移之路](https://www.zhihu.com/column/c_1139487758685900800) | ||
- [A half-hour to learn Rust](https://fasterthanli.me/articles/a-half-hour-to-learn-rust) | ||
|
||
### Move | ||
Rust 的 `Move by default` 有点像带GC的语言,与 C++ 有较大区别。 | ||
- [一文串连 Rust 基本概念:ownership/move/borrowing/references/lifetime/consume](https://zhuanlan.zhihu.com/p/659223133) | ||
|
||
这几篇虽然主要是讲 C++ 的,但是其语义与 Rust 的 Move 很类似,文章中也有与 Rust 的对比 | ||
- [什么是move?理解C++ Value categories,move, move in Rust](https://zhuanlan.zhihu.com/p/374392832) | ||
- [C++ 中的 relocate 语义](https://zhuanlan.zhihu.com/p/679782886) | ||
|
||
### Slice | ||
- [Rust 语法辨析:切片和字符串](https://www.ninephoenix.dev/tech/rust-slice/) | ||
|
||
### Fn/FnMut/FnOnce Trait | ||
Rust 的闭包涉及到了一些所有权的转移问题,所以有一些特别的 Trait 需要注意,初学时有些迷糊。 | ||
- [Closures: Anonymous Functions that Capture Their Environment](https://doc.rust-lang.org/book/ch13-01-closures.html?highlight=FnMut#moving-captured-values-out-of-closures-and-the-fn-traits) | ||
- [三种 Fn 特征](https://course.rs/advance/functional-programing/closure.html#%E4%B8%89%E7%A7%8D-fn-%E7%89%B9%E5%BE%81) | ||
- [Rust 中的闭包:function-like types and their traits](https://zhuanlan.zhihu.com/p/471241372) | ||
- [绕弯大王Rust里的Fn/FnMut/FnOnce](https://zhuanlan.zhihu.com/p/686873269) | ||
|
||
### Misc | ||
这几篇涉及一些对个别 Rust 语法的分析,其中最后一篇是我写的 | ||
- [Rust 中的 Magic function params](https://dogdog.wang/blog/rust-magic-function-params]) | ||
- [Does Rust have a way similar to prvalue in C++?](https://users.rust-lang.org/t/does-rust-have-a-way-similar-to-prvalue-in-c/91162) | ||
- [Don't use boxed trait objects](https://bennett.dev/dont-use-boxed-trait-objects-for-struct-internals/) | ||
- [Rust数组长度中使用泛型参数](https://mkfs.tech/index.php/archives/31/) | ||
|
||
--------- | ||
|
||
# 😋 第二阶段 - rCore | ||
## 实验环境配置 | ||
本文实验环境为 `Manjaro Linux x86_64 6.9.12-3-MANJARO` | ||
### Qemu 7.0.0 | ||
```shell | ||
wget https://download.qemu.org/qemu-7.0.0.tar.xz | ||
tar xvJf qemu-7.0.0.tar.xz | ||
cd qemu-7.0.0 | ||
./configure --target-list=riscv64-softmmu,riscv64-linux-user | ||
make -j$(nproc) | ||
``` | ||
qemu 7.0.0 编译的时候可能会报错,解决方案如下 | ||
修改 `ebpf/ebpf_rss.c` 中的 `bpf_program__set_socket_filter` 为 `bpf_program__set_type(rss_bpf_ctx->progs.tun_rss_steering_prog, BPF_PROG_TYPE_SOCKET_FILTER);` | ||
![qemu](https://mkfs.tech/usr/uploads/2024/11/2946624706.png) | ||
### GDB | ||
首先是确认各项目(如 `os`, `user` 和 `easy-fs`) 的 `Cargo.toml` 中包含如下配置: | ||
```toml | ||
[profile.release] | ||
debug = true | ||
``` | ||
这一步如果没做好会导致后面GDB调试的时候断点不生效,十分重要。 | ||
关于 GDB 的配置也可以参考以下资料: | ||
- [rCore (RISC-V):GDB 使用记录](https://zjp-cn.github.io/posts/rcore-gdb/) | ||
|
||
## 主要内容 | ||
### RISC-V 相关资料 | ||
| 寄存器组 | 保存者 | 功能 | | ||
| :-----: | :-----: | :-----:| | ||
| a0~a7 (x10~x17) | 调用者保存 | 用来传递输入参数。其中的 a0 和 a1 还用来保存返回值。| | ||
| t0~t6 (x5~x7,x28~x31) | 调用者保存 | 作为临时寄存器使用,在被调函数中可以随意使用无需保存。 | | ||
| s0~s11 (x8~x9,x18~x27) | 被调用者保存 | 作为临时寄存器使用,被调函数保存后才能在被调函数中使用。 | | ||
|
||
- zero (x0) 恒为零,函数调用不会对它产生影响 | ||
- ra (x1) 被调用者保存。被调用者函数可能也会调用函数,在调用之前就需要修改 ra 使得这次调用能正确返回。因此,每个函数都需要在开头保存 ra 到自己的栈帧中,并在结尾使用 ret 返回之前将其恢复。栈帧是当前执行函数用于存储局部变量和函数返回信息的内存结构。 | ||
- sp (x2) 是被调用者保存的。这个是之后就会提到的栈指针(Stack Pointer)寄存器,它指向下一个将要被存储的栈顶位置。 | ||
- fp (s0),它既可作为s0临时寄存器,也可作为栈帧指针(Frame Pointer)寄存器,表示当前栈帧的起始位置,是一个被调用者保存寄存器。fp 指向的栈帧起始位置 和 sp 指向的栈帧的当前栈顶位置形成了所对应函数栈帧的空间范围。 | ||
|
||
### 特权级的切换 | ||
特权级机制实现了用户态和内核态的隔离,因此这里的代码涉及到汇编与 Rust 代码的交互,硬件与操作系统的交互,与我以前接触的代码差别很大,虽然代码量不大,但难以理解。正确认识`ecall`, `sret`以及`CSR`相关的原子指令是理解这块内容的关键。 | ||
|
||
| CSR 名 | 该 CSR 与 Trap 相关的功能 | | ||
| :-----------: | :-----------: | | ||
| sstatus | SPP 等字段给出 Trap 发生之前 CPU 处在哪个特权级(S/U)等信息 | | ||
| sepc | 当 Trap 是一个异常的时候,记录 Trap 发生之前执行的最后一条指令的地址 | | ||
| scause | 描述 Trap 的原因 | | ||
| stval | 给出 Trap 附加信息 | | ||
| stvec | 控制 Trap 处理代码的入口地址| | ||
|
||
- csrr rd, csr (把控制状态寄存器 csr 的值写入 x[rd]) | ||
- csrrw rd, csr, rs (记控制状态寄存器 csr 中的值为 t。把寄存器 x[rs]的值写入 csr,再把 t 写入 x[rd]。) | ||
|
||
### 任务切换 | ||
任务切换不涉及特权级切换。任务切换同样对应用是透明的,因此也需要保存相关的寄存器。理解`__switch`的四个阶段是关键。 | ||
![switch](https://mkfs.tech/usr/uploads/2024/11/3753070041.png) | ||
- 阶段 [1]:在 Trap 控制流 A 调用 `__switch` 之前,A 的内核栈上只有 Trap 上下文和 Trap 处理函数的调用栈信息,而 B 是之前被切换出去的; | ||
- 阶段 [2]:A 在 A 任务上下文空间在里面保存 CPU 当前的寄存器快照; | ||
- 阶段 [3]:这一步极为关键,读取 `next_task_cx_ptr` 指向的 B 任务上下文,根据 B 任务上下文保存的内容来恢复 `ra` 寄存器、`s0~s11` 寄存器以及 `sp` 寄存器。只有这一步做完后, `__switch` 才能做到一个函数跨两条控制流执行,即 通过换栈也就实现了控制流的切换 。 | ||
- 阶段 [4]:上一步寄存器恢复完成后,可以看到通过恢复 `sp` 寄存器换到了任务 B 的内核栈上,进而实现了控制流的切换。这就是为什么 `__switch` 能做到一个函数跨两条控制流执行。此后,当 CPU 执行 `ret` 汇编伪指令完成 `__switch` 函数返回后,任务 B 可以从调用 `__switch` 的位置继续向下执行。 | ||
|
||
### 地址空间 | ||
第四章我感觉是最难的一章,突然出现了大量的新概念,代码量也激增。理解相关映射方式,跳板,地址空间的布局等是理解地址空间切换的关键。 | ||
![kernel-as-high](https://mkfs.tech/usr/uploads/2024/11/1753598548.png) | ||
![kernel-as-low](https://mkfs.tech/usr/uploads/2024/11/4192899132.png) | ||
另外第四章的lab可以实现一个辅助函数`copy_to_app`,用来从内核地址空间复制数据到应用地址空间,这样本章和后面的lab都会方便很多。 | ||
|
||
### 进程 | ||
这一章与前面的任务切换有些类似,理解前面的内容对这一章有很大帮助。其中进程的调度算法比较复杂,也直接影响操作系统的性能。 | ||
|
||
### 文件系统 | ||
这一章代码量比较大,新概念也很多,感觉难度仅次于第四章。但是好在这里的代码几乎不涉及汇编等与 Rust 的交互,代码逻辑上与平时的编程较为相似,相对来说更容易理解一些。把握`easy-fs`的五个层次是关键: | ||
- 磁盘块设备接口层 | ||
- 块缓存层 | ||
- 磁盘数据结构层 | ||
- 磁盘块管理器层 | ||
- 索引节点层 | ||
|
||
另外最近校内有一个工作室的招新题涉及到了这一块,于是我用Rust实现了一个[简单的虚拟文件系统](https://github.com/caozhanhao/cnfs/)。功能很简陋,很多特性都没有支持,但是做完后感觉对文件系统这一块的理解更加深入了。 | ||
|
||
### 进程间通信 | ||
这一章较前面简单一些,主要涉及管道、信号之类的方法,同时在练习中也了解了邮箱这种方式。这部分内容感觉与 Rust 中的mpsc有些相似。 | ||
|
||
### 并发 | ||
这部分主要是锁,信号量与条件变量的实现。用户态锁的实现比较有意思,特别是 Peterson 算法比较绕。 | ||
本章lab的死锁检测比较难,看题目有些摸不着头脑。 | ||
|
||
首先要辨清 `Available`, `Allocation` 和 `Need` 分别对应着什么。 | ||
- 可利用资源向量 Available :含有 m 个元素的一维数组,每个元素代表可利用的某一类资源的数目,其初值是该类资源的全部可用数目,其值随该类资源的分配和回收而动态地改变。 Available[j] = k,表示第 j 类资源的可用数量为 k。 | ||
- 分配矩阵 Allocation:n * m 矩阵,表示每类资源已分配给每个线程的资源数。 Allocation[i,j] = g,则表示线程 i 当前己分得第 j 类资源的数量为 g。 | ||
- 需求矩阵 Need:n * m 的矩阵,表示每个线程还需要的各类资源数量。 Need[i,j] = d,则表示线程 i 还需要第 j 类资源的数量为 d 。 | ||
|
||
要注意的是这里的资源就是 mutex/semaphore, 第 j 类资源就是 id 为 j 的 mutex/semaphore。 | ||
|
||
## 相关资料 | ||
- [深入理解计算机系统](https://hansimov.gitbook.io/csapp) | ||
- [RISC-V手册](http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf) | ||
|
||
|
||
[1]: https://mkfs.tech/usr/uploads/2024/11/2946624706.png | ||
[2]: https://mkfs.tech/usr/uploads/2024/11/3753070041.png | ||
[3]: https://mkfs.tech/usr/uploads/2024/11/1753598548.png | ||
[4]: https://mkfs.tech/usr/uploads/2024/11/4192899132.png |