0%

After the first stage of the 2024 autumn winter open source operating system training camp, I would like to share my experience and summary with you.

It was the training camp that made me touch the magic programming language – Rust.

In the stage, I learned the basic knowledge of Rust, and met a lot of like-minded students.

Before, besides assembly, I write the documents with only C series languages (C, C++ and C#). I was not able to imagine how the “memory-safety” would be. But after the touching the Rust, I found that the Rust is not only a simple language. Rust can check your code in compile time, considering the using for multi-thread, memory-leak, and the optimization in general.

Below are the impressive places that Rust has left me:

  • unsafe keyword: Like C#, Rust has a unsafe keyword, which is used to mark the code, which cannot be assured by the compiler. This enables us plug in the special code, solving the possible lack for the implementation of the current supply.

  • ownership and lifetime system: Rust has a very special ownership system, which is different from the C series language. It can help us to avoid the memory leak or multi-reference. In other languages, we may forget to consider the mechanism about variable usage.

  • trait: Rust has a very special trait system, which is similar to the interface in C#. It can make the code more flexible and reusable. We can avoid pure-virtual class in C++, which may import v-table making us uncomfortable.

  • default immutable: This encourages me to delare more variable when writing codes, which makes code more readable.

However, there are still a lot I need to learn. In fact, I have not mastered standard C and C++ yet. This let me have a weakness in computer programming – I cannot consider from all sides to output a great code.

In the future, I’d like to study the Embedded-development relatived knowledge of Rust.

Thank you. Thanks to teachers and the people who have helped me.

Dosconio.

2024AD Nov 10, Sun.

第一阶段总结

对Rust这门编程语言早有耳闻,在活动开始之前就学过一遍rust,不过因为用的不多也没有很熟练,第一阶段的Rust练习巩固了基础,我平时是写C/C++的,真正写起来的时候非常强烈的感受到Rust 的设计与C++之间的差异,Rust对安全的要求非常严格,还有就是这个编译器很强大,虽然过不了编译挺烦人的,但编译器又能教你改正错误,也算是Rust的一个魅力了

第二阶段总结

  • ch1~3

    在学习汇编语言的过程中,我深刻体会到了“切换上下文”的概念。通过阅读trap.s和switch.s的代码,我不仅学到了编程技巧,还对操作系统和计算机的深层次工作原理有了更为深刻的理解。

  • ch4

    地址空间的章节对我而言是一大挑战。尽管在理论学习中已经有所接触,但真正深入到代码层面时,我仍然感到些许迷茫。理解MapArea和MemorySet的过程颇费周折,最初我错误地将rCore视为单页表系统,导致对许多操作感到困惑。经过反复推敲和深入学习,我终于克服了这一障碍,这一部分的学习经历让我受益匪浅。

  • ch5 & ch8

    与第四章相比,第五章和第八章的编程题目难度有所降低。这两章引入了进程的概念,并为后续的并发章节做了良好的铺垫。我开始对之前觉得理所当然存在的进程和线程有了更深刻的理解。在Linux系统中,进程和线程只是共享程度不同的任务。相较于理论书籍,代码的直观性让我对这些概念有了更加清晰的认识。第八章中,我花费了一些时间来理解银行家算法,一旦掌握,编程题目便迎刃而解。

  • ch6

    这一章对我来说是整个学习阶段中难度最大的。在理论学习时,我对文件系统章节的理解不够深入,面对文档中逐步引入的抽象概念,我感到十分困惑。在反复阅读文档和代码后,我终于在微信训练营群友的帮助下完成了作业。我希望rCore在未来的讲解中能够更加细致地涵盖这一部分。

  • ch7

    很轻量的章节,为系统引入了管道,实现了进程间的通信,因为没有编程作业,就没上面的章节反复翻阅文档式的去理解各个细节

    感悟

    回顾这一阶段的学习,我感到非常充实。尽管在第六章的学习过程中遇到了不小的挑战,但这段经历对我的成长无疑是宝贵的。无论我能否在接下来的学习中继续坚持,这一阶段所学到的知识和技能都将对我产生长远的影响。

开篇想先说点废话

说实话真的挺感慨的,竟然真的顺利的做到了今天,竟然真的得到了500分。

我是一个小城市长大的孩子,虽然有幸出生于学历较高的家庭,但毕竟环境、观念等受限,我并没有像大城市的孩子一样有机会从小开始学习计算机。准确的说,我连电脑也几乎没有碰过,因为我家的电脑是对我设了密码的,而我的妈妈也比较反对我使用电子产品。

再加上年幼的我鼠目寸光,眼里只有考试成绩,小初高阶段我选择了在每一门不考试的科目,包括计算机课上,写物理数学作业。
2022年8月,我考入大学。因为性格等原因,我报的志愿全部都是工科专业。虽然我很恐惧计算机,认为自己的基础根本不配学,但作为工科专业里的热门专业,我仍然把它排在了志愿前列。

很不巧,我刚好是计算机专业的分数线,但刚好未录取。我去了其他的工科专业。

于是,我的大一,也依然保持着高中的习惯,电脑常年放在抽屉里,认为拿出来占地方。如果在学习那基本就是做数学物理题。

变化发生于我转专业进入计算机大类。其实也许也不算吧,我是网安专业的,在很多学校可能也不算计算机。选择的原因呢,一个是我学校这个专业保研率真的很高,另一个是它看起来没有计科那么需要优秀的代码编写能力。我的必修课程有408四门,有编译原理等计算机常见课程,却连java和python都没有正经学过。不过那都不重要。

转入计算机大类,我就顺理成章的进入了计算机大类的学生群。在和各种计算机类专业的优秀同学的相处中,我竟慢慢的发现我爱上了计算机。

我开始尝试自学,并想要跨专业考研。选好目标院校后,我进入考研群了解相关消息,并在群里看到了训练营的信息。

这就是我与训练营相遇的开端。

当然,那不是2024秋冬季训练营。是春夏季训练营。

2024春夏季训练营,败给了自卑与拖延

前面也提到了,我的计算机基础非常差,大二之前可以说是彻彻底底的零基础。大二刚转专业,又总抱着一种我大一学的c++比他们简单,又没学过好几门专业课的先修课,更加放纵着自己选择逃避。参加训练营时,又以自己年纪尚小、也没怎么学过(刚好是大二第二学期学,当时正在学)操作系统为由纵容自己拖延,直到时间不够为止。第一阶段内容不多,勉强完成。第二阶段,一题都没来得及做。
我的第一次挑战,就这样结束了。

2024秋冬季,我又来了

明年要开始复习考研初试,不会再有时间。我知道,这是我的最后一次机会了。说实话我自身实力并没强多少,但是没有退路的我选择了尽最大可能试一试。这一次,我希望我也可以顺利做到四阶段结束。

关于训练营本身,我的学习与解题过程

进入正题。

  • 第零章、ch1、ch2:
    主要是下载了一些东西,对二阶段有了一点初步的了解,学习了git的分支是什么【终于意识到二阶段仓库不是只有一个.md文件】

  • ch3:
    第一次在内核里写代码,说实话抵触心理强得可怕,依然是还没开始就开始暗示自己:我肯定写不出来的,我肯定看不懂的。
    感谢我没有退路,硬着头皮也得写。历时三天,终于在一个周一的早晨顺利通过。我的代码应该是有很多冗余处的,但我依然很兴奋。因为这真的是我第一次纯靠自己动脑子想、去尝试、去不断地修改bug,写出来的代码。
    以及第一次学会了git如何提交到一个分支上。

  • ch4:虚存!
    可能也不一定是五个lab里最难的一个,但却是我记忆最深的一个。虚存是我上学期操作系统学的最差的地方,多级页表的题我几乎做一道错一道,我很恐惧这里。看书,尝试,发现自己还是不理解,又看书,又尝试…草稿纸足足用了小半本,一个垃圾桶都差点没塞下hh。开始时最不能理解的是sys_get_time和sys_task_info的改写,我一点也想不明白他们哪里使用了地址。研究了好几天才意识到:ti和ts就是指向虚拟地址的指针,所以要转换成物理地址才能保存信息。
    以及,终于意识到最初提供的代码里传入的参数前加下划线是未被使用的意思,所以我不需要使用_ti等作为变量,我使用时可以直接删去下划线hhh

  • ch5、ch6、ch8:
    这几个因为本身纯底层理论的东西学的还行,逐渐也开始敢于尝试不妄自菲薄了,其实也没什么特别大的感悟。就是平均三天一章的边研读文档边尝试着写。
    闭包函数很有意思!但是我还是有点没太摸透,还需要今后再深入学习。
    ch6写sys_fstat时忘了虚存映射,一晚上都在报错time_out,睡前才突然意识到。以后写代码一定要更加注意细节。

感谢

真的非常非常非常感谢开源操作系统训练营能给我这么一次机会去参与学习一门新的语言以及内核相关知识。在这个过程中我不仅加深了对操作系统的理解,也逐渐培养起了自己解决问题,不轻言放弃的习惯。我第一次去将一个大的长期任务分隔成小任务,规划什么时候做完哪些,而不是全部拖延到截止日期再痛苦。也是第一次清晰的意识到:哇原来我也可以自己写出较长的代码!也很感谢群友们回答我的一个个过于基础导致对擅长计算机的人大概有点搞笑的问题。谢谢每一位训练营的创办者、负责人、讲师、助教、同学们。

补充

补充后的内容为2024.11.11二次提交pr时增加。为向给负责拉取pr的老师带来麻烦致歉,以及感慨这段意想不到的小插曲,增加了这一小段。感谢训练营,春夏季训练营是我第一次使用github,秋冬季训练营是我第一次提交pr。我学到了很多关于git的知识,也因为开始频繁打开github,在github上见到了很多好的项目与知识。我一直很好奇git是怎么工作的,企业中团队不同成员又是如何通过git来合作的,都在训练营使用git的过程中得到了解答。我第一次知道github的不同分支,也第一次知道了提pr是什么样的一个流程。也是第一次知道,git add . 真的不能随意使用,会将自动修改而不应该被修改的文件也修改。git很有意思也很有用,希望我也能有一天真的和一群人一起合作开发一个项目。再次感谢训练营,感谢训练营的老师们!

阶段一总结

初次认真学习rust每一个点

通过某些渠道,第一次了解到操作系统训练营,以前我也有用rust写过一些内容,但严格来说这是我的第一次正式学习rust。学习了很多的内容,了解到了
闭包,函数式编程,拓展了我的知识面,我第一次见到结构如此清晰的语言。

所有权的理解

众所周知,rust很贪。所有权是其中的灵魂。我认为,其本质就是“作茧自缚”,减少了许多东西,又为方便开发与调试添加了许多东西。这些设计减少了许
多的错误。所以简单的把所有权当作单线程的脑子就行,不要给它过多的行为。

错误处理中的Option Result

  • Option 用于表达可能存在或者不存在的值,它有两种可能的状态:Some(值) 和 None。当你不确定一个值是否存在时,可以使用 Option 来处理这种情
    况。

  • Result 则用于表达可能的操作成功或失败的结果,它有两种可能的状态:Ok(值) 和 Err(错误)。当你需要处理可能出现错误的情况时,可以使用 Result 来处理这种情况。

令人耳目一新的枚举类型 match

在 Rust 中,枚举类型是一种非常强大的数据结构,它可以用来表达一组相关的值。而使用 match 关键字可以让我们更加灵活地处理枚举类型的值,使得
代码更加清晰易懂。

match 表达式可以用来匹配枚举类型的不同变体,并根据不同的情况执行相应的代码逻辑。这种模式匹配的方式让代码的逻辑结构清晰明了,同时也增强了
代码的可读性和可维护性。

我的收获

通过学习 Rust,我收获了很多。不仅仅是语言本身的特性和语法,更重要的是 Rust 给我带来的编程思维方式的转变。在学习 Rust 的过程中,我更加注
重代码的安全性和可靠性,学会了如何利用 Rust 的各种特性来编写更加健壮的程序。

另外,通过与社区的交流和分享,我还了解到了很多其他开发者的经验和见解,这也让我受益匪浅。总的来说,学习 Rust 是一次非常有意义的经历,我相
信在将来的工作和项目中,我会继续运用 Rust 的知识和思想,为我的编程生涯注入新的活力和动力。

阶段二总结

在rcore的实验课程中,最让我印象深刻的就是虚拟地址部分,由于之前接触的操作系统全部都是nommu类型,直接访问物理地址的概念非常不容易被打破,并且ch4也是一个门槛,需要非常深入的理解rcore的整体代码框架,对虚拟地址的映射,软硬件协同操作,多级页表,地址空间的概念理解了很久,在不断查找资料,不断理解的情况下,才对虚拟地址的概念有了很浅显的理解,把任务完成。

在完成任务的过程中,其实rcore的整体框架非常完善,作业的部分也就是一些扩展功能,整体还是对rcore的实现的理解,在完成任务时,也是对已有实现进行模仿,理解整体框架,之后调用已有函数或自己根据数据结构实现函数,实现功能。

以及最后两个lab难度上升有点大,并且文章 中是对功能的描述居多(很多对于实验有用的点会被淹没在其中)虽然在边做lab边反复查看资料时可以发现,但是对于没有接触过的同学可能很多api看过就没印象了,在后续做lab中也找不到,所以可以适当增加一下强调或者提示,来减少难度增加坡度。

第一阶段:rustlings
1.去入门了一下rust,使用的是Rust语言圣经(Rust Course)
2.通过学习Rust语言圣经,了解了rust的基础语法,因为之前有过C++语言基础,所以学习较为容易
3.无GC,有强大的包管理工具,强大的安全性,以及独具特色的所有权机制
4.学习了rustlings的110道题目,虽然之后大多用不到就全忘记了,但是如果将来从事相关工作,很快就能重新捡起来
5.学习完成rustlings的110道题目之后就开始了之后的第二阶段学习

第二阶段:rCore-Tutorial
1.通过阅读rCore-Tutorial-Book学习到了很多关于操作系统的知识,之前也有过操作系统的学习,只不过都是理论上的,并没有相关实践上的学习,这也是我进入此训练营的原因,想通过训练营督促自己去学习到更多的关于操作系统的知识,以及拥有一个比较独特的项目,去助力就业
2.通过rCore的学习,完成了5个lab,可以说是学习比较辛苦的了,是rCore-Tutorial-Book和rCore-Camp-Guide-2024A两个教程同时跟进,学习完成rCore-Tutorial-Book之后就在相应章节的2024A中完成lab,
(1)在lab1中,我们的系统已经能够支持多个任务分时轮流运行,引入一个新的系统调用 sys_task_info 以获取当前任务的信息。
(2)在lab2中,在引入虚存之后重写内核的 sys_get_time 和 sys_task_info 函数,以及完成mmap 和 munmap 匿名映射两个系统调用函数。
(3)在lab3中,进程创建过程中,fork+execl两个函数高频率连续使用,感觉十分繁琐,我们引入一个spawn函数直接完成两个函数调用的结果,新建子进程,使其执行目标程序。还完成了stride 调度算法的实现,通过修改优先级,使步长不同,导致选取的优先级发生变化。
(4)在lab4中,引入硬链接的概念,让我们完成linkat和unlinkat和fstat三个系统调用,写这个的时候给我写破防了,当时看的代码抑郁了,但是其实没有那么难,很多代码其实和解决问题没有任何关联,这一个lab只能是非常难写,需要对源码有很深的理解,以及需要想到在文件存入fd_table的时候,想办法把文件名字也放入PCB中
(5)在lab5中,完成了死锁检测,这个我的完成过程也是比较抽象,写的时候很痛苦,一直往银行家算法上面想,后面发现need根本不知道啊,经过讨论之后才知道,原来只需要检测死锁就行,比较精妙的一点就是在lock阻塞或睡眠的过程中,就需要更新need数组,而且在死锁检测时候,需要让need+1而不是allocation,比较有意思吧。
3.其实完成了这个比较toy的玩具项目,感觉最后很多东西没有联系起来,如果要是最后把所有lab整合在一起,组合成一个可以展现自己从开始到现在的成果的话会更好一些,不过也是很感谢rCore这个教学,学到了很多操作系统的知识。

1 Stage

初次学习rust ,之前一直以c++作为主语言进行编程,大概了解了rust 的借用机制,而且对操作系统感兴趣,所以开始学习

在学习rust过程中,与c++相对比很多“不适应”

  1. 变量的借用机制以及生命周期的声明让人很水土不服,还在深刻感受
  2. 基础stl库的文档过于繁杂了。
  3. 比 c++ 更原生支持的泛型编程,而且比c++模板可读性更高,debug也相对更容易一些
  4. 总体比 c++ 更约束,c++ 的自由带来的是各种错误和不安全性,rust在这一方面确实做得更加出色(听说性能也与c++相差不多)

2 Stage

lab1

简单的系统调用实现,关键是了解了 os 最初的形状,见识到了如何使用汇编和高级语言一起实现应用,很酷

lab2

rcore 路上的第一道门槛,如果lab1只能算花了我一小会的放松时间,那lab2就相当于一整天。

  1. 先把lab1的实现在lab2中兼容(地址转换有点难受)。
  2. 而后专注于实现 mmap 与 munmap,在实现时,采用直接新建area的方式,删除则删除对应 start area
  3. 但遇到了问题,在校验vpn是否合法时,pte检验一直出现问题,最后在translate to ppn相关函数中做了自己理解的改动

lab3

实现角度看比较简单,但自己尝试了不同的stride位数 以及 bigstride,看到了很多很有意思的现象,包括一直不被调度,包括反转现象

lab4

感觉关键在于硬链接统计方法,实现比较粗暴(实在没时间了。。),有时间可以考虑如何使用抽象的思维跳过 STDOUT STDIN

lab5

考虑银行家算法 和 死锁检测的区别(🤔)

写在前面

我从稍微理解了我的专业之后就一直都很崇拜 Linus,这位堪称传奇的软件工程师在 1991 年 8 月 25 日——他 21 岁时就在网络上发布了 Linux 内核的源代码。现在是 2024 年 4 月 23 日,我也是 21 岁,追逐吗,梦想吗,我也想像他一样写出这样神奇的代码,33 年后的今天,我也要开始了,Linus。

欢迎交流;-)

