0%

第一阶段

第一个阶段是通过 Rustlings 练习 Rust 语言的基础知识,包括变量、函数、所有权等等。在这一阶段, 我更加深入地了解了 Rust 的一些基础概念,比如所有权、生命周期等等。在这一阶段的学习中, 我发现通过 debug 的方式来学习一门编程语言和熟悉其语法是非常有效的。这能够让我更深入的去思考一些语言设计的原因,而不是简单地记住一些规则。

在学习 Rust 的过程中,我发现 Rust 的所有权机制和生命周期机制是 Rust 的核心特性,也是 Rust 与其他语言最大的不同之处。这也是我认为 Rust 是一门非常有意思的语言的原因之一。所有权机制能够让我将每一个值看作绑定, 而非变量, 这种思维方式让我感觉非常新鲜, 也让我能够更多地将注意力放在程序的逻辑上, 而非内存管理上。生命周期机制则是 Rust 的一种保证, 保证了程序的安全性, 同时也能有效的避免 C/C++ 中的一些内存问题, 比如野指针、内存泄漏等等。

在后续的日常开发过程中,我应该会尝试使用 Rust 做为我的首选语言,因为我觉得 Rust 的所有权机制和生命周期机制能够让我写出更加安全、高效的代码。

第二阶段

第二个阶段是通过为 rcore 添加一些简单的 feature 来熟悉操作系统的基本概念和相关实现。在这一阶段,我更加深入地了解了操作系统的一些基本概念,比如中断、虚拟内存、系统调用、文件系统等等。在这一阶段的学习中,我发现通过实践来学习操作系统是非常有效的。这能够让我更加深入地理解操作系统的一些基本概念。对于模糊的概念,我可以直接看到它们的实现,这让我能够更加直观地理解这些概念,也能更深入的思考这些设计的原因。

在这一阶段,我的感受是操作系统是一个非常庞大的系统,其中涉及到的知识点和细节非常多,但同时操作系统对各组件的抽象也非常清晰。只要理解了这些抽象,将各组件串联起来也并不是一件非常困难的事情。

lab1总结

实验一相对简单。由于尚未实现页表,内核地址空间可以直接访问用户态地址空间,因此系统调用的实现只需直接返回用户态地址即可。此外,为了确保后续实验中的测试通过,建议使用高精度的 get_time_us 获取时间,避免因时间精度不足导致错误。至于统计系统调用次数,可以在 syscall 函数执行时进行更新。若需获取系统第一次调用的时间,则需修改 run_first_task 和 run_next_task。

lab2总结

与实验一不同的是,实验二中需要实现页表。因此,之前的系统调用逻辑需要重构。内核必须找到传递参数的实际物理地址,并在该地址进行读写操作。

此外,实验要求实现两个新的系统调用:mmap 和 munmap。这两个调用涉及一段连续地址,因此需要检查该地址范围是否完全未映射或已映射。mmap 负责插入页面,而 munmap 则用于删除页面。

lab3总结

在实验三中,首先需要将之前实验的代码引入本次实验。如果之前的代码有问题,本次实验的测试通常也无法通过。

本次实验的核心是实现 spawn 和 stride 调度算法。spawn 并非简单的 fork + exec,因此在实现前需充分理解其语义。可以借助 fork + exec 的部分代码来调整 TCB 数据结构至目标状态。

stride 是一种进程调度算法,由于本次实验示例较简单,按照教程步骤实现即可完成。

lab4总结

相比之前的实验,实验四的难度有所增加。首先,需要确保兼容之前的代码。一开始 spawn 的兼容性问题导致多次报错,最终通过重新实现 spawn 才解决。

此外,还需注意可能出现的死锁问题。由于某些函数(如 find 和 create)对文件系统加锁,不能直接调用它们,而是需要将其实现逻辑拷贝并进行调整。

最后是 unlink 的目录项删除操作。在找到目标目录项后,需要更新内容。采用了群里分享的方法:删除原目录项后,将内容重新拷贝过去。

lab5总结

