训练营总结报告

训练营总结报告

碎碎念

很久没有写过博客这样的记录了,尤其在 AI 时代,我已经习惯了我给出大概的思路,然后让 Agent 帮我填充文本,渐渐地感觉自己丧失了文字表达能力,所以这篇总结会采用古法编辑,整体也会偏向于碎碎念的记录方式。

缘起

其实一开始我没有想写 rcore 训练营的,我是先去看了很多朋友推荐的 mit 的明星 os 课程,也就是 6.s081,但是一部分是处于希望通过这样的优秀的课程作业,来锻炼我的 coding 能力,顺便可以让我的简历不要那么白(这才是主要目的)。但是当时能力非常有限,写到第二个作业的时候时候还好,只是引入了 syscall 的概念,还是相当顺利的,但是到 lab3 也就是 pagetable 的时候,就写得我头晕眼花了。

恰这时,有群友给我推荐了开源操作系统训练营,我一看,国内搞的,并且是 rust,刚好当时萌生了一些学 rust 的念头,遂转向了 rcore 这边。我总共报名了 3 次,第一次并没有花精力,第二次折戟在专业阶段,当时也是卡在了内存管理,写了很久才搞出来,后面的进程管理更是没看明白(当时还更相信自己阅读代码的能力,很多地方靠自己阅读非常吃力)。第三次我很大程度地使用了 AI 辅助我分析和阅读代码,并且我提供主要架构和思路,很多代码也是由 AI 生成并迭代正确的,相比于第二次也确实理解的更快且更深入了。

接下来就从每个阶段开始总结吧。

基础阶段 - rust 语言入门

不得不说写 rust 是一个很不一样的体验,对我而言主要是第一次接触到所有权这个概念,我之前没有写过 cpp,我是在后面才知道的 rust 的所有权机制是借鉴于 cpp 的 tail 机制。这对我而言是一个非常新的概念,由于所有权的存在,写 rust 代码也是要给很新奇的体验,有时会很惊讶于这种设计的巧妙。

我一度认为这是一个非常好的机制,直到代码开始变得复杂,在 rustling 阶段主要是体现在多线程并发那一块,以及链表的智能指针那一块,尤其是双向链表,借用和借用的所有权写得头大,现在让我离开 AI 手写,我可能也会写很久。尤其是专业阶段代码量和复杂程度都上来了之后,所有权的转移也变得非常复杂,有的时候我不得不尽量使用 clone 来缓解这样的分析,尽管我知道这样写可能并不是最优解。

剩下的对于 rust 的印象就不是很多了,只有一个关于 unsafe 的考虑了,在写 os 的时候,不可避免的会写很多 unsafe 的代码,我对于 unsafe 的理解是,对于 rust 理解不是很深入的 coder 就不要碰 unsafe 的代码,使用 rust 自身提供的基础也能完成大部分工作了,而 unsafe 的部分则交给更值得信任的程序员来编写。rust 本身不保证 unsafe 的正确性,但是可以由资深的程序员来保证。

专业阶段 - rCore

这是令我印象最深刻的一个阶段了,这也是我个人而言花时间最多的一个阶段,对于代码的理解,这一阶段是我看得最深入的阶段。

对于 ch3 的实验,是需要实现一个 trace 的 syscall,现在回过来看,我认为主要是为了引入 taskmanager 的概念,并且熟悉 syscall 的调用流程。

对于 ch4,地址空间这一部分,其难点在于理解 pagetable 的建立,以及地址翻译的过程,尤其是物理页和虚拟页之间的映射,有了这一个概念非常重要,内核和用户态,以及应用之间的隔离就是依赖于用户空间的隔离,每个进程都有独属于自己的页表。

对于 ch5,进程及进程管理,则引入了调度这一概念,在进程管理之前,os 更像是一个批处理系统,挨个从进程池里面取进程,执行,然后再塞回进程池。虽然也会有执行流的切换,但是管理并不灵活,而 ch5 的 lab 则要求实现了 stride 调度,然 processor 可以根据进程的优先级,更多的将执行流分给更优先的进程。

对于 ch6,文件系统,是我个人认为 rcore 最困难和最难理解的一部分。这一节引入和很多新的概念,并且引入了 easyfs-fuse 这样的独立的 crate 来单独编译出初始的文件系统。这一节不仅仅要理解文件系统的设计,还需要理解 os 启动流程。其中很多概念页容易混淆,例如 runtime 对应的 osinode 和文件系统中的 inode,已经相当难以理解的多维数组形成的目录项表。时至今日,很多概念我也没办法清晰的直接讲出来了,只有对着代码,才可能清晰的厘清其中的设计。其中最重要的是理清访问一个文件的时候,是怎么一层一层检索目录项,然后将 datablock 加入到 blockcache 当中去的,对于 blockcache 的优化也是一个很难处理的问题,我当时是实在优化得没招了,直接通过增大 blockcache 的容量来通过了 CI 检测。

