2024春夏季开源操作系统训练营总结报告
第一阶段总结报告
前言
作为一名大二在读生,在听同学说有一门语言很炫酷的时候,就算是面对着巨大的课业压力,我还是选择来看看rust到底是什么样的一门语言。
Rustlings总结
刚刚接触rust语言时,我并没有意识到rust的强大之处,认为它只是一门新的语言罢了。但是当我跟着文档一步步去接触rust中全新的概念时,我才发现这门语言与其他的编程语言有多大的区别。
从rust变量的所有权与借用规则,再到后面的智能指针和生命周期,每一个特性都是那么晦涩难懂。我甚至在刚刚接触这些特性时,认为这些特性就是在束缚我,让我没法自由自在的编程。我甚至真的想过直接给我的所有rust代码套上一个unsafe。
但是没有束缚的自由也确实不是真正的自由。随着学校课程,尤其是操作系统课程的推进,让我知道了rust的所有权、借用规则以及智能指针,都是为了从语言层面提高整个计算机系统的性能,包括但不限于防止内存泄漏、不让指针乱飞、实现共享区互斥访问等。让我不用像写C语言一样,总是要考虑堆上变量有没有free,指针到底是几级指针,到底指的是什么玩意。不得不承认,经过rustlings110道练习的磨炼,我意识到,rust在通过它的特性,让我更自由的编程。
从头开始学习一门语言是不容易的,尤其是学习像rust这样特性贼多的语言。我在这里要感谢一位之前一直被我忽视的一位朋友——编译器。在之前编程时,不论是写C还是写Java,又或者是写Go,我总是认为编译器只不过是在检查我的语句是不是合法罢了。但是在写rust时,我就深深感受到,没有rust编译器,单单靠我一团浆糊的脑子和厚厚的文档,我是学不会rust的,甚至说都难以写出符合rust语法的程序。每次的报错都让我能够更深地了解rust的语言特性,每次的help,都能让我精确无误的修改错误。编译器好像一直在对我说:“嘿,跟着哥,哥带你学rust。”
希望我能够在接下来的阶段乃至之后与rust同行的时间里,跟着rust编译器,不断加深对rust的理解。
嘿,把rust当做母语真的很酷好吗。
第二阶段总结报告
前言
进入到第二阶段的同时,学校其实也开始进入了期末考试的阶段,每天我说的最多的一句话就是:汗流浃背了。但是好在,我还是走完了这一艰难但是有意义的阶段。
1-2 应用程序基本执行环境与批处理系统
虽然第一章和第二章并没有让我动手实践,但是看着文档一步步从搭建最小环境,再到实现自己的print,再到实现riscv特权级的切换。很难想象这是我在两章里面能学到的东西。
3 多道程序与分时多任务
从这里开始,我终于能上手碰碰操作系统了。
这个实验主要是要让我通过rust实现一个任务状态查询的系统调用。我最自然的想法是,任务状态一定是跟任务绑定的,所以我直接在TCB中新增了一个计算系统调用的成员变量。实现下来也是非常自然顺畅的。但是我知道这个方法应该不是最好的方法,毕竟要在TCB中创建一个挺大的数组,而TCB是属于操作系统内核的,并且操作系统中可能有许多的任务,这样就会导致操作系统内核比较臃肿。
4 地址空间
这一章就涉及到了我之前很少接触到的部分——虚拟地址空间。
从sv39多级页表机制到内核与应用的地址空间,这些内容都挺让我头大的。虽然在进入第四章时走了点弯路,编写代码的时候甚至没有涉及到逻辑段。当后来发现其实框架代码中有一些函数接口可以直接调用(如:shrink_to、append_to、insert_framed_area等),我就开始吐槽之前浪费了太多的时间。但是现在想想,这一段小插曲让我对虚拟地址空间有了更深的了解:从分配物理页帧,到建立虚拟地址和物理地址之间的映射,再到用户程序逻辑段的构建。艰难但有意义。
5 进程及进程管理
本章主要实现操作系统中一个重要的系统调用——spawn。此外还涉及到了一种调度算法——stride调度算法
spawn与fork+exec的最大的区别就是,spawn并不需要像fork一样完全复制父进程的地址空间,并依此再创建一个TCB,然后再通过exec将TCB重写为需要执行的用户程序的TCB。它是直接根据新的用户程序创建一个TCB,省去了重写TCB的开销。所以如果要实现一个spawn系统调用,就只需要模仿fork来实现就好了。
而stride调度算法就是在TCB中增加两个成员stride和priority,在不考虑性能的情况下,只需要遍历就绪队列并执行 stride最小的任务即可。但是受于双端队列的限制,就只能从队列中取出一个TCB然后进行比较,如果stride比当前已经选出的TCBstride还大的话就重新将其放回就绪队列,这样可行,但是会导致更大的系统开销。
一些注意点
- 由于系统终止一个进程的时候是根据TCB的Arc指针强引用数量来判断的,所以很多地方就不能让编译器帮我drop变量,而是需要进行显式的变量drop。
6 文件系统与I/O重定向
在这一章的实验中我对操作系统中的另一个重要部分——文件系统有了基本的认识
从最底层的块设备、缓存,再到上层的文件系统以及操作系统相关的系统调用,学习下来我的感觉就是——多且杂,搞不明白为什么要分这么多层。但是现在回想一下这一切都是有意义的,这些分层让文件系统的不同层的代码高度解耦合,提高了文件系统的可移植性。
实验要求实现建立硬链接、释放硬链接以及查询文件状态系统调用。对于建立硬链接,我就按照文档上的提示按部就班为相同的磁盘索引块再创建了一个目录项。但是对于释放硬链接需要考虑的东西就多了:当一个文件在释放当前硬链接之后还存在硬链接的话,就只需要将磁盘上的某个目录项删除;当一个文件在释放当前硬链接之后没有硬链接了,那就需要将其在磁盘上所有的空间回收(虽然有同学说就算没有回收也能过测试用例)。而对于查询文件状态,我就是将需要查询的字段作为成员放在磁盘索引节点中,这样就能实现就算系统断电,文件的状态也不会丢失(由于是存储在磁盘块上而不是在内存中)
一些注意点
- 删除目录项的时候按道理来说不能仅仅是将该目录项清零,而是应该将后面的目录项移动到前面来,并且如果移动后刚好空出了一个磁盘块,还需要去回收该磁盘块。但是在我的代码中仅仅实现到了将后面的目录项移动到前面,之后有时间可以尝试去改进一下。
- 在给磁盘索引节点增加成员的时候一定要减少直接索引的数量以保证一个磁盘块的大小是128字节。
7-8 进程间通信与并发
这两章的实验主要实现了一个重要的算法——死锁检测算法
这个算法其实在学校的课程中学过,是一个非常类似于银行家算法的算法,但是银行家算法是为了避免死锁。
这个算法中我觉得最让我摸不着头脑的就是need矩阵到底应该如何初始化。Available矩阵可以根据剩余的信号量或者锁是否被占用来实现;Allocation可以根据查询当前哪些线程在占据资源来确定。而need呢?显然不能单从信号量和锁的阻塞队列来确定。
从结果来看,其实应该是我的理解错了,我将死锁检测算法和银行家算法混淆了。我之前一直以为死锁检测算法中的need是一个线程在全局角度上对资源的需求量。而实际上need矩阵只是系统在当前状态下各个线程对资源的需求量,在每次需要分配资源时都需要调用死锁检测算法。所以死锁检测算法中的need矩阵是由信号量或者锁的阻塞队列以及当前请求资源的线程决定的。接下来只需要按照算法描述进行编码问题就迎刃而解了。
虽然第三阶段的时间与学校期末考试的时间完美重合,但是从第二阶段的学习中我真正感受到了操作系统的魅力,冲就完了。