0%

2023秋冬季开源OS训练营第二阶段总结报告

首先感谢各位老师和助教,为我们呈现了这样一部优秀的Rust-OS教程。我本人是北航计算机学院大四学生,大二上过使用C语言编写的MOS操作系统课程。第二次系统性地学习操作系统,让我对OS的基本概念、本质原理、Rust编程能力等有了很大的进步和提升。下面对我在各个Lab中的收获进行总结。

Lab1

Lab1实际上引入了一个分时操作系统。该Lab较为简单,让我重新回顾了产生分时机制的原因以及具体实现。将任务分成多个时间片来执行,应用程序以为自己独占了整个CPU,是本章要理解的重点。

Lab2

这章引入了地址空间的抽象,应用程序以为自己独占了整个物理内存。我在本章学习中首先回顾了内存管理的知识,由于大二操作系统课程是基于MIPS汇编,且不区分内核地址空间和用户地址空间,因此又花了些功夫学习riscv页表机制、跳板页面机制。

Lab3

该章对进程的管理中规中矩,特色在于使用Rust语言编写,熟练了我对Rust的编程能力。

Lab4

本章主要介绍了一种文件系统的实现。我大二学习的OS属于微内核操作系统,将文件系统作为了一个用户态进程,其他进程请求文件系统服务时是通过进程间通信IPC机制实现的。而rCore的文件系统则融入到内核中,分层性感觉更强,也让我对inode有了清晰的理解,原来就是文件系统底层辨识文件的标识。

Lab5

还没时间做,想先进行第三阶段hypervisor的学习。

对Rust-OS的体会

用Rust语言编写OS,让我体会最深的不是安全性,而是方便。以前用C写OS,要专门编写释放内存的函数,而用Rust,只需要一条drop命令,甚至不需要drop,待Arc的引用计数清零,整个结构体及所包含的所有内容,全部都会被回收,真是太方便啦。

2023 rCore 二阶段总结

非常感谢清华大学开放的 rCore 课程,一番体验下来后,我对 RISC-V 上的 OS 开发有了比较清楚的认知。
课程为了一步步引导学生理解 OS 的结构和功能,费了很大的功夫在 OS 从简单到复杂的渐进式的构建,以及
各种周边设施的建设上,再次表达对课程精心准备和无私开放的感谢!

用 Rust 写 OS 是一件非常有趣的事情,传统上基建通常使用 C 语言,虽然它非常容易与汇编相对照,抽象
能力和整洁程度却不大理想。作为一门比 C 年轻了 30 多岁的语言,Rust 融入了许许多多现代的理论和特性,
在我看来,对于写 OS 来说比较突出的一点是,它的类型系统带来了更强的表达能力,而同时 RAII 思想的贯彻
有时也带来了一些挑战。

Chapter 2

这一章介绍了经典的批处理系统。该系统区分特权级,但没有地址空间的保护,一个任务容易把其他任务的数据
写坏。

Chapter 3

这一章介绍了分时多任务系统,任务通过定时器触发的中断来切换。

Chapter 4

这一章增加了分页保护,内容一下子长了很多。

不知为何,rCore 提供的 os::timer::get_time_us 在这一章的实现有问题,而上一章却没有。整数
乘除的顺序有误使得误差较大。

在这一章额外做了一下 timer 设施的封装,模仿了 Rust 标准库的 InstantDuration

rCore 的地址空间管理,以及相应的 syscall 设计的比较简单,我觉得这里使用区间树,不用 RAII 风格,
这样可能 API 和实现会更方便合理。

Chapter 5

这一章围绕进程管理以及呈现给用户态的接口。

按实验的要求,在进程被调度时为它的 stride 增加一个 pass,可能更合理的选择是在进程被切出时增加?
因为进程可能主动 yield,没有跑完一个时间片。CFS 调度器的设计看上去和这一算法的原理相似。

