0%

前言

从零基础开始学习 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。

至此,一锤定音。

尘埃,已然落定。

前言

基本上从零开始通过rustlings学习rust,rust作为一门编程语言,通过其合理的设计,保证了其内存安全和高性能,非常值得学习。

个人感悟

通过rustlings学习rust的基础语法、数据结构、所有权、引用等相关知识,初步了解了rust程序的编写,在通过之后rcore的学习,通过实践更加加深了对于rust编程的理解,这种学习方式还是非常高效,之后还需要更多项目的累计,不断提高rust编程能力,和对操作系统的理解。
对于rust来说,最难理解的还是所有权部分的内容,变量所有权之前的转移,引用、Copy、Clone等相关使用的分析,都需要很多代码的累计,才能写起来rust时得心应手。

第一阶段

做为一个几乎没接触过 Rust 的新手,第一阶段的 Rustlings 给了我很深的印象。通过不断地练习,我了解了 Rust 一些比较基础的概念,并对其所有权概念印象深刻。

做为一个常用 C 和 Python 的开发者,我时常会被 C 的内存问题及 Python 的性能问题困扰,通过学习 Rust 和其相关概念,我逐渐意识到,Rust 或许可以兼顾 C 的性能和 Python 的开发速度,给我带来更好的编程体验。

在后续的日常开发过程中,我应该会尝试使用 Rust 做为我的首选语言,并在其中逐步更加熟悉这门语言,感受 Rust 的魅力。

第二阶段

从我开始接触计算机以来,我就不怎么深入了解过操作系统底层,只是在日常学习中耳濡目染地学习了一些相关概念,有个一知半解。

还记得当年面试的时候,面试官问我“程序从用户态进入内核态是什么过程”,我当时只能尴尬地说不会。

当真正开始学习操作系统的时候,我才发现,操作系统比我想象地更简单,也比我想象中更困难。

觉得简单是因为当以前一知半解的知识被层层剥离,通过简单直白的语言袅袅道来的时候,会感觉“原来这个只要这样就可以实现了”。但是只要稍微深入理解,又会被背后的思路震撼,让人耳目一新,而当理解“为什么需要这样做,有什么考虑”的时候,就会感觉非常困难了。

通过第二阶段,现在我觉得我已经脱离了一知半解的状态,应该可以说是“新手入门”了,希望可以在后续的学习中,能得到更多的成长,更深刻地感受到操作系统的魅力。

第一阶段

通过rustlings学习rust的概念,相比于单纯的阅读更能让使用者明白rust的意义和具体的用法。在rustlings的完成过程中,越做越觉得rust在解放心智上的好处。

当然,在完成过程中我也发现仅仅完成rustlings是不够的,学习rust还需要更多的实践和思考。

第二阶段

第二阶段为在实验指导书的引导下熟悉rcore,并尝试编写一些内核功能。这一阶段重点在于对内核的熟悉程度,只有在对已有代码的熟悉之上才能比较顺利的完成任务。

ch3

该实验着重于记录信息,只需对内核相关代码了解即可。如知道何时发生系统调用

ch4

该实验引入了页表,需要对memory_set相关函数熟悉才能从分配内存的细节中抽身,减轻负担

ch5

spawn只需熟悉exec即可写出
stride算法实现上容易,更多可以关注正确性的证明等

ch6

该实验引入文件系统,需要在理清文件系统中inode,目录项,数据node的关系后再进行编码实现

ch8

该实验更多关注死锁检测本身,不需要太多对内核的熟悉,实现银行家算法即可