0%

第一阶段

这一阶段没啥好说的,之前已经参加过好几次训练营了,rustlings 也不知道是三刷还是四刷来着,总之还是很顺利的

第二阶段

这次第二阶段相较上一次进步就很大了(上次只做了lab1),总体而言感觉收获还是很大的

对我而言,最难的是lab2和lab4,光看文档是真不懂,还是得 RFSC

在理解页表分配的时候,一定要根据link.ld画内存图帮助理解,不然真的会绕晕,之前在学习操作系统的时候只知道有这么个机制,真到实现起来的时候才发现这么复杂,有些认为想当然的功能都要通过很复杂的调度

lab4代码很多,但是写起来是有技巧的,当我看到文档上说到“松耦合”的时候,就提醒了我一点:既然已经做好了解耦的工作,那也就意味着我在编写内核的时候其实是应该不需要太关心文件系统的具体实现的,只要调用固定的几个API应该就能完成任务。

果然,在逐个观察几个相关的系统调用的实现之后,我找到了几个关键的API,还算轻松地完成了任务

写lab5的时候有点被群里的人带偏了,还以为要实现银行家算法,清醒过来之后才发现是死锁检测

开源操作系统训练营第二阶段总结

学习契机

之前有断断续续看过一些课程,没有加入到学习训练营中,自己一个人没有啥动力。这次通过其他其群的信息,加入训练营和大家一起开始学习,一起探讨,最终完成了所有课程的学习,并完成了题目练习。

学习内容

  • 了解操作系统的基本概念和发展历史
  • 了解操作系统的基本原理和设计思想
  • 学习了操作系统的一些基本概念,如文件系统、进程、线程、调度、死锁、互斥、信号量、管道等。

学习心得

和大家一起学习,更加容易坚持到最后。学习中遇到的问题,也可以及时和群友讨论。因为需要完成题目拿分数,有时候会功利性比较强,以完成测试为目的,忽略掉一些知识点。最后学完之后可能容易忘记,可能需要多多复习。

开源操作系统训练营第一阶段总结

学习

  • 学习了rustlings的课程,并完成了所有练习。

心得

  • 学习之前主要还是需要看完官网推荐的书籍,然后再去学习和练习。
  • 练习过程中,可以边做边学,遇到问题可以及时查阅文档和群友进行讨论,避免陷入牛角尖。
  • 借助ai的帮助,可以更快的学习和掌握知识。

收获

  • 通过rustlings的练习,对rust的熟悉程度有了更深入的了解。
  • 了解使用rust来实现一些数据结构和算法。

第二阶段总结

这个阶段主要是要完成几个实验,借助几个实验理解一个具有进程/线程管理、内存管理、文件系统、进程间通信和提供了一定同步机制的内核是如何构成并运作起来的。

比较深刻的有这么几个知识点:

  • 链接脚本与全局符号的使用
  • Rust的汇编嵌入
  • 第四章中,在使用分离内核空间的时候。通过设计跳板页来解决切换页表后指令执行的问题。跳板页
  • 第六章了解了文件系统,了解了块设备的概念,对文件系统的各个抽象层有了一定的了解。
  • 第七、八章了解了操作系统是如何为应用提供同步原语的

跳板

由于我们的内核使用了分离内核空间的设置,所以在Trap的时候需要切换页表。但在切换页表之后,pc寄存器还是忠实的在其原来的位置自加到下一条指令,如果内核内存空间程序内存空间对这段代码的映射不是在同一个位置的话,则会表现出来程序跳转到了别的地方执行的效果。因此我们设计了一个跳板页,在虚存中映射到所有内存空间的最高页,确保在切换之后,也能正确运行下一条指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# trap.S
...
.section .text.trampoline
.globl __alltraps
.globl __restore
.align 2
__alltraps:
csrrw sp, sscratch, sp
...

# linker.ld
...
stext = .;
.text : {
*(.text.entry)
. = ALIGN(4K);
strampoline = .;
*(.text.trampoline);
. = ALIGN(4K);
*(.text .text.*)
}
...

在上面的汇编可以看到,我们给trap.S分配到了.text.trampoline段,并在链接脚本中定义了一个strampline符号来标记他的位置,这样我们可以在Rust中找到这个跳板页,映射到我们期望的位置。

但将跳板也映射到别的地方带来了新的问题,原来__alltraps中最后跳转到trap_handler使用的是call trap_handler。我们可以通过obj-dump看看编译得到的指令。

1
2
3
4
5
6
7
8
9
10
# obj-dump -Dx ...

