Skip to content

Commit

Permalink
Merge pull request #510 from caozhanhao/master
Browse files Browse the repository at this point in the history
2024 秋冬季开源操作系统训练营第一二阶段总结-曹展豪
  • Loading branch information
hky1999 authored Nov 8, 2024
2 parents 3ee37fa + f79a235 commit af22c4d
Showing 1 changed file with 158 additions and 0 deletions.
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

0 comments on commit af22c4d

Please sign in to comment.