这次参加训练营,也算是二周目,对很多内容都已经轻车熟路。在上次的训练营之中,我并没有完成三阶段的学习,多少有当时实习没有时间进行分配和对更加复杂的操作系统内核架构没有大概了解的原因。

所以在实习结束之后的暑假,我也没有闲着,主要都在学习 Rust 之前没有接触过的内容还有理解 Linux 操作系统内核上了。成果大概就是把《Rust 程序设计第二版》这本书看完了,加上初探了一下 Rust 的异步框架、对 Linux 0.12 这个老版本的内核代码进行了一些阅读。

希望这次可以成功冲击第四阶段,加油!

背景

在过去的一年时间里,我开始学习 Rust 语言,并完成了 Rust 官方的 rustlings 练习题。这些练习题主要帮助我熟悉了 Rust 的基本语法和特性,如所有权、借用、模式匹配等。通过这些基础知识的积累,我对 Rust 的语法和一些核心概念有了初步理解。

问题与挑战

尽管 rustlings 帮助我打下了基础,但在尝试编写算法和更复杂的数据结构时,还是遇到了不少困难。例如,在实现动态规划、递归和一些复杂数据结构时,感觉自己对于 Rust 的所有权、生命周期、以及内存管理的理解不够深刻。这让我在面对更复杂的算法实现时,仍然感到束手无策。