实验中使用了 Rust 标准库中的二叉堆来管理就绪队列,开始思考 CFS 选择红黑树而不是二叉堆的原因。

大概前阵子是和朋友聊天时,有人提到这么一个用Rust写的教学内核,产生兴趣后找来看了才发现训练营的存在。

我用Rust已经有一段时间了,但多数时候是用来写应用层面的程序,比如web服务。我对操作系统的理解几乎停留在理论,也没有做过系统开发。得知有这么一个难得的机会,那自然不可错过了。

我的文章中不会着重讲述开发的流程,但会更多地记录一些我自己遇到的比较独特的问题和经历,以及我个人的感想——我自认为我的技术实力是不如许多人的,如果你想找技术上的攻略心得,去看看别人吧。 😋

在二阶段前

不巧的是,在一阶段开始之后不久,我就遇到了一些生活上的琐事。我很快发现自己没有多少时间可以投入到rCore中了。所幸,因为已经有Rust的经验,速通rustlings也只是一晚上的功夫。

只是,可想而知之后的二阶段恐怕没这么容易了。

ch3:来自Apple的诅咒

训练营的rCore(Guide)其实已经是rCore Tutorial的再度简化版,但即便如此,第一个任务ch3还是打得我措手不及。

此前我对操作系统的理解是泛泛的,大致知道操作系统具备哪些东西,最深入的理解也只是本科课程作业中用C编写的调度算法模拟演示。对于一个真正的操作系统,该从哪里开始都不知道,比如说,在no_std的世界中,stdout从哪里来?怎么在屏幕上渲染出日志?(遗憾的是rCore并没有回答这个问题,SBI完成了这部分)

要一次性消化这么多新东西并不容易,特别是当自己以为已经把Tutorial读得足够清楚了,一打开代码库时依旧觉得无从下手,不得不再次回到Tutorial啃。在这种状况下,我僵持了差不多一个星期才弄清楚ch3究竟应该怎样完成,或者说,代码究竟该写在哪。

然而,在功能完成后,我的代码出现了一个难以理解的错误:

1
Panicked at src/bin/ch3_sleep.rs:16, assertion failed: current_time > 0

有很多人遇到了get_time_ms和get_time_us的精度问题,但我很确定我的问题不属于此类——就算我的电脑再怎么快,get_time也绝对不可能是0。

在反复重写了几个不同的实现方式之后,我开始留意到一个惊愕的事实:“get_time为0”这个错误是否发生,在时间上不均匀。

有的时候随便改动一两行代码,第一次编译可以通过测试,但第二次之后就会遇上这个错误。

在探索到深夜时,我发现在get_time加上一个println,就能把这个错误的发生概率降到零。

Commit, push, CI… Run failed.

那,把这个println删了试试吧。删除之后,在我的机器上又不能通过测试了,但出于好奇,我还是把这个commit推到了远程。

这一次,测试通过了。

这是一个只会在我的机器上出现的错误。

我使用的是QEMU 8,当然也更换了最新的pre-release RustSBI,但这……我确实没看到有其他人遇到这种问题,也无法理解。

那么只剩下一种可能了……Apple Silicon。

ch4:困难模式

虽然已经在ch3得到了教训,我还是没有在第一时间更换Linux虚拟机。我想着,时间已经不多了,同样的问题,或者更诡异的问题出现之后再换虚拟机吧,还是想贪那两秒钟的编译时间。

幸好,那来自Apple的诅咒之后再没发生过。

然而ch4的难度直线上升,尤其是对我而言——我对页表一无所知。在ch4感觉到的压力,还有那种面对代码库不知所措的感觉,比ch3还要强烈得多。

这时我已经萌生了打退堂鼓的想法,ch4无论是知识本身的难度还是任务难度都让我有点招架不住。

不过,最终还是研究明白了ch4两个函数的实现方式,以及需要为它们补上的其他函数,虽然花的时间远比预期中长。

ch5:迟来的句号