...
80201056: 73 90 02 18 csrw satp, t0
8020105a: 73 00 00 12 sfence.vma
8020105e: 97 80 00 00 auipc ra, 0x8
80201062: e7 80 e0 0b jalr 0xbe(ra) <trap_handler> # pc+0x80be
...
000000008020911c g F .text 00000000000003b2 trap_handler
...

可以看到,这里用的是pc相对寻址,也就是基于当前指令的偏移找到trap_handler所在的位置。但是现在__alltraps已经被我们映射到内存的最高页去了,也就是说我们实际运行代码的时候是在下面这一段内存中。

1
2
3
4
5
6
7
8
9
# gdb
>>> x /20i $pc-10
0xfffffffffffff054: ld sp,280(sp)
0xfffffffffffff056: csrw satp,t0
0xfffffffffffff05a: sfence.vma
=> 0xfffffffffffff05e: jr t1

>>> p /x $t1
$9 = 0x8020911c

很明显如果这里跳转到$pc+offset$的话,并不是跳到位于正常代码段的trap_handler。所以我们要将这里换成寄存器跳转,将trap_handler的地址放到寄存器t1中,这样才能顺利地调用到trap_handler

第一阶段总结

第一阶段主要考察 Rust 基础编程和数据结构与算法,由于我有一定 Rust 基础,所以前 100 道题比较轻松,但从未使用 Rust 刷过算法题,因此后 10 道题让我学习了如何使用 Rust 编写常见数据结构如链表、栈、堆、图等,以及常见的排序、搜索算法等,有所收获!

第二阶段总结

第二阶段我的学习方法是先看完 v3 book 对应章节,然后再做实验题。v3 book 写得循序渐进,质量上乘,读懂后做实验题都比较轻松。第二阶段的精华都在 v3 book 中,十分建议精读一遍,我自己精读一遍后发现了若干内容和文字错误,还提交了 19 个 pr 修复。这期间我还写了篇博客分析有栈协程示例代码。

五个实验题中最后的死锁检测算法花费的时间要久一点,因为前面章节没有铺垫,直接就是抛出一个算法让实现,且算法只有过程描述,没有原理分析。这个算法似乎很少在生产上见到被实际使用(可能是我孤陋寡闻),我建议换成其他更有意义的实验,比如实现某一种同步互斥原语。

第一阶段总结

原版rustlings曾经做过,但是这次添加了一些题目,不过也曾经在exercism.org上接触过相似的
通过这110道题,完整的复习了一遍Rust的相关基础知识,特别是最后的几道很有Rust特色的数据结构题

内容

  1. 特权级切换
  2. 虚拟CPU(进程调度)
  3. 虚拟地址
  4. 文件系统
  5. 进程间通信
  6. 多线程
  7. 资源共享

    遇到的困难

  8. rCore抽象层次多,第一次编写系统调用时,缝合代码逻辑困难
  9. 程序之间进行切换的上下文资源维护
  10. 虚拟硬件资源的抽象
  11. 文件系统的内存映射
  12. 银行家算法设计
  13. 系统调试

    收获

    阶段二的完成让我对操作系统的运行有了全局的认识。在紧张的时间内完成了阶段二的任务,真正的帮助我了解了系统级编程。在以后的学习生活中,更容易占在全局的角度看待问题。
    我特别喜欢的一句话是“没有什么问题是加一层抽象层解决不了的”。

内容

  1. 所有权
  2. 基本数据结构
  3. 集合类型
  4. 生命周期
  5. 闭包
  6. 智能指针
  7. 面向对象编程

    遇到的困难

  8. 在第一阶段做110题时,每每完成一道题后,下一道题就会出现许多全新的知识点,这时只能再回到指导手册继续阅读。
  9. 变量的生命周期延长
  10. 智能指针的内存结构
  11. 集合的遍历处理.iter()、.enumerate()、.find()、.map()、.fold()等的组合使用。
  12. 使用rust库需要时间熟悉
  13. 闭包的使用,闭包是我遇到的全新的语法,难在分析它的输入、内部逻辑以及输出。

    收获

    使用rust时,需要非常关注变量的生命周期,进而要考虑其在内存程序内的位置。通过rust的使用让我对程序的组织结构有了清晰的了解。在掌握rust基本语法后,因为rust语言的可读性,阅读rust代码成为了一件相对简单的事情,但是需要格外小心变量的生命周期。要想熟练掌握rust语言,还需要对rust的各个特性进行深入研究。

2024年秋冬季开源操作系统训练营第一、二阶段总结报告

在开始正式报告之前,我想说作为一位三战老兵,三次参加开源操作系统训练营,从一开始的只能勉强完成rustlings,再到现在完全实现第二阶段五个lab的所有功能。一路走来,走到今天实属不易。遥想一年前的我,因为二进制漏洞挖掘与利用(pwn)接触到操作系统,并励志于弄清操作系统的运行原理,但是苦于实在没有学习路径而踌躇不前,是开源操作系统训练营给予我接下来奋斗努力的方向。在此也非常感谢这个项目。