下一步计划

为了解决上述问题,我计划进一步提升自己的 Rust 算法编程能力。具体来说,我会在 LeetCode 等平台上使用 Rust 进行算法题目的练习,以巩固对语言特性的理解,并适应使用 Rust 实现高效的算法。通过逐步解决不同难度的算法问题,相信自己能更好地掌握 Rust 的特性和应用。

总结

Rust 是一门极具潜力的系统级编程语言,其独特的内存安全机制和高性能特性值得深入学习。尽管当前在编写算法时遇到了一些困难,但相信通过不断的练习,尤其是在 LeetCode 上的算法刷题,我可以进一步提升自己在实际应用中使用 Rust 的能力。

写在前面

我从稍微理解了我的专业之后就一直都很崇拜 Linus,这位堪称传奇的软件工程师在 1991 年 8 月 25 日——他 21 岁时就在网络上发布了 Linux 内核的源代码。现在是 2024 年 4 月 23 日,我也是 21 岁,追逐吗,梦想吗,我也想像他一样写出这样神奇的代码,33 年后的今天,我也要开始了,Linus。

欢迎交流;-)

这次参加训练营,也算是二周目,对很多内容都已经轻车熟路。在上次的训练营之中,我并没有完成三阶段的学习,多少有当时实习没有时间进行分配和对更加复杂的操作系统内核架构没有大概了解的原因。