ch4完成后,二阶段原定的期限只剩下两天了,我那时还不知道通过的要求已经放宽到了ch5,在周末啃了ch5文档一天之后,我还是决定离开了。

很遗憾,没能和你们一起走下去。

……吗?

在原定的期限一周之后,我发现还有人在冲击着二阶段。

那么,来画上一个真正的句号吧。

ch5的难度倒是下降了不少,大概也是因为已经对代码库更加熟悉了。在ch5上花的时间最多的地方竟然是在cherry-pick以前的代码上,花了不少时间手动处理合并冲突,直到能通过编译和通过BASE=1。至于实现两个功能倒是比较轻松愉快,毕竟,现在已经知道代码要加在哪了。

阶梯

回过头看,在这个仓库上写的代码,好像真的不多。但是,对于一个未曾了解过操作系统本质的人,可以说是打开了新世界的大门。此刻,操作系统已经变得不再神秘,而rCore Tutorial同样也是值得反复精读的教材。

最后,也感谢老师们提供的资源以及帮助。

拖了一周总算把第二阶段的三个实验搞定了。训练营的第一阶段设计本意应该是为了让同学们学习下rust相关知识,一百道练习题也不算难,奈何自己太懒,都是在做题前看一下相关基础知识,做完题可能就忘记了,也未深入学习,因此在第二阶段阅读和编写代码时捉襟见肘。第二阶段是让完成rcore相关的实验,完成实验重要的是理解相关操作系统的概念以及整体代码框架和一些程序接口,而实验中要自己写的内容并不算太多,好多时候都是调用现有接口。如果想对操作系统和一些代码细节有更深一步的理解的话,仅靠这次实验是不够的,有时间的话还是要多看看源码和操作系统相关知识。
最后感谢这次开源操作系统训练营项目的所有贡献者们,训练营的一二阶段已使我收获颇丰,希望能在第三阶段中收获更多知识。

实验1

在实验1中,我们需要实现一个sys_task_info的系统调用,用于获取进程的信息。首先,我们要能够返回task运行的总时间,即第一次执行时间到当前时间的时间差,单位为ms,要实现这个功能,我在TaskControlBlock结构体中添加了我需要的开始时间戳,然后在第一次run该task的时候,记录开始时间戳,然后在sys_task_info中,通过current_task,找到对应的TaskControlBlock,然后计算时间差,返回即可。第二步,能够统计task的sys_call的调用次数,同上,我在TaskControlBlock结构体中添加了我需要的sys_call调用次数,然后在每次调用sys_call的时候,增加该系统调用的调用次数即trap_handler中抓取到系统调用的中断处理时处理即可,然后在sys_task_info中,通过current_task,找到对应的TaskControlBlock,然后返回即可。

实验2

实验2使用 Rust 的全局的动态内存分配器的实现 buddy_system_allocator 完成在内核中使用堆空间。 基于 RISC-V 硬件支持的 SV39 多级页表实现地址空间、物理页的分配与回收。开始划分内核地址空间及应用地址空间,实现应用程序不在需要关注应用起始地址及存放位置。相比上一章节的中断和系统调用处理过程添加了跳板页面,来避免在用户模式进入内核模式或内核模式退回用户模式时导致的地址不一致。
lab2 实验中 lab1 实验的相关内容需要重新实现,因为内存分页后在用户态传递进来的参数地址是虚拟地址,内核的访问地址映射和物理地址一致,无法通过虚拟地址对传递进来的参数赋值,所以需要将虚拟地址转换为物理地址,才能完成赋值。 sys_mmap 的实现参考系统中 insert_framed_area 的实现,添加逻辑校验给定的地址中是否包含已经映射的地址即可。sys_munmap 根据 sys_mmap 的实现反推即可实现。

实验3