另外这一路上结识的,给予我帮助的伙伴,我也非常的感激并庆幸认识了你们。尤其是23年春夏季的助教徐堃元老师和朱懿老师,没有你们的帮助我可能还卡在一些看上去非常令人费解而又苦恼的地方,你们在rcore与系统能力赛上的指导对我的启发很大,在这里向你们表示感谢。

我身边的朋友曾问我,为什么要做操作系统?操作系统在外人看来晦涩难懂,且在实际业务场景中似乎并不需要我们去细究它的实现与原理。每到这种时候,我就会想到登月前肯尼迪总统所做的讲话:We choose to go to the moon, not because it’s easy, but because it’s hard.

做难而正确的事情,至少目前为止,我都是这样干的。至于前程?但行好事,莫问前程。在我还有精力干的年纪,稍微干点事情吧。

2024.11.7 stone-san于寝室

第一阶段总结

第一阶段就是rustlings的通关。一开始我对rust语言一无所知,且光看一些书籍的话,学习的效果也不佳,所以使用rustlings来实操是有助于我理解rust语言的。在这个阶段我觉得rust不同于其他高级语言的一些地方一个就是所有权的概念,使用所有权,编译器在编译时会根据一系列规则进行检查,且这种检查只发生在编译期,因此对于程序运行期,不会有任何性能上的损失。我理解是说同一个作用域范围内,数据只能被一个变量所使用,如果说发生了赋值的情况,那么除非是这个数据类型本身支持自动拷贝之外,像String这样的类型则必须显式调用clone方法来进行拷贝。这样在内存安全上,rust从根本上杜绝了一些c语言里会有的问题,诸如悬垂指针等。

另外,训练营版本的rustlings本身也加了一些自己的内容。比如今年新增的数据结构与算法章节,就给rustlings通关带来了新的挑战。不过这对于接下来rcore是有好处的,因为rcore里全是数据结构相关的一些东西。

第二阶段总结

第二阶段是rcore,有五个lab,分别对应着多道程序、地址空间、进程管理、文件系统、并发同步这几个我认为操作系统里最基本的一些实现。

多道程序的实验主要是实现一个系统调用来获取当前任务信息。第一次做的时候并不清楚到底要干什么,但是随着多次的学习了解了,这个TaskInfo结构体的信息其实是要我们在任务结构体里先增加一些对应的字段,然后再通过实现方法来设置这些字段的内容或者是获取这些字段的信息,最后输出给用户。其实绝大多数后面的任务基本也就是对结构体字段的完善与实现方法的撰写。、

在地址空间的实验中,除了上一章的实验之外,还增加了内存分配mmap和munmap的实现。因为开启了虚拟内存,所以现在我们不能直接对任务结构体操作,因为会遇到数据存储在两个页上的情况,我们只能先根据token找到真实地址,然后再获取内容。至于mmap和munmap,那涉及到对页表和memory_set的更改,为此维护了一个BTreeMap,来对应内存地址与具体页的关系。

在进程管理里,先前的任务结构体被重新修改为了进程。并且要实现的系统调用也与进程的产生有关系。此外,对stride算法也有了一定的了解。本质上还是进程里添加了一个字段,然后在每次执行进程时都重新处理一次prio字段并更新记录,以决定下一次执行的进程。

文件系统是比较难啃的一章,这里首先文件系统我不是很了解,而且这一章里也大量使用了闭包,这都是要从零开始学习的地方。由于文件系统大改,sys_spawn需要适配上新的文件系统。这里我原本的写法是将这些操作全部放置于syscall/process.rs下,但是问题在于data会莫名其妙的数据置零。所以只能将这个任务扔进task中了。

而并发同步是我另一个不熟悉的领域,在此之前我对并发这里并没有过多的了解。通过这章的学习也了解了互斥锁和信号量的实现机制。总之还是非常有挑战性以及很有趣的。

之后……

至于之后要做什么?我不知道,我手上的活有很多。有很多的任务等待我去完成,比如三阶段的项目制实习,比如明年系统能力赛的内核实现,比如软件所的kernel开发任务。总之一直在路上。我想到了我小学时候读过的一本关于奥数的书,在那里,序言的最后写下一句话:

奔跑了,就别停下!

我想。应该是这样的。

第一阶段学习总结

学习心得

