第一阶段的学习以 Rust 语言 和 rCore 操作系统实验为主,历时一个月。从结果来看,到完整完成第一阶段任务还有一段距离,但是学习过程还是有着不小的收获。
Rust语言学习
跟随本次学习训练营重新学习了Rust语言的基础知识。之前也有尝试学习和使用 Rust,但更多停留在上层应用层面,直到参加本次学习训练营,看到 rCore OS 是如何使用 Rust 来实现操作系统的各种功能之后,才算是真正认识了Rust“零成本抽象”的威力。同时,Rust的安全性、良好的工程性也大大降低了系统软件开发的门槛。
通过完成 Rustlings 编程题目和阅读相关教程,全面学习了 Rust 的基本语法,能够比较流畅地看懂和编写 rCore OS 相关代码,对所有权和Trait也有了更深入的认识。但这是第一次与 Unsafe Rust 打交道,虽然在实验过程中会编写一些 unsafe 代码,也能够明白 unsafe 的“不安全性”,但是并没有过多关注 unsafe 可能引起的问题,这也是在后续的学习中需要加以注意的。
OS实验
Lab0
SBI
学习并理解了 SBI 与 RustSBI:SBI 是 RISC-V 的一种底层规范,RustSBI 是它的一种实现。 操作系统内核与 RustSBI 的关系有点像应用与操作系统内核的关系,后者向前者提供一定的服务。
裸机平台
裸机平台 (bare-metal) :没有 Rust 标准库 std,也不存在任何受 OS 支持的系统调用的目标平台。以裸机平台为目标编译程序,我们要将对标准库 std 的引用换成核心库 core。
特权级机制
实现特权级机制的根本原因是应用程序运行的安全性不可充分信任。特权级机制需要软硬件协同设计。
具备特权级保护检查的新的机器指令:
- ecall: 具有用户态到内核态的执行环境切换能力的函数调用指令
- eret: 具有内核态到用户态的执行环境切换能力的函数返回指令
RISC-V 特权级架构,共定义了4种特权级。除 M 外,其他特权级均按需实现。
特权级切换:
- 重要规则:在 Trap 前的特权级不会高于 Trap 后的特权级。
- 软硬件配合:特权级切换的具体过程一部分由硬件直接完成,另一部分则需要由操作系统来实现。
- 用户栈与内核栈:在正式进入 S 特权级的 Trap 处理之前需要保存原控制流的寄存器状态,需要使用内核栈来保存。这样做主要是为了安全,防止用户看到不该看的内核数据。
Lab1
编程作业:Lab1 编程作业
Lab1 实现分时多任务系统,它能并发地执行多个用户程序,并调度这些程序。
- 一次性加载所有用户程序,减少任务切换开销;
- 支持任务切换机制,保存切换前后程序上下文;
- 支持程序主动放弃处理器,实现 yield 系统调用;
- 以时间片轮转算法调度用户程序,实现资源的时分复用。
多道程序放置与加载
主要解决之前的OS内核在同一时刻只能在内存中放置1个应用,资源利用率不高的问题。
在编译时为每个应用指定不同的.text段起始地址,使它们互不重叠,从而能同时加载到内存中。
任务切换
应用可能会主动让出CPU(yield系统调用),也可能会由内核控制被动让出CPU(时间片结束)。内核要保证任务切换前后的上下文一致。
任务上下文:与应用执行相关的寄存器状态。
任务调度
- 现代的任务调度算法基本都是抢占式的。
- 时间片 (Time Slice) 作为应用连续执行时长的度量单位,一般为毫秒级。
- 通过时钟计数器 mtime 和 CSR mtimecmp 实现时钟中断,当中断触发时执行中断处理函数,暂停当前应用并切换到下一个。
Lab2
编程作业:Lab2 编程作业
提高系统物理内存的动态使用效率,通过隔离应用的物理内存空间保证应用间的安全性,把“有限”物理内存变成“无限”虚拟内存,是操作系统的一系列重要的目标。
静态与动态内存分配
局部变量、全局变量分配,都是静态内存分配,需要在编译期知道大小。而有些场景,变量大小在编译期无法知道,就需要用动态内存分配。
动态内存分配放在堆内存空间。
地址空间
地址空间 某种程度上讲,可以将它看成一块巨大但并不一定真实存在的内存。
在 MMU 的帮助下,应用对自己虚拟地址空间的读写才能被实际转化为对于物理内存的访问。
常见内存管理方式:
- 分段内存管理
- 分页内存管理
一些关键概念:
- 页面 (Page)
- 页帧 (Frame)
- 虚拟页号 (VPN, Virtual Page Number)
- 物理页号 (PPN, Physical Page Number)
- 页表 (Page Table)
- 页表项 (PTE, Page Table Entry)
地址转换流程:当 MMU 进行地址转换的时候,虚拟地址会分为两部分(虚拟页号,页内偏移),MMU首先找到虚拟地址所在虚拟页面的页号,然后查当前应用的页表,根据虚拟页号找到物理页号;最后按照虚拟地址的页内偏移,给物理页号对应的物理页帧的起始地址加上一个偏移量,这就得到了实际访问的物理地址。
多级页表
线性表存在严重的空间浪费,需要“按需分配”,只存储合法的地址映射,从而节省空间。
SV39 分页机制等价于一颗字典树,虚拟页号被分为三级 页索引,是三级页表。
SV39 地址转换过程
物理页帧管理
定义 FrameAllocator
来描述物理页帧管理器的功能:
- alloc
- dealloc
FrameTracker
利用 RAII 机制实现 PageFrame 自动管理。
Lab3 (TODO)
Lab4 (TODO)
Lab5 (TODO)
总结
收获
- 对RISC-V体系、rCore操作系统、Rust语言有了一个整体的了解。
- 重新唤起了对系统软件的学习兴趣。
- 对基于 Github 打造自助式学习社区环境有了一些新的认识。
不足
- 整体的学习时间投入不足,加上 RISC-V 和 OS 知识比较欠缺,导致作业完成度不高。
- 对系统软件的调试工具链还不太熟悉,调试基本靠
println!
打日志观察,浪费了不少时间。 - 没有充分利用训练营讨论交流的环境,仍然以个人学习为主,学习效率偏低。
未来
- 把剩余的lab做完
- 深入了解一些经典文件系统的实现原理,尝试用Rust重写
原文链接 作业还没有完全做完,后面的学习进展会在原文中持续更新。