2022 开源操作系统学习笔记
2022.7.1 网上冲浪时发现了由清华大学、CSDN、毛豆教育等共同举办的2022年开源系统系统训练营活动。异常兴奋!之前很长一段时间内利用碎片时间学习了 Rust 语言,并初步了解了计算机操作系统,因为个人工作是使用C语言、嵌入式实时操作系统进行产品开发,所以对基于 Rust 的计算机操作系统开发非常感兴趣,观看、学习了计算机操作系统的相关的教学视频后,好奇并希望完成相关 Lab,但一直没有找到较好的资源。现在这个训练营提供了完备的学习计划、实验内容,并且有多位老师、助教全程指导,上百位爱好者共同学习、交流,能够系统地学习 Rust 语言和计算机操作系统,堪称完美,希望能坚持到底。
作为从业者,因为工作和家庭的原因,每天没有太多的时间专用于此次训练营活动,基本每天能够早上1.5个小时,晚上小于1个小时。不管是 Rust 还是计算机操作系统,都还只是入门甚至未入门的状态,每天完成的内容非常有限,所以将每日内容记录在 git commit
中,周末总结一篇周记,这是适用于我的学习过程记录方式。
这是一篇 2022 开源操作系统第一阶段的总结,摘取自个人Blog,部分内容单独建了页面做学习笔记,可能无法跳转,[个人Blog](https://thy1037.github.io/rust-based-os-comp2022.html)中有完整内容。
Weekly 1 (7.3 ~ 7.10)
内容
1. 查看活动安排
详细查看了训练营的活动安排,分为两个阶段,现开放第一阶段活动,根据第一阶段的完成度决定是否可以进入第二阶段。第一阶段主要分三步:
- step 0 自学 Rust 编程 (大约7~14天)
- step 1 自学 risc-v 系统结构(大约2~7天)
- step 2 基于Rust语言进行操作系统内核实验–based on qemu (大约14~31天)
2. 完成 rustlings
根据训练营计划的 Step0 描述内容,花了一周的时间完成了 rustlings 。纯粹只是完成了全部的习题,没有深入体会考核意图。
整套练习共 84 小题,个人认为涵盖了 Rust 除生命周期外的所有内容,都是一些浅显的考核内容。
但即使如此,从 error_handling
开始的习题,明显感觉知识掌握不够,需要借助翻阅资料完成。之前的内容是一些基本数据类型、语法定义,与 C语言大同小异,从这里开始就是一些 Rust 特有的数据类型、语法,错误处理是 Rust 的特色之一,C语言中的多用函数返回值判断错误标示,用 goto
方式集中处理错误内容,繁琐且覆盖不全,代码难以阅读,而 Rust 使用特有的 Option
、Result
数据类型来定义和处理错误,enum
于C语言也有非常大的区别,要深入理解并熟练掌握使用这三个数据类型,需要大量的代码积累。
从 conversions
章节开始,部分题目已无法独立完成,需要在网上寻找答案,根据答案进行分析理解。Rust 另一个难点在于类型转换,C语言中可以使用强制类型转换,更是存在 void *
这种极致自由的任意门类型,为了安全,Rust 的数据类型设计的没有那么显然,这也是与编译器作斗争的主要原因,让熟悉 C语言的人概念转换过于艰难,要掌握 Rust 的类型转换需要熟悉每种类型自带的转换方法,如 to_string()
、collect()
等,并理解每种类型的设计需求,类型间的关系、关联。这些也都只能在后续的实验中,通过阅读大量的代码逐步加深理解。
掌握人类语言需要多说多听,掌握计算机语言则需要多写多看,代码量是掌握程度的唯一标准。就像张汉东老师说的,大致的意思是:不存在某一个瞬间就突然学会了 Rust。
计划
根据活动安排,接下来自学 risc-v,从特权模式、汇编语言入手,两天时间先过一遍所有的汇编指令。
Weekly 2 (7.11 ~ 7.17)
内容
1. RSIC-V 汇编指令
观看计算机组成与设计:RISC-V【浙江大学】的P1~P15,并做了相关笔记2.1 汇编指令,视频主要讲解了 RV32 的基础汇编指令、指令编码格式和芯片内部硬件设计原理,纯粹只是心中有数,并没有做任何实战代码练习
2. LAB1
完成了 LAB1,该实验分三个小项目,其中前两个不用作代码修改就可以通过 Github CI 评分,所以一开始并没有特别关注这两个小实验的内容,结果在第三部分卡壳,返回仔细学习前两个小项目的讲义,为了完成 LAB1 的简答题,讲义反阅读多遍。不要欠下技术债,这不仅是 Rust 语言传达的教义,更适用于所有场景。
其中第二个项目,需要修改 overwrite.py
中的一段内容,修改后 Github CI 通过未及时看到,又修改了其它部分代码,导致 CI 一直没能通过,发现时将所做修改复原依旧没能通过,不得以通过 Git 回滚的方式回到通过状态。没弄明白其中原因,特作记录。
计划
下周需要完成 LAB2 和 LAB 3,任务艰巨。
Weekly 3 (7.18 ~ 7.24)
内容
1. 地址空间
这是 CH4-LAB2 的实验内容,这一章的内容是计算机操作系统与嵌入式实时操作系统(FreeRTOS、RT-Thread 这类)的首个也是最大的不同之处:虚拟地址。在不带 mmu
的微处理上无法实现操作系统内核与用户程序、程序与程序间的内存、特权级隔离。一直没有深入了解过内存管理、虚拟地址、特权级,这一章中学习并使用了虚拟地址、特权级切换,对我个人来讲难度很大,收获更大。
这一章中学到了SV39多级页表的相关知识,包括硬件机制和软件实现。页表项的数据结构、虚拟地址与物理地址的映射,以及惰性加载,有了虚拟地址,可以大大降低了应用的程序的开发难度、简化了程序的逻辑,更是加强了整个系统的稳定性,内核对于应用程序不可见、不可修改,应用的稳定性、安全性一定程度上不再能影响到内核,内核有了对应用程序的监控能力。
可以将这部分的内容、机制、思想,简化后应用到RTOS开发中,不管是RTOS内核还是应用程序。
2. 进程及进程管理
这是 CH5-LAB3 的实验内容,引入了进程的概念,不同于RTOS中的任务、线程,RTOS可以通过特殊方法曲线实现动态加载应用,但无法做到地址空间的隔离,没有也无法做到特权级的隔离。
进程可以说是有了地址空间后的自然产物,前几章中的任务概念的改进版。主要是引入 fork
这个非常巧妙的系统调用,将程序扩展方式一下子放大,并且参数传递非常方便,配合 exec
可以很方便的启动新执行流。
增加了新的进程调度机制,stride
算法实现了进程优先级,对比时间片轮询机制更具普适性,但调度机制本身就很复杂,这里简单的借此简单地学习进程管理。
计划
根据活动安排,第一阶段只剩一周时间,还有 LAB4 、LAB5 两个实验没有完成,任务艰巨。
Weekly 4 (7.25 ~ 7.31)
内容
1. 文件系统与 I/O 重定向
这是 CH6-LAB4 的实验内容,引入了文件系统,实现了一个简单的文件系统 easy-fs,用户程序编译后的 elf 数据,可以以文件的形式存储到文件系统而不是内核中,内核运行后可以在新进程中以文件名为参数加载用户程序 elf 数据。
不同于 RTOS,文件系统在 RTOS 中并不是必须的功能模块,因为在 RTOS 中,文件系统主要用将配置、日志或其它数据以文件的形式保存起来,固然可以方便一些,但有多种其它更高效的方式代替,另外由于系统本身和文件系统欠缺对意外情况的处理,导致整体可靠性不高,所以非必要一般不使用文件系统。而计算机操作系统首先要加载非内核程序的 elf 数据,其次特别是类Unix系统一切皆文件的哲学思想,各种I/O也被抽象成了文件,也就意味着必须带有文件系统功能模块,同时也必须有处理各种意外情况的处理方案。可是由于处理意外情况的复杂性,在这篇一操作系统教学为主的课程中,只有文件系统的功能实现,没有应对各种意外情况的处理。
文件系统的实现上,首先是对文件系统的抽象,抽象出文件与目录两个概念,一个用于实际存储数据,一个用于归档;其次是抽象出权限控制,主要是用户、读、写、执行权限;最后抽象出操作方式,将打开、关闭、读写操作作为系统调用,方便操作系统内核进行监控。在这之前会有一个对存储设备的抽象,抽象成块存储设备,让文件系统不依赖某种具体存储设备,使文件系统本身方便移植、使用,也更利于上层编写代码。为了提高读写效率,还会有一个块缓存层,需要实现块缓存全局管理器。
文件系统在磁盘上的布局是基于块索引和位图等数据结构,逻辑目录树结构中的每个文件和目录都对应一个inode(Index Node)。在 easy-fs 磁盘布局中,按照块编号从小到大顺序地分成 5 个不同属性的连续区域:
- 最开始的区域的长度为一个块,其内容是 easy-fs 超级块 (Super Block)。超级块内以魔数的形式提供了文件系统合法性检查功能,同时还可以定位其他连续区域的位置;
- 第二个区域是一个索引节点位图,长度为若干个块。它记录了后面的索引节点区域中有哪些索引节点已经被分配出去使用了,而哪些还尚未被分配出去;
- 第三个区域是索引节点区域,长度为若干个块。其中的每个块都存储了若干个索引节点;
- 第四个区域是一个数据块位图,长度为若干个块。它记录了后面的数据块区域中有哪些数据块已经被分配出去使用了,而哪些还尚未被分配出去;
- 最后的区域则是数据块区域,顾名思义,其中的每一个已经分配出去的块保存了文件或目录中的具体数据内容。
工作上在部分项目中用过嵌入式文件系统,如 FatFS,但一直没能深入了解其具体实现,之后可以结合这一章内容,对比着学习这两种文件系统,加深印象。
2. 并发
这是 CH8-LAB5 的实验内容,引入了线程机制,线程与进程的区别:
- 进程间相互独立(即资源隔离),同一进程的各线程间共享进程的资源(即资源共享);
- 子进程和父进程有不同的地址空间和资源,而多个线程(没有父子关系)则共享同一所属进程的地址空间和资源;
- 每个线程有其自己的执行上下文(线程ID、程序计数器、寄存器集合和执行栈),而进程的执行上下文包括其管理的线程执行上下文和地址空间(故同一进程的线程上下文切换比进程上下文切换要快);
- 线程是一个可调度/分派/执行的实体(线程有就绪、阻塞和运行三种基本执行状态),进程不是可调度/分派/执行的的实体,而是线程的资源容器;
- 进程间通信需要通过IPC机制(如管道等), 属于同一进程的线程间可以共享“即直接读写”进程的数据,但需要同步互斥机制的辅助,以保证数据的一致性。
这一章中除了实现线程,还实现了同步互斥机制:互斥锁、信号量和条件变量。互斥锁用于单个全局资源的独占访问,一定程度上实现对该资源的原子访问,信号量则是用于一类全局资源的独占访问,当这一类资源的个数为1时,则是互斥锁。对于C语言和Rust语言,条件变量没有新的具体实现,只是通过互斥锁在程序逻辑上变相实现了条件变量,互斥锁保护的不再是资源,而是条件判断语句,保证独占某个资源的状态判断,这样可以有效避免死锁和忙等检查。
第一阶段总结
不知不觉一个月过去了,从 7.1 号发现活动,到 7.3 号正式开始,4周的时间,利用早上早起和晚上睡前两个时间段,囫囵吞枣的过了一遍全部内容,这对于 Rust 和计算机操作系统都是初步入门水平的我来讲,难度确实太大了。之前用碎片时间听过南大的计算机操作系统课程,缺乏实践而一直浮于空中,通过这次训练营活动,多少都点落地的感觉,但仍需要后续各种方式的巩固,达到最终扎根的程度。
就因为难度太大,毕竟不管什么学校,计算机操作系统都是一个学期的教学内容,我不认为自己可以一个月仅花业余时间的大半精力就可以全部掌握,所以为了完成部分实验参考了其他同学的实现方案,对我个人来讲,别人的实现方案同样是一种学习资料,但还是更应该独立完成。
最终对为期一个月的第一阶段总结是:收获巨大,需要二刷。
对于后面的计划,争取进入二阶段,找一个自己领域的课题继续深入,继续坚持,尽力完成。
对于再后面的计划,只能等到二刷后,判断是否需要三刷。。。