在实验3中,ch3 中我学习了多道程序的放置与加载,任务切换,如何管理多道程序。然后在练习中为了实现 sys_task_info 我在原本 TaskControlBlock 的基础上添加了 task_syscall_times 数组和 first_start_time 字段来记录获取 taskinfo 所需信息。在 syscall 中调用自己封装的 add_cuurent_task_syscall_times 来实现对 task_syscall_times 记录更新。而对于 first_start_time,我在程序第一次运行时更新来记录,使得在调用 sys_task_info 时能够准确获得程序的运行时长。

感想

第一次接触结构如此清晰的OS课设,从历史演变的角度一步步搭建操作系统,每章的讲解图文并茂,将内核的功能与结构十分直观地剖析了出来。在遇到问题时老师们的讲解也帮助了我很多。

前言

最近正好在学习Rust,然后在rustcc上看到了相关的信息就想着来参加一下。记录一下整体的学习过程。

第一阶段总结

因为断断续续学习过rust相关的语法,而且以前学校上过C++的课程,所以对于一些概念上的东西理解还是比较容易上手,但是因为工作了好几年,平时的工作代码量不高。所以第一阶段做一些概念上的test还可以。到了第二阶段刚开始有点捉襟见肘。

第一阶段总结

学校的操作系统以理论偏多,而且当时确实没有特别好的实验指导,记得当时还花重金买了一本《操作系统真相还原》,但是由于一些原因没有做下去。这次正好补上。
环境为了方便,就直接选择了docker,但是docker有个问题就是gdb调试的时候每次都要打开一个窗口,要有一点docker和tmux的基础。

Chapter 1

第一章讲述了怎么写一个 Bare-metal 应用,涉及到了一些内存布局、汇编、链接器之类的知识,而且上学时候还是有好好学习的,所以压力不大,对于一些不懂的概念可以结合BlogOS,但是要注意一些细节。

Chapter 2

第二章,主要引入了特权级的概念,以前也知道这个,但是具体的是怎么实现的还真没有相关的了解。重点在于串起来整个应用的启动流程,先通过汇编指令在固定位置加载内核代码。内核代码中完成应用初始化后,最后再通过一个trap上下文来跳到应用态。整体因为不涉及地址空间所以还是比较简单。

Chapter 3 & Lab 1

第三章,引入了分时多任务,出现了任务切换的概念,核心在于任务切换是在两个 Trap 控制流间跳转,达到切换应用的目的。同时也引入了时钟中断,时钟中断主动在trap中处理切换。还有一个点就是用户态的时候,S的时钟中断是无法屏蔽的。

实验1,这个实验其实是个人感觉前三个实验最难的,当时想了好几天,一直想精确的统计应用启动的时间,甚至想过通过寄存器的值来区别应用是否被切换了,但是没有成功,最后通过和群里的同学交流后,知道了不需要很精确。就直接在 TCB 里面加启动时间字段,初始化为0,在切换的时候在加一个启动时间的判断,注意第一个应用的启动时间可以直接在first_task中设置。syscall个数通过数组计数即可。

Chapter 4 & Lab 2

第四章,引入了页表。但这个其实当时上学理解的比较透彻,所以还是比较容易的,内核只有一个内核应用空间,内核空间创建的时候就对整个剩余可用的其中几个空间做了一个恒等映射,设置完之后启动页表硬件设置,然后平滑迁移的过程以及跳板的设计是比较精妙的,还有就是初始化为0的静态/全局变量也默认在.bss段。

实验二,没有理解在指针指向的数据跨页的情况。个人理解即使数据跨页,变量在分配的时候虚拟地址是连续的,物理地址足够的情况下也应该是连续分配的,test没做相关的考虑。

mmap和unmap分配内存主要在于增加限制条件的判断,最开始没有注意启动位置要对齐,导致用例不过,其他的没有什么问题。

Chapter 5 & Lab 3

第五章,引入了进程,了解到了 “idle task”。任务切换每次都要到 idle 控制流然后又从idle控制流通过调度算法切换到新任务。

实验三 参照fork和exec的代码即可。

结语