所以在实习结束之后的暑假,我也没有闲着,主要都在学习 Rust 之前没有接触过的内容还有理解 Linux 操作系统内核上了。成果大概就是把《Rust 程序设计第二版》这本书看完了,加上初探了一下 Rust 的异步框架、对 Linux 0.12 这个老版本的内核代码进行了一些阅读。

希望这次可以成功冲击第四阶段,加油!

内存管理

在二周目的学习之中,我重点学习了 rCore 内存模块关于内存管理的这部分内容,这部分内容在一周目里面可是给我吃了不少苦头,从 ch3 到 ch4 的难度跨度确实有些大,并且 ch4 之中的内容基本上就是后续内容的基石。这部分内容还是看得越懂越好,我越看越觉得当初这些人是怎么想出来这个方案来实现内存地址空间管理的,而且这还是没有实现页置换(顺带一提,这是 Linux 当年的大杀器)和cow。说实话这部分的内容 Linus 当年也是调试到吐。

从物理地址、虚拟地址和物理页号、虚拟页号的初级表示到页目录项、页表对内存的统一管理再到MemoryArea和MemorySet实现对一个进程的内存地址空间的高级表示,确实很精彩。

更加高级的内容

后面的内容,包括文件系统、进程间通信和 I/O 重定向、和并发的复杂性实际上更上一层楼,所以 rcore 可以去繁从简,用几篇文档的篇幅就带我们对这些操作系统的重要抽象有一个大概的概念也实在不容易。就实验要完成的测例来说,感觉也是简化的副作用吧,有点“面向结果编程”的感觉,这个时候就要考验程序员对自己的要求了:只通过测例完全不够,还需要理解这部分的内容。

