Post

2022年开源操作系统训练营学习笔记

之前一直就想完完整整地做一次操作系统的Lab,但值得学习的课程资料实在是太多了,经典的xv6,modern的OSTEP,包括IPADS的ChCore…不过借着清华这样一次开源操作系统训练营的机会,就从rCore开始吧,这篇博客用来记录我在训练营中的笔记

7.1 Day1

今天仔细看了本次训练营的大概内容1,主要关注workload,目前看来代码量不是特别大,每天做一点的话应该是能完成的,关键在于要多提问题并记录在issue中

  • 在学习实践过程记录表上登记自己每日/周学习记录情况的repo网址,并在这个repo上记录每日/周学习记录情况 (成绩分数:20%)
  • 在第一阶段学习issues上的提问和回答问题情况,在第一阶段OS学习项目 、 rCore Tutorial v3的详细实验指导内容 上的Pull Request提交情况(代码改进、文档改进、文档错误等) (成绩分数:15%)
  • step 0 要求的编程代码的完成情况 (成绩分数:15%)
  • step 2 第一阶段OS学习项目的5个实验的完成情况 (成绩分数:50%)

7.2 Day2

今天进行了第零章:实验环境配置的工作。其实我在之前就配置过 rCore 相关的 Qemu,riscv64-gnu 工具链和 Rust 开发环境了,所以很流畅地就把 hello world 给跑起来了。不过我也尝试了一下 Github classroom 的新功能,感觉还是挺有意思,可以通过 CI 的方式来提交作业。就是可能受到网速和云主机的影响,运行起来没有那么快,这也是 cloud editing 的一些通病吧。

最后决定还是使用本地开发了,明天开始看看 Rust 系统编程的资料,捡一捡 Rust 语言基础。

7.3 Day3

今天开始step0 自学Rust编程。咋一眼看上去觉得 Rust 的教程太多了,比我之前自己整理的./2022-04-20-Rust-learning-record.md多了不知道哪里去了。不过还是不能一口气吃个大胖子,先按照训练营的要求来做吧。

由于我之前已经做过 rustlings 了, 所以直接开始做 32 Rust Quizes,对照着教程复习一些语法的知识,顺便把 writeup 写在下面。这个练习的难度相对来说比 rustlings 要难,需要理解 rust macro,还包括了一些 corner case。所以也不要给自己太大压力了,先求了解吧。

  1. Rust 宏定义以及参数传递。Rust 宏和 C 的字符串预处理不同,是带有语义类型的替换
  2. 使用 Rustfmt 进行语义分析,闭包,impl trait
  3. const 修饰符用于直接替换,即修改的是临时变量。值的命名空间和变量的命名空间不一样
  4. .. 可以代表通配的元素或者是切片中的 RangeFull,u8 类型的 ASCII 值
  5. 闭包参数的类型推断
  6. 变量覆盖,可以手动借助中间变量推断,赋值语句的值为(),是 zero-sized type
  7. match arm 和 enum 类型的匹配,prelude 提前引入了 Result::OkOption::Some
  8. macro_rules! 中 token 的组织,单个 token 的字符数,例如 ==> 被解析为了 == >
  9. opaque exporession token
  10. dyn Trait
  11. 函数指针之间不要相互比较,延迟生命周期绑定
  12. Drop 的时机和所有者有关
  13. ZST 可以同时有多个可变引用,且他们指向同一位置,但不存在解引用的操作
  14. impl Trait 的作用范围在程序的全局,auto-ref 机制
  15. Trait 在作用时的类型推断
  16. Rust 没有自增和自减的一元运算符,--x = -(-x)
  17. 同16,解析的结果为a-- - --b = a - (-(-(-(-b))))
  18. 调用成员变量的函数指针 VS 调用成员函数
  19. s 的所有权发生了转移,但直到程序括号结束后才被移除,从而调用 Drop
  20. return 表达式需要先被求值再被返回,break-with-value 表达式,returnbreak 的eager consume 行为不同
  21. 判断是 || true 的闭包函数还是 (),break-with-value 表达式
  22. 判断宏参数包含的 token 个数,默认 - 为单独的 token,也可以将整个负数解析为一个 token
  23. 默认调用内置方法和不可变引用
  24. 宏的变量默认使用局部变量,const 代表的不是局部变量,会被覆盖掉
  25. 函数将返回值的所有权转移给调用者,但返回值作为类型,没有接受的变量,所以被 Drop 了两次
  26. Iterator::map 中的闭包是跟随实际的迭代器,延迟调用的
  27. Supertrait,动态分发和静态分发
  28. let _ = Guard 后立马调用 Drop,因为没有 owner 了
  29. 1-tuple 需要显式地表示,但多元 tuple 不需要;intergral literal 默认被推断为 i32
  30. &ZST 的大小不为0,&TRc<T> 都默认实现 Clone trait
  31. 函数签名的匹配顺序和 auto-ref 机制的配合
  32. match arm 中的 if 语句会作用于尽可能多的 arm
  33. (|| .. .method())() = (|| ..).method(),随后再调用返回的闭包