实验五的任务是实现一个死锁检测算法,类似于银行家算法。但教程中的描述较为模糊,许多细节未明确说明。因此,需要仔细分析各个细节。sem 和 mutex 的实现思路基本相同。另外,sys_get_time 的实现必不可少,否则某些测试实例会出现死锁。

总结
总体来说,这些实验内容并不复杂,涉及的概念较为基础。但由于是第一次接触 Rust 并使用它编写操作系统,对我而言是一次新奇的体验。实验的主要难点在于 Rust 的使用上。此外,教程的部分内容并不完善,例如死锁检测算法的部分解释得较为模糊。若不是群里有人提到类似银行家算法,我很可能难以理解。因此,希望教程能进一步完善文档内容,帮助更多初学者。

前言

从零基础开始学习 Rust,通过 Rustlings 逐步入门。Rust 作为一门编程语言,以其优秀的内存安全和高性能设计而闻名,非常值得深入学习。

个人感悟

通过 Rustlings 学习了 Rust 的基础语法、数据结构、所有权和引用等核心概念,初步掌握了编写 Rust 程序的基本能力。在接下来的 rCore 学习中,通过实践进一步深化了对 Rust 编程的理解。这种从基础到实践的学习方式非常高效。未来还需要通过更多项目积累,不断提升 Rust 编程能力以及对操作系统的理解。

Rust 的所有权机制是学习过程中最具挑战的部分。变量所有权的转移、引用的使用,以及 Copy 和 Clone 等特性的分析,都需要反复实践和大量代码积累,才能在实际编写 Rust 代码时得心应手。

第一阶段

是我第四次学习 Rust 编程语言,第一次看 course.rs,第二次用 rust 刷 leetcode,第三次刷 rustlings,第四次再刷了这里的 rustlings。每一次学完后总是不知道学到了什么,脑袋空空,但实际上写的时候,体验在逐渐变好。可能还是留下了些什么东西,只是没有察觉到。

不知道这次留下的东西能持续多久。

Rust 很有特点,枚举变量和模式匹配相得益彰,所有权机制比 std::move 舒服了不少,生命周期让我似懂非懂,trait 很有意思,宏看不明白,并发和异步根本没看。

Rustlings 比官网多了很多东西,例如后面的算法题。数据结构那一块,实在找不到什么优雅的方式来实现移动,应该是学的东西太少了;前几个题目对 copy trait 进行了滥用。后面的倒是简单,不涉及什么 Rust 独有的特性,如果会用其他语言写,那么这里就会写。

相关资料:course.rs,the book,rust by practice

第二阶段

rCore 是一个的小内核,长得像 xv6,基于 riscv ISA 和 RustSBI。

前面几章的内容大同小异,主要是熟悉操作系统的基本概念,例如内存分页管理。文件系统章节需要对 easyfs 有基本了解,这一点通过阅读 guide book 可以达到。

死锁检测算法很新颖,是 xv6 里面没有的内容,一直以来,只学过算法,没考虑过要怎么把这个东西切实地加进内核里面,现在又多知道了一点点,还是有不少收获。

测试用例很弱,可以轻度 hack。

第一阶段总结

1年前开始学习rust,初衷是了解一门操作系统级别的开发语言(深入了解,作为工作语言那种)。并为此写了系列的微信公众号文章《拥抱未来语言Rust》并在社区取得了不错的反响,感兴趣的可以微信公众号搜索“三角兽”,欢迎关注。
rust作为一门系统级语言,拥有高性能的同时还具有高安全性,基于RAII风格自动资源管理,避免了很多内存安全问题(这也是吸引我学习的主要原因)。本次比赛是我第一次参加的系统级比赛,通过比赛,夯实了对rust语言的理解,包括:所有权,作用域,生命周期,智能指针等。非常有意义,在此感谢主办方!

第二阶段总结

