开源操作系统训练营记录
caozhanhao
2024-11-07 0

前言

刚开学就被学长推荐做rCore, 一开始是参加了校内工作室举办的光点计划Ⅱ,做到后面还剩下些时间就来参加了这个训练营,确实学到了很多东西,感觉这几个月是技术提升最快的一段时间。

🦀 第一阶段 - Rust

由于我之前有一点 C++ 基础,这一阶段的入门没有那么困难。Rust 中相当多的概念是与 C++ 互通的,如 RAII, move等等。在这一阶段了解到了 Rust 很多优秀的方面,比如:

  • borrow checker (带来了更好的安全性,但是也提升了学习曲线)
  • macro (过程宏很强大)
  • cargo (太方便了,包管理比 C++ 完善多了)
  • rustdoc (标准化了第三方库的文档管理,不管是写文档还是看文档都舒服了很多)

相关资料

学习中确实遇到了很多困惑,以下是一些资料的整理

All

Move

Rust 的 Move by default 有点像带GC的语言,与 C++ 有较大区别。

这几篇虽然主要是讲 C++ 的,但是其语义与 Rust 的 Move 很类似,文章中也有与 Rust 的对比

Slice

Fn/FnMut/FnOnce Trait

Rust 的闭包涉及到了一些所有权的转移问题,所以有一些特别的 Trait 需要注意,初学时有些迷糊。

Misc

这几篇涉及一些对个别 Rust 语法的分析,其中最后一篇是我写的


😋 第二阶段 - rCore

实验环境配置

本文实验环境为 Manjaro Linux x86_64 6.9.12-3-MANJARO

Qemu 7.0.0

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_filterbpf_program__set_type(rss_bpf_ctx->progs.tun_rss_steering_prog, BPF_PROG_TYPE_SOCKET_FILTER);
屏幕截图_20241101_091503.png

GDB

首先是确认各项目(如 os, usereasy-fs) 的 Cargo.toml 中包含如下配置:

[profile.release]
debug = true

这一步如果没做好会导致后面GDB调试的时候断点不生效,十分重要。
关于 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 相关的功能
sstatusSPP 等字段给出 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.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.png
kernel-as-low.png
另外第四章的lab可以实现一个辅助函数copy_to_app,用来从内核地址空间复制数据到应用地址空间,这样本章和后面的lab都会方便很多。

进程

这一章与前面的任务切换有些类似,理解前面的内容对这一章有很大帮助。其中进程的调度算法比较复杂,也直接影响操作系统的性能。

文件系统

这一章代码量比较大,新概念也很多,感觉难度仅次于第四章。但是好在这里的代码几乎不涉及汇编等与 Rust 的交互,代码逻辑上与平时的编程较为相似,相对来说更容易理解一些。把握easy-fs的五个层次是关键:

  • 磁盘块设备接口层
  • 块缓存层
  • 磁盘数据结构层
  • 磁盘块管理器层
  • 索引节点层

另外最近校内有一个工作室的招新题涉及到了这一块,于是我用Rust实现了一个简单的虚拟文件系统。功能很简陋,很多特性都没有支持,但是做完后感觉对文件系统这一块的理解更加深入了。

进程间通信

这一章较前面简单一些,主要涉及管道、信号之类的方法,同时在练习中也了解了邮箱这种方式。这部分内容感觉与 Rust 中的mpsc有些相似。

并发

这部分主要是锁,信号量与条件变量的实现。用户态锁的实现比较有意思,特别是 Peterson 算法比较绕。
本章lab的死锁检测比较难,看题目有些摸不着头脑。

首先要辨清 Available, AllocationNeed 分别对应着什么。

  • 可利用资源向量 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。

相关资料

评论 0
没有评论
评论已关闭
发表评论
评论 取消回复
Copyright © 2024 mkfs