其实还想要写很多,但是“我有一个对这个命题的十分美妙的证明,这里空白太小,写不下。”,哈哈,期待第三阶段和 ArceOS 擦出的火花。

前言

我是来自华中科技大学计算机科学与技术专业的一名本科生,课堂上做过类似的操作系统实验,但当时忙于其他事情,只是草草应付了事,所以想趁着这个机会,重新详细认识一下操作系统的基本逻辑,也学习一下rust这门语言。

第一阶段

主要参考资料:https://course.rs/basic/intro.html

之前日常学习都是c和c++写得多,习惯了各种指针等非常自由的操作,遇到rust确实非常不适应,感觉编译器时时刻刻都要和我作对,我明明知道这么写没问题,但编译器就是不让。
但是随着我对于所有权、借用引用、生命周期这些核心概念的了解,慢慢我也体会到了rust这种“安全编程”带来的好处(后面写操作系统实验时,也比之前课堂上用c写bug少了很多)。
此外,“万物皆是模式匹配”的思想,也让整个编程风格看起来优雅了许多。

第二阶段

Lab 1

一开始接触这个系统,主要还是不太了解rust的rs文件之间是如何包含的,在引用一个外部模块时,实际上这个查询路径是怎样的。

如果是在c语言中,就是很自然地,只要有对应的PATH环境变量,根据对应的相对路径去include就可以了,但在rust里面,是通过各个目录下一个名为mod.rs的文件去形成一个文件树的,各种文件以什么程度可见,都由mod.rs控制。

