OS Summer of Code 第一阶段总结报告
首先自报家门,我是在来自上海科技大学信息学院的张18级本科生张驰斌。了解到这一次实习机会是通过大群里面一位同学发的消息。原本自己也对“用Rust在RISC-V机器上面写一个OS”这样新技术的梦幻联动也有过畅想过,加上对计算机底层知识比较感兴趣,没有多想就报名了。
从8月初开始,我从0开始学习了一周的rust语言,完成了相关练习。学习rust的公开repo为:
https://github.com/gystar/HelloRust
之后开始跟着rCoreTutorial的实验指导完成了lab0-lab6,从简单的中断到内存管理,再到进程的创建和调度,最后是系统调用和设备树,将一个小型os内核用rust语言一步步的构建出来,还是挺有成就感的。对于os的原理有了比教科书要深刻得多的理解,感觉从中获益良多,感谢各位老师和助教。学习rCore的公开repo为:
https://github.com/gystar/rCoreTutorial
并且我完成了所有的实验题目。其中,我感觉比较难写的是伙伴分配算法,难点在于:
算是完成了100道题,但感觉多线程,闭包的掌握还太差,函数式编程有点新奇
读了一遍guide后接触项目时有点不知所措,参考了blog再加上自己的思考后慢慢地才感觉有点掌握了实验
完成了lab1,感觉思路上并不难,其中的链接,特区等级的切换等仍需仔细研究。
感觉lab2代码量十分的大,我花了大量时间来理解代码,这是我花费时间最长的实验,仍感觉部分理解不透
最后感觉实验逻辑比较简单,发现位运算的漏洞,该写datalab了。
还有个println调试导致的乌龙,该学gdb了。
lab3感觉比较简单,就是修改了一下idle,spwan的逻辑理解fork和exec后并不难,而且和new几乎一样。
在这一阶段,主要任务就是熟悉 Rust 的语法。
首先是 Rustlings 的练习。作为颇负盛名的 Rust 入门练习,起到的效果也是极佳的,尤其对于我们这种拥有其他语言基础的人。
题目虽然看起来挺多,而如果没有太多不懂的地方的话,每道题的用时都不会很多。只有许多不常用的功能才需要频繁的查阅文档。同时也能提高我们查阅 Rust 文档的能力。
相较而言,Rust 的 API 文档比较好理解。不过其中的 Trait 方面容易被忽略,查阅起来还是要费一番功夫的。
在例子中学会 Rust,也是比较推崇的一个方法,效率比单纯看 API 高效,也比单纯看别人源码来的轻松。这本书适合结合 API 文档使用,尤其是当 API 文档中的例子不够详细的时候。
在做”笨方法学xx“系列时,其实感觉效果很差,里面很多都是鼓励我们直接使用现有的 API 来开发,不够底层。与此同时,C 语言中很多简单的语法,在 Rust 中都需要绕几个弯路才能实现,也带来的不便。
而如果选择 Python 版本的练习题,又会受限于 Python 频繁的”调包“特性,对学习 Rust 其实帮助不是很大。
于是自己选择了一些 Leetcode 上的题来练习手动实现数据结构。由于主要为了学习 Rust 的语法,因此并没有选择困难的算法题,而是选择了两三道普通的中等题来练手。
此外,为了详细了解 Rust 如何凭借 Unsafe 块,达到 C 语言同等的能力,还翻阅了 Rust 中对 LinkedList
的实现,了解了 Unsafe
使用的方式和技巧,并自己实现了一个链表数据结构。
这一阶段主要着重于对 RiscV 指令集的熟悉。主要的材料就是 RiscV 的官方手册和中文手册。
RiscV 的指令大部分都是定长的。这样有利于 CPU 高速读取指令并且修改 PC
的值。
RiscV 的指令都是经过良好设计的。每个指令中的部分,都能获得很好的利用。
RiscV 中关于特权级的部分十分清晰,对设计系统而言起到帮助。关于 U, S, M
三个态的特权级切换和特性与功能,都在手册中得到的良好的阐述。关于页表切换的部分,也有足够长的篇幅进行描述。
主要用于熟悉一下如何通过 Rust 的工具链来生成一个最简易的内核,为之后的步骤作铺垫。
在这一步,我们舍弃了 Rust 的运行时,同时切换到 RiscV 指令集下的工具链。
之后需要通过链接器,将我们的内核起始地址,放到 0x80200000
处,也就是 Qemu 调用我们内核的地址。
通过上述的步骤,最基本的 Playground 就已经搭建好了。
实现了中断处理机制的基本。
这一步主要通过和 RiscV 的中断机制相互结合,实现了内核中的中断机制,并且分别为几个中断事件实现了简单的中断处理程序。
同时,还将上下文的概念引入到内核中,使我们能够备份程序的运行状态,也为之后的实验做基础。
我们实现的中断中,时钟中断是最重要的。时钟中断将允许我们在之后进行线程的切换,也是计算机中并行的一种体现。
这一章的实验题,让我们自己捕获一种新的中断并处理,同时希望我们能够触发这个中断以验证。
建立起动态内存分配的机制,通过分配器来动态分配页帧,使得我们可以在内核运行过程中使用动态增长大小的数据结构了。
在这里,我们将物理地址具象化为一个结构体,为我们看待地址空间提供一种新的方式,也为下一章节引入虚拟内存作铺垫。
这一章的实验题,希望我们亲手实现一个物理页米分配算法或者堆分配的算法。
实现 Sv39 标准的页表机制。通过引入页表,并且开启 RiscV 中的 Sv39 的页表机制,我们实现虚拟内存的概念。其中,我们查询、建立和修改页表以适应我们的需求,让每个线程看到的地址空间几乎都是独立的。
同时完善了内核重映射的过程。为了将我们的内核成功地放置于虚拟空间的高地址处,我们通过临时的页表,使得我们能够将内核的虚拟地址转移到高地址处。
这一部分的实验题,要求我们自己实现一个页面缺页置换算法,并且需要使用到第 5 章的有关文件的内容。
本章节完成线程概念的实现,使运算资源能分到各个线程上去。
我们将线程中的信息统合为 Thread
结构体,并通过修改时钟中断历程,结合线程调度器,做到不同时间运行不同的线程,以分摊运行时间。
为了使我们在时钟中断的时候,内核能够拥有稳定的栈空间,而非使用程序的空间,我们还指定了程序中一段空间作为内核栈。
这一章的实验题,一部分在于通过获取键盘输入(第 5 章的内容)来响应线程中断和线程克隆的事件;另一部分在于自己实现 Stride Scheduling 的调度算法。
这一章配置 Qemu 挂载支持 VirtIO 协议的设备,同时通过现有的 rCore-fs 工具,实现特定文件系统的访问。
这要求我们熟悉设备树的概念,并且了解 VirtIO 的协议。了解我们的系统如何通过 DMA 来获得对应设备的信息。
同时,还要拥有最基本的文件系统知识,能够清晰对文件访问的过程。
这一章相对较于杂糅,不仅实现了 ELF 格式文件的读取,还实现了少数的系统调用,同时还完善了条件变量的机制,以实现更好的线程调度功能。
我们通过调用现成的 ELF 解析器,能够做到读取 ELF 文件的信息,并且将系统的控制权交由 ELF 可执行文件来运行,也就相当于允许用户程序的运行。
同时,为了让用户进程更好的运行,我们还实现了几个系统调用,并提供其接口给用户进程,逐渐建立起了用户的框架。
此外,为了更有效的分配运行时间,还添加了条件变量的机制,使得系统不需要理会睡眠中的线程,更有效的分配运行时间。
这一章的实验题基本上重点在于自己实现几个系统调用,以供用户使用。
我是 losfair / 云海Linvy / 周鹤洋 / Heyang Zhou.
感谢 rCore 组提供参与本次 OS SoC 的机会,感谢张汉东老师的推荐和陈渝老师的指导。