参加本次训练营真的受益匪浅,把os的理论和实践结合起来真的很有意义。因为平时还要工作,已经超过了第二阶段上课时间一周才做完前三个实验,后面努力做完剩余两个实验。感谢本次训练营的老师,能提供这么优秀的课程和资源!!!

前言

我是一名工作了五年的社畜,先后用过php、go语言开发应用端的程序,尽管接触过一些操作系统的知识,但都是二手资料,只了解个大概。

我对问题都会追本溯源,想了解得更深一点,这样才能更好的总结归纳。

工作中用的很多工具以及知识底层都涉及到操作系统,只有了解了操作系统内核才能更好的理解整个计算机体系结构。

国庆节前在网上冲浪时,无意中看到Rust语言中文社区有关于开展开源操作系统训练营的公告,了解后,竟然是免费教程,太让人惊喜。

机不可失失不再来就立马报名参加了。

第一阶段rustlings 100道题

这一阶段的题相对比较简单,我花了一周的时间就刷完了。但也发现了自己的盲点,比如高级生命周期这块,是从没了解过。

顺带一提,我承接了队长的工作,解答队员不懂的知识点,队员都很争气,基本都没这么问我,都能完成rustlings100道题,到第一阶段截至,8名队员有7名晋级,
还有一名太忙了,没时间做,也不参加后续的了。

第二阶段 r-Core内核

第零周

也就是完成rustling之后,我开始摸索搭建实验环境的搭建和看第零章的内容,到第一周开始,环境已经搭好和看到第一章

第一周

发现第三章才是第一个实验,奋起直追,主要的时间都发在阅读文档中,我看的是详情书,看到周六才到第三章末。

周六才是完成实验,实验不是很难,主要在于理解两个异常控制流的转换,all_trap和switch,理解特权级切换硬件都干了什么,软件如何衔接。

最终在周日完成了第一个实验。

第二周

第四章比前面三章要理解的东西都多,而且也很难。可能对于我来说,操作系统这块内容比较陌生,没有一个概念,做起来就有点畏手畏脚。

如何将用户态的一个变量赋值,这个变量还可能分散到两个实际不相连的物理页,实在想不到如何下手。群里的大佬支招,让我把结构体的字段拆开分别赋值,
这个方法可行,但不通用,每个结构体字段都不一样,每个都要根据它的字段分别赋值,很麻烦。

最终我想到的方法是,写一个通用的方法,根据用户态的指针查到对应物理页面,在内核态实例化一个和用户态一样对象,转化成字节数组,将字节数组拷贝到物理页面中。
这个方法收到拷贝应用数据的方法的启示,我不用管对象是什么,只需要当成字节数组来看,逐页拷贝就行。

只要理解了虚拟地址空间和物业页帧之间的关系,mmap和munmap都是比较好实现。

第三周

前面两周只做到了实验2,接下来的一周虽然很快看完了第五章,但是完全没有时间完成实验,只能到周末完成。

实验3相对于实验2算比较简单的,前面抽象的概念都已经展开,实验3只不过增加了fork、exec之类的系统调用,用于生成新的进程。

spawn只需要获取pid和从elf中获取堆栈等信息,组装成一个taskControlBlock,并加入到任务队列即可。

stride算法的实现需要扩充两个字段stride和priority,并在每次进程切换时stride加上pass,pass= big_stride/priority。之后是修改任务管理器的fetch方法,
实现fetch能获取最小stride进程,我将VecQueue改成LinkedList,为了好从中间去掉某个节点。

总结

  • 在这5周内,学习到很多知识,以前很模糊的点现在已经清晰了,感谢训练营给了这个机会
  • 因为是打工人,上班完全没法摸鱼,学习的时间不是很多,很多时候都是在地铁上,休闲时间里来完成实验。但这五周都过得很充实,尽管也很忙
  • 接下来就要进入第三阶段,希望能学到更多的知识

前言