对于 ch7 和 ch8,这两块相比于前面几章倒是没有那么有特色,对于标准 I/O 的重定向,我倒是对于 xv6 的实验印象更深刻一点,尤其是在 fork 执行之后,对于文件描述符也会相应的复制出来,所以需要在父进程和子进程里面各自关闭一个输入和输出口。ch8 我的印象实在是不深刻了,我只记得其中需求矩阵的计算我是直接进行了两层循环,并没有进行任何优化就是了。

项目先导阶段 - 组件化操作系统 ArceOS

这一块的印象就没有 rCore 那样深刻了,由于作业的要求并不需要很深入地理解代码框架,所有很多地方我都是浅尝则止,当然这也造就了后面项目阶段的举步维艰。

很大部分我已经忘记了自己怎么实现的了,但是 simpe_hv 我有比较深刻的印象,这也是我认为的这一阶段最难的一部分,实现了一个简单的虚拟机,在这里,就有了很多架构分离的实现了,例如在 vm 层次只有最简单的声明,实际编译的时候是会有一个后端实现的,但是当时我写这一块的时候也是迷茫的,对于这样的设计并没有理解得很深入,在发现改了后端实现后就可以跑通测试了,也就没有深究其中的设计细节了,也就是其实我连 vm 怎么设计的都没有弄清楚。所以现在看来,当时这一部分其实做得相当草率。

项目阶段 - 虚拟化 AxVisor

接下来就是项目阶段,我选择的是项目二的方向二,设备与中断框架重构。

这一阶段,我光是摸清主线就废了相当大一部分时间。在最开始,我是希望按照调用栈逐层摸清到 ArceOS 的 component 去的,但是我发现这样去阅读,不仅烧脑子,而且阅读量相当巨大,很容易密室在细节当中,尤其是不同的主线可能架构还不尽相同。故此,深入优先的阅读方式在这里并没有工作的很好,我在 rcore 阶段大多依赖的深入优先的阅读方式,既让 Agent 按照调用栈的方式,给我一份阅读路线,我可以一层一层地深入阅读。

但是 rcore 毕竟是教学项目的 os,设计并没有很复杂,代码量也没有特别多。但是基于 ArceOS 的 AxVisor 再采用这样的阅读方式就不合适了。所以我后面换成了类似于广度优先的方式阅读代码,我显示最顶层的调用流程,这个就更像是 pipeline 的线性流程,而非调用栈那样的树状结构,对于从抽象的层面理解代码在做什么相对来说更容易一点,然后只有涉及到我需要处理的部分,再根据调用栈的方式去阅读,这样需要深入理解的代码量就小了非常多了。按照遮掩的方式,我很快就厘清了我需要改的文件基本就 axdevice_base 和 axdevice 这两个 crate。

而我在项目阶段所做的主要工作,就是在这两个 crate 里面建立的统一的设备抽象,并且建立了设备路由,这样在 Vm exit 的大 match 中,对于设备的访问就直接重定向到了设备路由这里。并且对于设备的注册,参考了项目原有的 link-section 机制,通过 ld 脚本在编译的时候统一收集设备的工程函数,这样新增设备就只需要改自身的代码,也可以将自身注册到内核的工厂函数清单里面。并且迁移了一个最简单的 aarch 设备,验证了这个方案的可行性。

项目阶段更详细的记录可以查看 study_records,这个记录是由 AI 总结的项目中的每一步规划和修改。

对于 IRQ 路由,我就没有理解的很明白,这也是后面需要继续研究的一部分了。

感言

这个总结是我古法编辑的,已经很久没有手动写过这么长的文字了,写到这里,突然发现,写“这么点文字”原来要这么久。每一段文字,都是在反复鞭策大脑进行回想,在努力回想起每一个细节,从现在回看整个训练营,原来不知不觉已经做了不少内容了,虽然在做每一块的时候其实工作都没有大到可观的程度,但是最后竟然也能写不少文字。

碎碎念

我用文字计数工具看了一下,写了这么久的总结,汉字居然才 2600 个,原来写 2600 个字需要这么久的吗?之前水作业多少字都能很快的用 AI 水完,不得不说 AI 对于文字工作者生产力的提升是相当巨大的,当然,对于程序开发也同样巨大。