一直以来对OS非常感兴趣,本次通过身临其境的“代码调试”,熟悉了整个项目架构,并对OS有了进一步的深刻认识。在调试过程中不仅熟悉了OS,还对Rust语言有了更深入的认识。第二阶段的操作系统实现基于RISC-V指令集,之前没有了解过RISC-V,因此看汇编语言会有些头痛,但结合RISC-V手册加上AI的辅助,理解这些汇编代码完全没有问题。
通过第二阶段的学习,破除了一直以来对操作系统底层实现机制的迷雾,那层隔阂在应用开发人员心底的对操作系统的朦胧自此打破,世界上没有彩蛋,只有认识盲区,破除这些盲区,就是扩大自己的认知,增加自己的技术自信。后面打算写系列的博客来总结、分享操作系统,影响更多的人来学习操作系统。

下面是第二阶段各个实验的总结,重要的知识点我都画成了流程图,希望帮到需要的人。

lab1

这是第一个实验,整体难度不大,通过print打印信息调试,一天过关。

lab2

地址空间映射这一章知识密度较高,反复看了几遍才基本弄懂,调试代码陆陆续续调试了3天。(还是太菜,菜就多练!)

简单总结下本章:在开启分页SV39分页之前,OS和都是直接访问物理地址,这给系统带来很多潜在的安全隐患,例如地址空间未隔离等。开启分页模式后,OS和用户代码中就都是虚拟地址了,需要通过页表和MMU进行转换,并且页表上的属性区分出了U和S,进行了权限和空间的隔离,分别在特权级和地址空间上保证了OS内核的安全,同时也保证了用户程序之间相互隔离,彼此空间不会重叠。(虚拟空间可以重叠,但通过页表映射后通常是隔离的,有种特殊情况是通过映射到相同的物理也实现内存共享)

另外为了OS在开启分页后能平滑的访问,对于OS采用的是恒等映射(虚拟页号=物理页帧)。而对于用户程序通常采用Framed映射,通过栈式页帧分配器分配页帧并和虚拟页号建立映射关系,动态生成页表及页表项,实现物理页帧的按需分配。

另外一个比较好的抽象是地址空间MemorySet,它作为任务的一部分,管理着页表及和逻辑区。在实现采用了RAIL机制,加上rust的所有权及drop trait自动实现页表项的释放。

lab3

sys_spawn第一版实现测试通过,但是到实验4发现实现有问题,然后修改为exec+fork的方式完成。

lab4

文件系统章节是我花时间最多的一个章节,时间主要花在了对文件系统的理解上,看源码也费了些时间。将细节通过在线文档整理如下图所示:

lab5

死锁检测可以基于银行家算法实现,参考Wiki

阶段一:rust语言学习阶段

作为一名C++程序员,不得不说,rust确实是让人眼前一亮。

所有权机制

rust的所有权是rust语言内存安全的重要特性之一。一块内存(或者说一个值)在任一时刻只能有一个所有者,而其他的地方只能持有这块内存的引用。并且在同一时间内,不能有多个可变借用或同时存在可变和不可变借用。生命周期机制还会在编译时检查内存的生命周期,防止野指针的出现,大大地提高了内存安全性

Option

rust的Option类型(一种枚举类型),有Some和None。这与传统意义上的NULL不同,虽然麻烦了点,但是也保证了安全,并且可读性更强。Option类型是一种显式的枚举类型,要求程序员在定义一个可能为空的值时必须使用Option类型。这种方式使得空值情况成为类型系统的一部分,强制程序员在编译时考虑到值可能不存在的情况。相较之下,Null带来的麻烦反倒会比使用它的便利来说更大一点。

阶段二:rCore OS设计实现阶段

lab1

lab1实现了get_time和get_task_info。粗略地了解了一遍现有的这个框架。

lab2

lab2实现了mmap和mumap,并且重写了lab1的两个实现。由于页表有关的知识之前已经学过了,所以也是光速完成了(自己写了给v2p,虽然后面发现已经有线程的接口可供调用了,难蚌)。

lab3

lab3实现了spawn和stride调度算法(其实压根没实现调度,本来想着先过了拿分,但是截止到这篇博客写下都还没实现)。spawn通常通过fork+exec实现,在这里就各copy一点出来就行了。