前前后后入了几次 Rust 的门,都以失败告终。这学期课排得少,很闲,一开始是选择做 jyy 的 OS 实验,后来在菜鸡取暖群中看到这个训练营的信息,遂跑路开始做这个。
第一阶段的话,因为以前做过 Rustlings 做到差不多一半,所以还是比较轻松的,然后就开始摸鱼了(逃)

第二阶段总结

学校的操作系统课教得比较水,而且以理论偏多,也有我自己不认真学的原因,最后效果比较差。能从理论和代码层面逐渐实现一个操作系统,对我来说是一次崭新的体验。现在磨洋工磨了三个周做完三个实验之后,作关于第二阶段的总结。

一开始配环境的时候碰了点壁,当时想在 Arch 下面做,但是 Qemu 太新了,旧版源码也编译不了,最后还是重回 WSL 的拥抱…

Chapter 1

第一章讲述了怎么写一个 Bare-metal 应用,涉及到了一些内存布局、汇编、链接器之类的知识,好在之前看 CSAPP 是看过相关内容的,所以压力不大。不过一上来面对这些东西,对一些概念还是有些模糊或是错误理解。比如内核栈其实一直都是放在 .bss 段的,我老以为它在别的地方,这一点我直到第三章才弄清楚…

关于脱离操作系统和标准库的依赖来写一个程序这件事,心里却没有太大波澜,因为我之前也看过一点 BlogOS, 那个是在 x86 上实现的。接触一个较新的体系结构对我来说挑战会更大一些。

Chapter 2

第二章,批处理系统。看到这个名词我脑海里浮现的就是那种几十年前的大型机…不扯别的了。这一章的重点是特权级和 Trap 的管理, 这部分的汇编确实得慢慢消化。在看最后一节的前半部分的时候我都在想,要怎么执行第一个应用呢,我的最初想法就是直接把 pc 跳到初始地址,其它的寄存器该保存保存,也不需要转换特权级。看到最后,通过构造特殊的 Trap 上下文来使程序运行的做法给我留下很深印象,这也是特权级的意义所在,用户态就该乖乖跑程序,切换应用是内核该做的事。 (主要当时忽略了内核开始运行时是在 S 态下的,不 __restore 也进不去 U 态)
批处理虽然比较过时了 (个人理解…), 但是这里涉及到了重要的概念,给后面的章节做下铺垫。

Chapter 3 & Lab 1

第三章,从批处理变成了分时多任务,这一次多了任务切换的概念,在两个 Trap 控制流间跳转,达到切换应用的目的。同时也引入了时钟中断,感觉和之前学过的理论知识串起来了。

实验一,这个的实现就是在 TCB 里面加字段,加一个返回相应字段的函数,并在调用 syscall 的时候加个计数,没了,还是比较简单的。

Chapter 4 & Lab 2

第四章,引入了页表。我在这里头疼了很久。页表的设计很巧妙,同时也是在这一章,编程语言带来的优势体现得淋漓尽致。比如同样是 usize, 但是都可以包一层变成不同的类型 (VirtAddr, PhysPageNum, etc…),借助 Trait 机制还可以随意转换。虽然 C 也有 typedef, 但是按这样写的话,代码肯定会相当混乱;Drop Trait 相当好用,RAII 比手动释放资源不知道高到哪里去了。写到这里,Rust 的高贵已经尽数体现了

这一章与上一章不同的是,因为引入了虚拟地址空间,所以在切换任务的时候页表也得一起换;内核和应用地址空间的设计也经过了精心规划,比如跳板的设计等等,以及在内核空间创建的时候就对整个剩余可用的内存段建了一个恒等映射,所以在内核里不用担心地址找不到的问题,很是巧妙。同时,在这一章的代码实现中出现了多个平滑的表述,让我意识到操作系统的编写需要足够细心,能洞察对切换前后系统的状态。尤其是这种涉及 xxx 切换的部分,写出 Bug 很难查。