具体见:https://skyao.io/learning-rust/docs/build/module/rust-module-system-explanation/

核心在于这句话:

当执行mod my_module;则编译器可以在同一目录下寻找到 my_module.rs 或 my_module/mod.rs 。

Lab2

在这个实验被一个小问题卡了很久。

1
2
3
4
5
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start, len, prot));
assert_eq!(mmap(start + len, len * 2, prot), 0);

上面的代码会在第二个mmap是触发如下错误:

1
2
3
4
5
let pte = self.page_table.find_pte(start);
if pte.is_some() {
println!("conflict_vpn: {:?}", start);
return -1;
}

当时百思不得其解,自己造了一些其他测试,发现下面这样的操作居然不会报错:

1
2
3
4
5
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start - len, len, prot));
assert_eq!(mmap(start, len * 2, prot), 0);

这两个测试有什么区别,我仔细思考了一下虚拟地址转换为物理地址的过程,怎么使用多级页表一步步得到最终的物理页,结合这个如此“整数”的start,我发现了它们的区别很有可能在于,第二个测试,两个请求会处在不同的子页表中,而第一个测试则都在同一个子页表。因此,我终于开始详细看find_pte的流程,发现它是只要找到对应页表项就会返回,不会判断该页表项是否Valid。

那么此时,如果某个子页表已经分配,只是对应页表项不Valid,那么conflict的判断就会失误。