初学这门语言时总觉得是一门很繁琐的语言,需要和编译器,但是经过编译器和 clippy 方便的静态检查(折磨),发现这门语言的确是很严谨的,很多错误都是在编译期就能发现的,到后面就会发现这门语言有一种可以帮你减少错误的感觉。而且 rust 语言的创新性也是很吸引人的,下面是我认为 rust 语言的一些特色:

  1. 类型安全:默认不可变,默认私有。
  2. 内存安全:包括所有权系统(遇事不决用.clone()),借用概念,智能指针&RAII,生命周期等。
  3. 并发安全:多线程并发,异步并发。
  4. 零成本抽象:在编译期就能消除抽象的开销。对于泛型代码,rust 会在编译期将泛型代码展开,消除了泛型的开销。trait 又保证了代码的灵活性。
  5. 宏:rust 的宏是一种强大的元编程工具,可以指定匹配模式,并在编译器生成代码。

另外,rust 的迭代 API 也和其他语言不太一样,rust 的迭代器是惰性的,只有在需要的时候才会计算,这样可以避免一些不必要的计算。rust 中的迭代器处理同时满足了效率和代码的简短,是大部分 rust 代码中无处不在的。这些特性使得 rust 语言在系统编程中有着很大的优势。

在 rustling 过程中,有一些题目是编译器告诉我要这么做,然后就过了,虽然体验到了编译器的强大,但是对底层的原理还是似懂非懂。

Option和Result类型的使用感觉有点繁琐,各种.unwarp()。

最后在algo的题目被裸指针的转换弄得很头疼,也许链表Box::into_raw(node)可以用内部可变性和引用计数?

学习路径

rustlings 只是对 rust 中的一些基本特性做了一个简单的介绍,没有很深入的要求。对于很多特性还可以深挖,而且 rust 中还有很多有意思的特性。以下是我个人再看的一些资料:

A half-hour to learn Rust

rustlings + The Rust Programming Language - The Rust Programming Language

Introduction - Rust By Example

Effective Rust - Effective Rust

Table of Contents - Rust Cookbook

第二阶段学习总结

rCore 用比较现代的 RISCV ISA 和 rust 语言介绍并实践了 OS 相关的重要概念和基础功能,如线程调度、地址空间切换、文件系统、IPC和并发。

学习过程

整体思路:先看看每章的引言和题目,了解下一章将要学到什么。并且带着题目和测例中的疑问点看实现细节和代码。(面向测例编程)

前三章的知识更偏向ISA,riscv 还是比较简单直观的,没有很多复杂的指令和很绕的概念,只需要了解基本的 load/store 和算术运算即可。特权级相关的寄存器比较重要,需要重点记下。

lab2 从内核态复制一个结构体到用户态可以单独用一个函数封装一下,可以参考已经给出的 translated_byte_buffer 的实现,在实现 mmap 的时候要注意 SimpleRange 是左闭右开的区间,然后申请内存前枚举判断区间的相交就很显然了。

lab3 的 spawn 实现的一个坑点就是不能用 TaskControlBlock::new(),猜测是 stdin 等不能初始化多次,所以最好还是仿照 new 和 fork 在 TaskControlBlock 结构体中写一个 spawn。stride 算法听起来很吓人,但实现还是很简单的,只需要加入 stridepass,然后在调度时暴力计算就好了。

前面的代码还算直观,在 lab4 和 lab5 的代码就比较抽象了,为了方便实现参数乱传。

easy-fs 的层级太多了,代码量比较大,做实验时要重点看块管理器和 inode 部分。在 vfs 的 Inode 结构体中只存了 block_idblock_offset,再加一个 inode_id 会对 statlink 的实现比较方便,当然从block_idblock_offset 也可以直接反推出 inode_id。在 unlink 操作时,我获取了一下 nlink,即枚举这个 inode 有多少个硬链接,如果只有一个,就需要回收inode以及它对应的数据块。这个枚举还存在优化的空间。

ch8 的死锁检测算法需要在每次获取锁和释放锁时更新 Available 和 Need 矩阵,需要注意这两个矩阵的区别,什么时候用到不同的两个矩阵。同时新建线程和锁时要维护矩阵的大小。在 lock 和 down 操作执行前判断死锁。因为要更新矩阵中对应 tid 的一行,所以需要知道task在数组的位置。但是锁里面只能获得 task 对象,没法得到对应的下标。最后我用 get_trap_cx() 获取每个 task 的 trap_cx 比较。现在想想更科学的方法应该是往 TaskControlBlock 里面再加一个元素。

体会与总结

感觉 rCore 整体的代码量比较小,用用户程序测试操作系统的过程特别有意思,用户体验很友好,但是部分测例比较弱。

写完 lab 题目之后还是要尽快写总结,感觉过了几周已经忘了rustling干了啥了。