实验二,自我感觉我的实现很丑陋。先谈重写上个实验两个系统调用的事情,现在是要对一(或两)个 [u8] 上操作。在指针指向的数据跨页的情况下我是真不知道咋办,感觉这种时候可能 C 语言更好用些。 (不过不写也能过用例, 所以 sys_task_info 的我就鸽了)

分配内存的那两个调用,因为限制条件很多,体现在代码上就是一堆 if. 以及 unmap 的实现感觉有点低效,每删一页都要找一下在哪个逻辑段,但也想不到更好的办法了。这里还被小坑了一手,find_pte 就算返回了一个 Some 也不一定有效,还得手动 is_valid 一下。

Chapter 5 & Lab 3

第五章,任务变成了进程,这里开始有一点点知识盲区了,第一次知道 exec 和 “idle task” 的作用。idle 控制流就是不断循环取任务执行,每次有应用交出 CPU 使用权时,就会切换到 idle 控制流并开启新一轮的任务调度,调度算法就可以在这里做文章了。

实验三,代码框架提供的函数非常够用,所以很快就做完了。 (除了一开始忘记维护父子关系有点难绷)

结语

参加本次训练营让我受益匪浅,我是越来越喜欢 Rust 了(同时也越来越不喜欢 Java…),同时对操作系统也有了更立体的认识。写到后面,随着抽象程序越来越高,感觉和写一个普通的程序差别不大了。能优雅地构建一个精密的系统是一件多么令人愉悦的事!

这一次因为前面太偷懒了,用了多达三个周的事件才草草做完三个实验。后面的时间我会做完剩余两个实验,并尽力完成第三阶段的任务。

感谢参与本次训练营的老师和工作人员,谢谢你们!

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

Lab1

Lab1要实现获取任务的信息,包括任务使用的系统调用及调用次数、系统调用时刻距离任务第一次被调度时刻的时长,实验内容比较简单,但是本章的知识点却是后几章的基础,需要深刻理解。因此关于Lab1的总结主要分为两部分:

  1. 操作系统特权级切换及任务切换
  2. 实验完成思路

    特权级切换及任务切换

    特权切换

    对于rCore,特权切换发生在系统调用和中断前后,如下图所示:

要实现特权级的切换,需要软硬件协同工作,主要包括两部分:

  1. Trap上下文的保存与恢复
  2. CSR寄存器的设置
    对于Trap上下文的保存与恢复,主要有以下几步:
  3. 系统初始化的时候,修改 stvec 寄存器来指向正确的 Trap 入口
  4. 发生trap时,进入入口函数__alltraps,它做了以下几件事:
    1. 将栈指针指向内核栈
    2. 在内核栈保存通用寄存器和CSR寄存器
    3. 调用trap_handler
  5. 调用完成后,调用__restore,它做以下几件事:
    1. 内核栈顶的 Trap 上下文恢复通用寄存器和 CSR
    2. 将栈指针指向用户栈
    3. sret返回用户态

而任务切换不涉及特权级切换,是在进入到Trap之后,进一步进行的,主要有以下几步:

  1. A 在 A 任务上下文空间在里面保存 CPU 当前的寄存器快照
  2. B 任务上下文,根据 B 任务上下文保存的内容来恢复 ra 寄存器、s0~s11 寄存器以及 sp 寄存器

实验思路

我们需要实现系统调用:

1
fn sys_task_info(ti: *mut TaskInfo) -> isize

其中:

1
2
3
4
5
struct TaskInfo {
status: TaskStatus,
syscall_times: [u32; MAX_SYSCALL_NUM],
time: usize
}

我们需要获得syscall_times和time。

  1. syscall_times: 需要在任务控制块中加入用以记录系统调用及其次数的字段,并且暴露出一个可以更新这个字段的方法,然后在syscall函数里面,通过调用更新方法,维护调用次数。
  2. time:需要在任务控制块中加入用以记录时间的字段,每次调用某任务前,首先查看是不是第一次调用该任务,若是,则记录当前时间。