完结证明:

placeholder

7.4 Day4

今天继续昨天的 32 Rust Quizes,不过训练营的要求又改动了,所以还需要在 Github Classroom 里面额外复习一遍 rustlings。

Rust 部分打算跟着张汉东老师的视频复习吧,当作是增加理解了

7.5 Day5

在 codespace 上搭建了 rustlings 的练习环境,跟着张老师的视频。因为之前做过 rustlings 了,所以感觉视频有些无聊,于是便开始自己先按照节奏把前面简单的部分做完了。但张老师的思路是对的,做这样的练习不在于做的多快多正确,而是要理解题目背后的逻辑。之后有时间也可以看看 rustlings 本身的代码。

今天开会后加了老师,助教和一些营员的微信,准备和 ShiLi 同学组个队。此外,还加了几个飞书的群聊,多接受一些 Rust 资讯吧。

7.6 Day6

今天做完了 rustlings 剩下的部分,和第一次做的时候相比来说更加多了些经验,也在不断学习官方的一些写法,重构之前的练习代码,比如 match arm 的妙用。同时,也增加了对于错误处理和类型转换两个主题的理解(之前做的时候可能没仔细看)。本质上说,都是实现 trait,不过是根据 Rust 一些规定的要求来实现 trait,从而能够使用一些内置的函数(比如 fromparse)。在实现 trait 的过程中,我对于编译器报错的阅读能力也增加了。

完结证明(顺便吐槽一下 auto-grading 的问题,只有0/100两种分数):

placeholder

7.7 Day7

今天开始正式做 lab 了。虽然我还没有系统性地看老师给的 RISC-V 的资料,不过还是想着边看边学吧。今天看完了第零章的导读,在我的 MBP 上配置好了环境。由于 Makefile 中有些指令在 MacOS 下的表现不太一样,所以我在云服务器上也配置了一遍环境。读第零章印象最深的三个概念就是:

  • rCore 是按照历史发展各个阶段的 OS 形态来展开,从 libOS 到 batch OS,再到现在通用性的 OS
  • OS 大致提供了三层的抽象
    1. CPU -> 进程
    2. 物理内存 -> 地址空间
    3. 外设 -> 文件
  • 异常控制流的定义有很多种,大概分为中断、陷入、异常。都需要 CPU 切换运行环境,并保存上下文

其他的,对于一些零碎的概念(比如编译目标三元组的表示, RISCV 类型的标记)都更加熟悉了。同时,课程传达的“实践引领原理”的学习顺序也让我产生了一些思考。

从第一章开始,课程开始讲解如何在裸机上编写 Rust 代码,从编译工具链的切换到 !#[no_std] 这样的声明,这都和以前在应用程序层编程的体验不同,也是 Rust 作为系统级语言必备的特性。此外,我复习了 Qemu 和计算机启动的工作原理。还做了一部分手动适配 Qemu 的工作,并简单实践了链接器脚本和 rust-objcopy --strip-all

最后,我把只包含一行汇编,经过裁剪后的内核映像在 Qemu 上跑了起来,并通过 gdb 进行了初步的调试。顺便吐槽一下,brew 提供的 riscv64-unknown-elf-gdb 需要 Python2 的支持,所以我选择直接下载 SiFive 预编译的版本。

7.8 Day8

今天把第一章剩下的部分看完了。理论部分主要复习了 RISCV 的调用约定,以及寄存器的作用,和 x86 的思路类似,不过 更加区分了 caller-saved 和 callee-saved 的寄存器。还学习了 log 的原理,包括如何输出 ANSI 规定的彩色字符和如何使用 RustSBI 提供的服务。