lab4

lab4实现了linkat、unlinkat、fstat等。当时写完的时候发现fstat一直无法正确的输出nlink,后来才发现是我inode_id算错了。我一直在找一个只存在于虚空之中(bushi)的inode_id。

lab5

lab5实现了死锁检测(又见到了我们熟悉的dijkstra大神的身影:指银行家算法)。虽然只是粗略地实现了semaphore的检测,但是确实让我更深入的学习了这个死锁检测算法。

其他:

参加这个训练营的时候也才大二,暑假刚看完CSAPP,在看MIT的6.s081,刚看没几节课就听说了这个训练营,不仅很好地巩固了自学时所学到的知识,还学了一门新的,非常优秀的rust语言。非常享受这种解决一个又一个挑战所带来的成就感。

Rustlings

  • Rust 的编译器异常严格,尤其在内存安全、生命周期、借用检查等方面。Rustlings 的每个练习都会在编译时给出详细的错误信息,这些错误信息通常带有很强的提示性,帮助理解如何管理内存、如何使用所有权、如何避免数据竞争等
  • Rust 的内存管理系统(所有权、借用、生命周期)对比其他语言来说非常独特。特别是在多线程编程中,Rust 提供的无垃圾回收的内存管理方式可以有效避免潜在的并发问题,这使得它非常适合高并发、性能要求高的场景
  • 在 Rustlings 中,经常使用到 ResultOptionmatch,不仅提供了对错误的显式处理,还强制我们处理所有可能的异常情况,避免遗漏潜在的错误分支
  • Rustlings有很多智能指针,感觉之前没有接触过类似的东西,各种特性让我理解了很多操作

rCore

  • 在 rCore 实验中,我深入理解了操作系统的各个核心部分:进程管理、内存管理、文件系统、系统调用等。每个实验模块都涉及到这些基本概念的实现,通过动手实践,我加深了对操作系统内部机制的理解
  • 在内核代码的开发过程中,Rust 强制管理内存的方式迫使我时刻注意内存分配、释放的问题,这大大降低了由于内存问题引发的 BUG 的出现几率
  • rCore 的实验涉及到进程调度、线程切换等并发编程内容,这让我更好地理解了操作系统是如何管理和调度多个进程或线程的
  • Rust 的多线程编程模型使得并发的实现变得更容易。通过使用 ArcMutex,我能够有效地管理共享数据,避免了传统的并发编程中容易出现的数据竞争问题
  • 在 rCore 的实验过程中,调试是一个非常重要的部分。在操作系统内核层面,调试不像应用程序那样可以直接运行调试工具,而是需要通过打印日志、手动检查内存、模拟调试等方式进行
  • rCore 是一个操作系统内核,涉及到硬件编程的部分,比如中断处理、IO 设备控制、虚拟内存等。这些部分让我更加理解了操作系统如何与硬件交互

学了什么

Rustlings

  • 学习了rust语言的基本概念
  • 了解了所有权,不可变性,生命周期,借用等概念
  • 了解RAII的思想(集美u这不是我们c++的RAII吗)

rCore

  • 学习了riscv汇编
  • 阅读了rCore源码
  • 实现了一些简单的系统调用
  • 实现了简单的死锁检测
  • 被借用检查拷打

收获了什么

  • 了解了一些OS的基本概念
  • 锻炼了阅读源码的能力
  • 练习了debug与编程
  • 熟悉了git等工具的使用
  • 提高了安全意识

前言

这次加入训练营的前因来自于两位朋友曾断断续续提到了 rCore,以及这个与 rCore 有关的 OS 训练营。

关于 Rust,我曾抱着将信将疑的态度。在社交媒体上关于此的风评给我的感受来讲,就好像万物皆可被该语言重写,这个语言能解决一切编程问题。而包括这种偏见在内等原因,我却没有对 Rust 有太多了解。