这个故事告诉我们:不要主观臆测一个函数的用途,在使用一个函数之前,一定要确切知道它的输入输出表示什么意思。

此外,也不应该使用find_pte函数,本来这个函数就不是pub的,有一个判断了是否Valid的封装好了的函数供使用。

Lab3

这个lab挺简单的,就看懂fork和exec的执行逻辑之后,找到那些二者都改了的数据,只执行exec的对应改动,减少无意义的赋值。

stride算法,我使用了一个溢出标记的方法来防止溢出,即计算某个进程的stride值加完之后溢出了,就把该进程标记一下,之后都不考虑调度它,直到所有进程都溢出了。其实这样徒增了很多计算量,问答作业中的方法其实才是比较好的解法:

思想大致如下,没实际运行过,可能有一些细节上的问题:

1
2
3
4
5
6
7
8
9
10
11
impl PartialOrd for Stride {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let a = (self.0 & 0xff) as u16;
let b = (other.0 & 0xff) as u16;
if (a > b && a < b + 255 / 2) || (a < b && a + 255 / 2) < b {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
}
}
}

Lab4

这个实验做的时候遇到很多多重借用的错误,一个比较好的编程习惯是,在函数A调用一个函数B之前,先看看B里面有没有对什么进行了借用,如果有,注意先在调用之前把对应的借用drop掉。

Lab5

银行家算法,增减数据的时候记住,allocated + remian = all,有bug的时候就多看看这个规则有没有违背。以及记得在获得对应数据之后减need。

总结

这些实验确实都聚焦在了操作系统中基本且核心的问题,而且架构也是循序渐进,让人能够慢慢了解虚拟化、进程线程、文件系统这些都是怎么一步步得来的,感觉受益匪浅。

第一阶段

工作中已经写了一年多的Rust代码,体验确实不错,特别是完善的包管理工具,大大减少了编译时的心智消耗,所有权和借用系统并没有很复杂,写一阵代码自然就熟悉了

第一阶段算是再巩固下基础,数据结构题比较有意思,不过深入学习还是要抽空看看链表

第二阶段