实践部分,跟着示例代码完成了 Qemu 输出第一行彩色文字的过程。还把文档的源码拷贝下来,并自己构建了一份(踩了一个坑,sphinx 对 Python3.10 的支持不太友好),之后就可以本地看文档了。

此外,我还看了一下第二阶段的一些选题,发现基本都是 zCore 的,感觉还是想做和系统安全相关一点的话题,不知道能不能和 penglai 这样的项目结合。

7.9 Day9

今天完成了第一章的课后练习题和实践部分。首先是简单写了几个应用程序,复习了关于系统调用和栈回溯的知识。随后,学习了如何通过 Log crate 来实现终端打印彩色字符,并提供日志输出的等级。

我顺势完成了 lab0-0。其实本身不需要修改什么代码,主要是熟悉构建和测试过程中使用的 Makefile,复习基本语法。也尝试理解了测试的思路,大约是将每次实验的代码都重新放到一个 workspace 目录,重新构建、运行,并保存其输出结果,使用 Python 脚本进行正则匹配。这样的好处是,隔离了每次实验,坏处是增加了测试的开销。此外,user 目录下还包括了许多用于测试内核的 Rust 示例程序,并在每次测试的时候加上了随机数,保证了测试过程的完整性,这是一个很安全的思路。总结一下,第一章主要为内核代码准备了运行环境,并将 RustSBI 提供的服务封装为打印字符串的库,是一个简单的 libOS。

今天还开始了第二章的学习,看到了应用程序是如何使用 ecall 指令来陷入到 S 模式进行系统调用,用户库又是如何封装出类 Linux 的系统调用的,随后还使用 qemu-riscv64 进行了实践。还有一个有意思的是,之前买的 k210 开发板,今天到货了。可以直接上电把 rCore 前几个不需要文件系统的 lab 跑起来,等之后有 sd 卡后还可以试试完整版的 rCore。

但值得注意的一点是,我是跟着 rCore 的文档看的,所以需要注意和训练营的 lab 之间的差距。此外,之后看书看累的时候也可以考虑把课堂视频3看看,感觉更容易集中注意力。

7.10 Day10

今天把 RISCV 的特权级相关资料初步看了一下,主要是一个大会的视频RISC-V手册:一本开源指令集的指南的第十章。但可能自己对于 Architecture 的基础太弱了,导致看起来很费劲。所以还是接着看文档了,这样能更快地熟悉几个重要的 CSR 寄存器。

7.11 Day11

今天把第二章的内容都看完了,并做了课后习题。感觉增加了用户态的支持后,第二章的内容复杂了许多。在学习了其采用静态绑定+动态加载的思路后,还深入理解了系统调用的实现以及上下文保存和恢复中需要注意的顺序问题。

其实以 print!println! 这两个宏为例,不论是系统调用还是请求 RustSBI 的服务,它们的逻辑都是类似的。只不过,之前我们把 RustSBI 当作黑盒,但现在需要理解是如何系统软硬件来达到特权级别的切换,并分发异常的。总结下来,在编写和用户态的接口时,一定要弄清楚代码此刻处于什么特权模式,以及目前的栈布局。此外,利用 sscratch 进行内核栈和用户栈的切换,以及复用 __restore 来实现用户程序初始化这些操作都很精妙,值得细细评味。

课后习题一中有一个 bug 会造成很严重的死循环,大概是由于只有 Rust 代码内部设置了 fp ,而汇编代码没有注意到,所以保存的实际是用户态进行系统调用之前的 fp ,从而导致产生访问非法地址的异常,从而循环调用 panic 中的栈回溯代码。解决的思路:要么就是调整循环结束的标志为用户态 fp 的值,要么就增加对 Exception(LoadFault) 的处理代码。总而言之,手动写汇编时一定要小心。

今天还重新看了看 Makefile 中的命令参数,了解了 tmuxqemu-system 的一些命令参数。此外,还重新复习了用户态程序和内核态程序的链接过程,复习了链接器脚本的语法。

7.12 Day12