而至于 OS,我曾因观看过蒋炎岩的 OS 课程而对 OS 略知一二,但却一拖再拖,因各种原因没有完整的学完全部课程以及做完 labs。

我想,是时候改变这一切了。我最终报名加入了进来。

<–! more –>

PT1. 初次上手 Rust 与 Rustlings

在报名之后,接下来的第一个任务便是完成 Rustlings 全部 110 道题目,以此形成对 Rust 初步的认识。Rust 的基础语法适应起来很快,但是最重要的还是适应 Rust 最核心的 borrow,所有权等一系列与 RAII 相关的设计。虽然碍于 Rustlings 篇幅受限,我没能完整掌握这方面,不过这部分知识还是在 rCore 的实验中补回来了的。

最终,我成功完成了全部的 rustlings 题目,并晋级至重要的下一阶段——rCore。

PT2. rCore 之旅

接下来要完成的,便是相对而言更为重要的 rCore。一路上磕磕绊绊不少,但还是完成了挑战。

首先是 Lab1,Lab1 的任务主要是理清 rCore 原有的代码结构。我在刚看到 rCore 的框架代码的时候,老实讲的确会感觉无从下手。不过,在我花上一段时间了解原本的代码结构,理解调度部分的实现后,就没感觉问题很大了。这中间有个小插曲–由于我一开始看错了题目,我一开始以为 syscall_times 部分记录的是每次 syscall 调用时对应进程运行的时间,但是后来发现其实是每种 syscall 调用的次数。

接下来是 Lab2。因为 Lab2 的框架代码已经提前实现了多种用于内存管理的 struct,我最开始的想法是直接在内存的 PTE 上做文章。但我后续发现,在不对框架原有分配部分不做出大变动的情况下,这种想法似乎并不可行。我好像又有点手足无措,不过思考过后,我最终想到遍历 MemorySet 中全部 MapArea 区间计算并调整区间的方案,总算实现了 map 和 unmap 的 syscall。

Lab2 中重写 sys_get_timesys_task_info 的部分就相对容易些,我实现的方案是按照对应函数所需返回值的大小,对于每个可能包含该变量的虚拟页,反查用户空间对应的物理页,并从内核 0x10000 虚拟页处开始分配虚拟页表然后 map 到对应的物理页。Lab2 原本这部分的实现实际上每次 map 后都不会 unmap 掉, 而是在接下来继续 map 的时候从上一次分配的末尾处 map。但在后面的 Lab 中,由于分配量超出预期,从而会导致其尝试 map 已经在别处分配了的 PTE。到问题发现之后,我添加了 unmap 部分,并使分配都从 0x10000 开始,以此解决了问题。

随后便是 Lab3,相对而言其实还算简单,stride 部分只需要参照题目描述实现。而至于 spawn 部分,可以参照已实现的 fork 与 exec 部分,这样照葫芦画瓢也同样能较为轻松的实现出来。

而接下来的 Lab4,由于项目结构又一次进行了变化,引入了一个新的 easy-fs 库,从而又感到了和刚进行 Lab1 时,没法理解代码结构时的头疼。而在进行 Lab4 的过程中,因为不知为何导致的死锁,我又一次头疼不已,甚至一度想放弃。好在最后因为 Rust RAII 的特性,通过封装一层函数的方式,将某个我并不知情的引用自动 drop 掉,才最终解决了问题。

最后,到了 Lab5,rCore 阶段要结束了。虽然这次我并没有选择将检测死锁的部分独立拆分为单独的 crate,但这部分仍然先在裸机上开发,完成后再移植进内核。由于是直接在裸机上开发,整体调试起来也会方便很多。不过话虽如此,在刚开始的时候,由于我开始进行 Lab5 的时间早于这次线程相关课程的上课时间,且题目没有详尽地描写检测死锁的算法,以及对算法的不熟悉,导致我在这里卡住了很久没有进展。不过最后,我确定了题目给出的算法为 Dijkstra 的银行家算法,在不断的了解这个算法后,最终完成了 Lab5。

至此,一锤定音。

尘埃,已然落定。