对操作系统很感兴趣,尤其是第三阶段的虚拟化方向。由于平时工作比较忙,也就晚上和周末抽时间抓紧写写代码,下边主要总结下各个章节产生的疑问和实验遇到的问题吧

lab1(ch2,ch3)

主要熟悉了应用加载,中断,用户栈/内核栈切换,任务切换的机制,再是学习了下risc-v寄存器以及汇编方面的知识,受益匪浅

实验比较简单,主要熟悉下syscall的开发流程

lab2(ch4)

本章新增了地址空间,内核和应用通过页表机制把虚拟内存映射到实际的物理内存,虚拟内存使得不同应用都可以使用连续独立的地址空间,并做到了不同应用之间的隔离

strampoline段用于保存陷入内核和返回用户态的代码,并通过把内核和应用的strampoline设置成相同地址的虚拟页(最高页),使得切换地址空间之后,这段代码地址可以无缝衔接

TrapContext紧贴着strampoline位于应用的次高页(看起来没必要一定要在次高页)。TrapContext用于保存上下文切换时的寄存器以及kernel satp和kernel sp等。TrapContext位于用于应用地址空间是因为,切换上下文时只有一个sscratch寄存器可以用于周转,而如果TrapContext位于内核栈(内核栈不是恒等映射),那就只需要先通过sscratch得到kernel satp切换到内核地址空间之后才能访问TrapContext,这时就没有额外寄存器获得kernel sp也就拿不到TrapContext地址。如果位于应用地址空间,就可以通过sscratch寄存器保存TrapContext地址,在陷入内核后,先在应用地址空间保存好上下文之后,再切换到内核地址空间

实验需要注意的点:

  • mmap页面要4k对齐,空间左闭右开

  • syscall复制结构体时,需要通过translated_byte_buffer拿到用户态结构体对应的物理地址(这部分内存在内核是恒等映射的,可以正确写入),再复制

lab3(ch5,ch7)

这两章主要讲了进程和进程通信,是概念相对简单的章节,难点主要在于调度算法

实验是实现spawn,总体来说是new+fork,不复制地址空间,但继承父子关系

lab4(ch6)

本章学习了文件系统,代码中数据结构依赖关系相对复杂,画图梳理下比较方便理解

实验中nlink需要要保存在DiskInode中,同时减少INODE_DIRECT_COUNT保证DiskInode只占用128bytes

这里我把file_count也存下了,方便unlink时候,直接把最后一个DirEntry替换到被删除的Entry位置。其次可以用union保存nlink和file_count来节省空间,毕竟文件没有file_count,目录不允许有link,这里偷懒了

1
2
3
4
5
6
7
8
9
10
11
12
const INODE_DIRECT_COUNT: usize = 26;

#[repr(C)]
pub struct DiskInode {
pub size: u32,
pub nlink: u32,
pub file_count: u32,
pub direct: [u32; INODE_DIRECT_COUNT],
pub indirect1: u32,
pub indirect2: u32,
type_: DiskInodeType,
}

一定要注意锁不能重入,在开发unlinkat时,因为调用其他也加文件锁的函数导致了死锁,被卡了有一阵

lab5(ch8)

本章主要讲了内核中线程和锁的实现机制

下面有几个注意点

  • 调用schedule切换线程前一定要手动drop资源,否则会造成资源泄露或panic

  • exit_current_and_run_next在调用schedule之前分配了TaskContext::zero_init()作为dummy TaskContext,开始还想着这个线程不会再恢复了,那这个栈空间在后边是如何保证地址合法并最终回收的,而这就是sys_waittid存在价值之一

  • 代码中的PROCESSOR并不是thread_local的,但有很多exclusive_access的调用,可能教学用的rCore并不存在多个cpu,这里暂时不需要考虑这个问题

1
2
3
4
5
6
7
lazy_static! {
pub static ref PROCESSOR: UPSafeCell<Processor> = unsafe { UPSafeCell::new(Processor::new()) };
}

pub fn current_task() -> Option<Arc<TaskControlBlock>> {
PROCESSOR.exclusive_access().current()
}

实验是死算检测,并不复杂,按着文档来就好