因为明天要去上海了,今天做了一些准备的工作,包括和几位国防科技大学的老师聊天了。感觉收获很多,简单记录如下:

  • 王小峰老师:老师主要做的是网络防御这一块儿的工作,而且偏工程一点。虽然我们聊具体的科研问题不多,但是从他的口中验证了我对于现在国内科研形势的了解。他对我的科研选择也做出了肯定,希望我能多到大组里面看一看吧。

  • 王鹏飞老师:老师在17年国外交换的时候就发过一篇 USENIX Security,可谓是新生代的科研力量了。他是做模糊测试比较多,但对于静态分析的了解也很多,尤其是对于 IR 的分析,希望之后能够有机会在寒暑假的时候过来听一听吧。对于目前,他给我的建议有以下这些:
    • 在科研的起步阶段,先做一些轻量级,新颖的work。不要去管老大难问题,比如程序分析的一些理论上难以解决的问题。
    • 从直接修改某个开源项目或者是论文的公开代码入手,短时间熟悉,并做一些改动
    • 在脚踏实地地做工作的同时,也要尽快想好之后的规划。如果走学术界,每年的人才计划和基金都是一环扣一环的,要紧跟节奏才不会掉队。
    • 老师还提醒我,不管是去大组里还是工业界实验室里工作,都要利用好学长学姐和老师的力量,而且时刻要警惕“打白工”(即只为公司创造价值)的行为,把握好自己的初心。
  • 王戟老师:王戟老师是这里面水平最高,资历也最丰富的老师。虽然在去之前我对他的故事和背景没有做功课,但从一进门后的几句对话,就让我感受到了他的功力深厚。比如他问我固件分析是在哪个设备上做,问我是源代码的静态分析还是二进制的静态分析,问我自己对于哪个比较感兴趣。而当我说到 angr,klee 等现代化一点的工具时,他也是如数家珍,而且问了我一些深层次的问题,这也暴露了我并没有深入理解这两个工具以及基于这些工具的论文,只是浮于表面。
    • 总而言之,老师点醒我的是,我的论文阅读的太少了,以至于一直没有开始科研的工作,还是一个“门外汉”。按照老师的思路,我应该先阅读足够多的顶会论文,再挑选出自己感兴趣地一两篇仔细地复现,培养出自己的 taste。
    • 而对于我之前学习方式的问题,老师也利用了后向遍历比前向遍历效率高这一程序分析中的事实让我更加信服自己之前“万事俱备,只欠东风”的科研思路是有很大问题的。

总而言之,和几位老师讨论过后,我得到他们的一个共同的信号就是:摆正心态,给足信心,现在开始科研生活完全来得及。不要浮躁和眼高手低,从基础的工作做起。

7.13 Day13

今天在赶路,只看了第三章的引言部分,了解了第三章要做的内容是实现一个分时多任务的系统,需要改变加载应用程序的方式,并增加主动/被动切换 CPU 控制权的方法。

7.14 Day14

今天看了第三章的主要部分,复习了之前通过 build.rs 和内联汇编加载应用程序的方法。为了能一次性地将应用程序加载到内存,需要硬编码地指定每个应用程序的起始地址,并通过汇编中的符号表,在启动内核时将其装载。此外,为了交出控制权,需要完成两个应用的内核态栈的切换,这一部分还是通过汇编语言实现,并通过 Rust 封装成 unsafe 代码。最后,为了实现抢占式调用,需要引入 mtimemtimecmp 两个寄存器,并使用到了时钟周期等概念,每隔一定的时间设置中断,并在 trap 的处理中加入硬中断的处理例程,即任务的调度算法。而在调度算法方面,目前使用了最简单的 RR 算法,还没有考虑优先级等策略。

7.15 Day15

今天开始做 lab1 了,重新熟悉了一下实验框架,其实只要参考 LAB1 关键字,并复用 os3-ref 的代码即可。需要注意的是对 TaskControlBlock 结构的熟悉,以及在 task/mod.rs 中对于函数的封装调用。至于具体的实现代码,其实逻辑很简单,如果遇到 unsafe 或者是权限的问题,按照编译器的提示修改即可。

7.16 Day16

今天在看程序分析的课程和一篇论文,大概看了第四章的引言部分。

课程资料

  1. https://github.com/LearningOS/rust-based-os-comp2022/blob/main/scheduling.md 

  2. https://learningos.github.io/rust-based-os-comp2022/ 

  3. https://github.com/LearningOS/rust-based-os-comp2022/blob/main/relatedinfo.md 

This post is licensed under CC BY 4.0 by the author.