Lab2

目前的操纵系统存在两个问题:

  1. 用户程序可以通过物理内存修改其他用户程序的代码数据甚至内核的代码数据,这是非常不安全的。
  2. 此外,由于用户程序的加载强依赖于物理地址,因此我们在编写用户程序的时候就需要考虑程序在物理内存中的布局以防止和其他用户程序和内核程序之前的重叠,这给用户程序编写者带来了极大的不便。
    为了解决这两个问题,引入虚拟内存。对于虚拟内存如何翻译到物理内存,这部分比较简单,略过。对于OS代码中如何建立地址空间,着重讲一下,因此关于Lab2的总结主要分为两部分:
  3. 地址空间
  4. 实验完成思路

    地址空间

    如图为应用地址空间

每一段我们抽象成一个MapArea,整个地址空间我们抽象成一个MapArea的数组。因此,建立地址空间就是建立一个个MapArea,然后push到数组里面。在这一章里,有一个很重要的细节点,就是RAII的思想,可以说是贯穿了操作系统内存管理的始终。

实验完成思路

我们需要实现系统调用:

1
fn sys_mmap(start: usize, len: usize, port: usize) -> isize
  1. 首先需要检查错误
  2. 因为memory_set是暴露出来的,我们可以直接访问memory_set里面的相关方法进行一段连续内存的映射

对于取消申请的系统调用,思想同上。

1
fn sys_munmap(start: usize, len: usize) -> isize

Lab3

这一章的重点是如何新建一个进程

  1. 进程生成机制:这主要是指 fork/exec 两个系统调用。
  2. 进程资源回收机制:当一个进程主动退出或出错退出的时候,在 exit_current_and_run_next 中会立即回收一部分资源并在进程控制块中保存退出码;而需要等到它的父进程通过 waitpid 系统调用(与 fork/exec 两个系统调用放在相同位置)捕获到它的退出码之后,它的进程控制块才会被回收,从而该进程的所有资源都被回收。
    关于Lab3的总结主要分为两部分:
  3. 进程生成
  4. 实验完成思路

进程生成

在内核中手动生成的进程只有初始进程 initproc ,余下所有的进程都是它直接或间接 fork 出来的。当一个子进程被 fork 出来之后,它可以调用 exec 系统调用来加载并执行另一个可执行文件。因此, fork/exec 两个系统调用提供了进程的生成机制。

fork

fork主要做了以下几件事:

  1. 复制父进程地址空间
  2. 得到trap上下文物理地址页号
  3. 分配pid及内核栈
  4. 创建进程控制块
  5. 添加子进程至父进程进行管理
  6. 修改trap上下文中的kernel_sp使其指向新建的内核栈

exec

exec主要做了以下几件事:

  1. 从elf新建一个地址空间
  2. 得到trap上下文物理地址页号
  3. 更新地址空间
  4. 更新trap上下文物理地址页号
  5. 更新entry_point, user_sp

实验思路

实现系统调用

1
fn sys_spawn(path: *const u8) -> isize

结合exec和fork方法,糅合即可。

实现系统调用

1
2
3
4
5
// syscall ID:140
// 设置当前进程优先级为 prio
// 参数:prio 进程优先级,要求 prio >= 2
// 返回值:如果输入合法则返回 prio,否则返回 -1
fn sys_set_priority(prio: isize) -> isize;

只需在任务控制块里添加prio、pass、stride字段,将TaskManager里的VecQueue换成BinaryHeap,然后自定义任务控制块的比较方法,使其按照stride进行比较。然后自定义stride的比较方法,以处理溢出问题即可。

总结

学习rCore使我认识到,不论是rust还是操纵系统,我都还有很长的学习之路要走;写总结报告使我认识到,将一个论点清晰地展示给别人,是一件难度不亚于习得该论点的事情。如何高效学习,再如何简洁明了地教给别人,值得我长时间的思考。