拖了一周总算把第二阶段的三个实验搞定了。训练营的第一阶段设计本意应该是为了让同学们学习下rust相关知识,一百道练习题也不算难,奈何自己太懒,都是在做题前看一下相关基础知识,做完题可能就忘记了,也未深入学习,因此在第二阶段阅读和编写代码时捉襟见肘。第二阶段是让完成rcore相关的实验,完成实验重要的是理解相关操作系统的概念以及整体代码框架和一些程序接口,而实验中要自己写的内容并不算太多,好多时候都是调用现有接口。如果想对操作系统和一些代码细节有更深一步的理解的话,仅靠这次实验是不够的,有时间的话还是要多看看源码和操作系统相关知识。
最后感谢这次开源操作系统训练营项目的所有贡献者们,训练营的一二阶段已使我收获颇丰,希望能在第三阶段中收获更多知识。
CoreAttacker-2023秋季OS训练营第二阶段总结
实验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课设,从历史演变的角度一步步搭建操作系统,每章的讲解图文并茂,将内核的功能与结构十分直观地剖析了出来。在遇到问题时老师们的讲解也帮助了我很多。
2023开源操作系统训练营第二阶段总结报告——fakerpawnno1
前言
最近正好在学习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的理论和实践结合起来真的很有意义。因为平时还要工作,已经超过了第二阶段上课时间一周才做完前三个实验,后面努力做完剩余两个实验。感谢本次训练营的老师,能提供这么优秀的课程和资源!!!
2023开源操作系统训练营第二阶段总结报告-庄广
前言
我是一名工作了五年的社畜,先后用过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周内,学习到很多知识,以前很模糊的点现在已经清晰了,感谢训练营给了这个机会
- 因为是打工人,上班完全没法摸鱼,学习的时间不是很多,很多时候都是在地铁上,休闲时间里来完成实验。但这五周都过得很充实,尽管也很忙
- 接下来就要进入第三阶段,希望能学到更多的知识
2023秋冬季开源操作系统训练营第二阶段总结-EastMonster
前言
前前后后入了几次 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开源操作系统训练营一二阶段总结-zhang722
2023开源操作系统训练营一二阶段总结
Lab1
Lab1要实现获取任务的信息,包括任务使用的系统调用及调用次数、系统调用时刻距离任务第一次被调度时刻的时长,实验内容比较简单,但是本章的知识点却是后几章的基础,需要深刻理解。因此关于Lab1的总结主要分为两部分:
要实现特权级的切换,需要软硬件协同工作,主要包括两部分:
- Trap上下文的保存与恢复
- CSR寄存器的设置
对于Trap上下文的保存与恢复,主要有以下几步: - 系统初始化的时候,修改 stvec 寄存器来指向正确的 Trap 入口
- 发生trap时,进入入口函数__alltraps,它做了以下几件事:
- 将栈指针指向内核栈
- 在内核栈保存通用寄存器和CSR寄存器
- 调用trap_handler
- 调用完成后,调用__restore,它做以下几件事:
- 内核栈顶的 Trap 上下文恢复通用寄存器和 CSR
- 将栈指针指向用户栈
- sret返回用户态
而任务切换不涉及特权级切换,是在进入到Trap之后,进一步进行的,主要有以下几步:
- A 在 A 任务上下文空间在里面保存 CPU 当前的寄存器快照
- B 任务上下文,根据 B 任务上下文保存的内容来恢复 ra 寄存器、s0~s11 寄存器以及 sp 寄存器
实验思路
我们需要实现系统调用:
1 | fn sys_task_info(ti: *mut TaskInfo) -> isize |
其中:
1 | struct TaskInfo { |
我们需要获得syscall_times和time。
- syscall_times: 需要在任务控制块中加入用以记录系统调用及其次数的字段,并且暴露出一个可以更新这个字段的方法,然后在syscall函数里面,通过调用更新方法,维护调用次数。
- time:需要在任务控制块中加入用以记录时间的字段,每次调用某任务前,首先查看是不是第一次调用该任务,若是,则记录当前时间。
Lab2
目前的操纵系统存在两个问题:
- 用户程序可以通过物理内存修改其他用户程序的代码数据甚至内核的代码数据,这是非常不安全的。
- 此外,由于用户程序的加载强依赖于物理地址,因此我们在编写用户程序的时候就需要考虑程序在物理内存中的布局以防止和其他用户程序和内核程序之前的重叠,这给用户程序编写者带来了极大的不便。
为了解决这两个问题,引入虚拟内存。对于虚拟内存如何翻译到物理内存,这部分比较简单,略过。对于OS代码中如何建立地址空间,着重讲一下,因此关于Lab2的总结主要分为两部分: - 地址空间
- 实验完成思路
地址空间
如图为应用地址空间
每一段我们抽象成一个MapArea,整个地址空间我们抽象成一个MapArea的数组。因此,建立地址空间就是建立一个个MapArea,然后push到数组里面。在这一章里,有一个很重要的细节点,就是RAII的思想,可以说是贯穿了操作系统内存管理的始终。
实验完成思路
我们需要实现系统调用:
1 | fn sys_mmap(start: usize, len: usize, port: usize) -> isize |
- 首先需要检查错误
- 因为memory_set是暴露出来的,我们可以直接访问memory_set里面的相关方法进行一段连续内存的映射
对于取消申请的系统调用,思想同上。
1 | fn sys_munmap(start: usize, len: usize) -> isize |
Lab3
这一章的重点是如何新建一个进程
- 进程生成机制:这主要是指 fork/exec 两个系统调用。
- 进程资源回收机制:当一个进程主动退出或出错退出的时候,在 exit_current_and_run_next 中会立即回收一部分资源并在进程控制块中保存退出码;而需要等到它的父进程通过 waitpid 系统调用(与 fork/exec 两个系统调用放在相同位置)捕获到它的退出码之后,它的进程控制块才会被回收,从而该进程的所有资源都被回收。
关于Lab3的总结主要分为两部分: - 进程生成
- 实验完成思路
进程生成
在内核中手动生成的进程只有初始进程 initproc ,余下所有的进程都是它直接或间接 fork 出来的。当一个子进程被 fork 出来之后,它可以调用 exec 系统调用来加载并执行另一个可执行文件。因此, fork/exec 两个系统调用提供了进程的生成机制。
fork
fork主要做了以下几件事:
- 复制父进程地址空间
- 得到trap上下文物理地址页号
- 分配pid及内核栈
- 创建进程控制块
- 添加子进程至父进程进行管理
- 修改trap上下文中的kernel_sp使其指向新建的内核栈
exec
exec主要做了以下几件事:
- 从elf新建一个地址空间
- 得到trap上下文物理地址页号
- 更新地址空间
- 更新trap上下文物理地址页号
- 更新entry_point, user_sp
实验思路
实现系统调用
1 | fn sys_spawn(path: *const u8) -> isize |
结合exec和fork方法,糅合即可。
实现系统调用
1 | // syscall ID:140 |
只需在任务控制块里添加prio、pass、stride字段,将TaskManager里的VecQueue换成BinaryHeap,然后自定义任务控制块的比较方法,使其按照stride进行比较。然后自定义stride的比较方法,以处理溢出问题即可。
总结
学习rCore使我认识到,不论是rust还是操纵系统,我都还有很长的学习之路要走;写总结报告使我认识到,将一个论点清晰地展示给别人,是一件难度不亚于习得该论点的事情。如何高效学习,再如何简洁明了地教给别人,值得我长时间的思考。
2023开源操作系统训练营第二阶段总结报告-14432222
2023开源操作系统训练营第二阶段总结报告-14432222
实验总结
lab1
本次实验要求我们实现一个sys_task_info来获取进程的信息。最开始的时候我的设计是在TASK_MANAGER中存放两个桶分别存放每项任务的调用次数和起始时间,查询运行时间时有当前时间减去起始时间即可得到系统调用时间。但由于调用次数会变化,而TASK_MANAGER是个静态变量。遂放弃调用次数的桶转而在TCB结构体中存放记录。
lab2
本次实验要求重写sys_get_time 和 sys_task_info,同时实现虚拟内存和物理内存的绑定与解绑。
由于虚拟储存机制的引用我选择了重写一个get_kernel_ptr的泛型函数获取物理内核中的指针。但是由于虚拟缓存机制的影响导致我的lab1中的起始时间桶出现了一些奇奇怪怪的问题,我又含泪放弃lab1的起始时间桶,选择把起始时间封装到TCB中。
lab3
万恶之源!!!和前面两个lab兼容性极差,刚切过去的时候让我怀疑人生,我lab2的代码根本找不到原本放置的位置。我需要重新找位置来存放一些函数。又让我重构了大部分lab2的代码。优先级设置较为简单,但还是出了点问题。最开始我是选择在任务队列里按顺序插入,fetch任务时在遍历队列找到stride最短的任务,但是我本地测试电脑风扇轰鸣了二十分钟也没出结果。经过参考其他大佬们的总结我决定在任务进入的时候按照stride长度进行插入,提前将队列有序。重构了代码之后才通过。
个人感想
本次的rcore总结下来并不难,但在写的时候还是挺崩溃的。一是虽然有一阶段的rust语言的学习,但对于多个文件协同运作对我来说还是比较艰难的。二是由于对于测试方式的不太理解,导致我无法通过gdb进行调试,只能通过不断的打印信息来获取bug原因。三是几个lab的兼容较差,需要我们自行向前,没有类似于一些公开课的项目那种前面模块的测试通过之后后续基本可以不用改动前面模块的模式,心智负担极大,希望后续能有所改进。
最后很感谢训练营能为我们提供了这么一个学习平台,让我们有机会接触到操作系统比较底层的内容。让我们对操作系统有了更深刻的体会。
实验总结报告
背景
我本人特别看好Rust语言及RISCV指令集架构的,所以也经常会查找一些相关的信息。在看到这次训练营之前,我在网上关注到了rCore的教程。之前也自己跟着教程学习过一段,那次是只完成了前两章,实现了在终端打印彩色字符串。和本次做实验不同的是那次是从 cargo new r_core_study
开始的。所以那时候看到彩色的字符串输出,还是很有成就感的。
这是我之前学习的仓库连接:https://github.com/aLingYun/r-core-study
第一阶段
第一阶段的难度还好,可能是因为我之前有学习过Rust,所以前大半部分的题很快就做完了。后面涉及到Rust的一些复杂特性,做起来要慢很多。需要不断的查教程,理解教程。也是很高效的学习过程。
第二阶段
由于工作关系,第二阶段的第一周基本上什么也没有做。第二周也基本上只有每天晚上十点之后可以学习。
由于时间不较短,我的策略是先看实验题目。以实验题目为目标去看教程,所以速度还可以,但是对知识的理解程度应该会差一些。
完成lab1之后又陷入没有时间学习的窘境,知道第二周周末,我跟让我的家人带孩子。自己全身心的去做剩下的lab2和lab3两个实验。
那两天每天都是到凌晨1点以后。终于还是勉强完成code了,不过实验报告再也没时间写了。
总结
通过这次训练营的一二阶段,对Rust的理解加深了,尤其是对Rust在底层开发的应用。
之前自学时,第三阶段开始感觉到困难,一直没有过这关。这次训练营帮助我跨了这关,所以真的感谢各位老师的辛苦付出。
rcore2023_learning_blog
2023rcore第二阶段学习总结和个人与计算机系统的漫游
初识
与 rcore开源操作系统训练营 的相识,算是一个很偶然的机会吧。我与计算机结识很晚,在我上大学后,才从一个对计算机连打字都不会的人到慢慢熟练使用以及熟悉各种技术的人。与操作系统(Linux)结识,是大一下学期的 计算机系统基础课(教程是那本鼎鼎大名的 深入了解计算机系统),那节课开启了我Linux的漫游旅途。
兴趣
在我刚接触计算机的时候,一直认为开发出一个web网站或者APP,就是一件特别特别酷的事情。在整个刚接触计算机的事情,写出一个web网页或者APP便是我一直想要做的事。但后来,大一结束的暑假,学习了一些这方面的技术,扒开了web的真实面目,便慢慢失去了很多兴趣,曾经很酷的事情,突然感觉很无味了。所幸的是,在这个时候,学校的 OS 课开了,杨老师是一名非常知识渊博、热爱体系结构的老师,他 OS 第一门课留给我们的作业便是
下载linux内核源码,并往内核中添加自定义系统调用
这算是我开启了我正式与操作系统内核接触的旅途。永远无法忘却第一次下载linux内核源码,然后编译的时候,满屏报错的电脑界面,特别是每次编译的时候,都会让我等待很久,几乎每次都是编译了三十多分钟,然后给我报错,如此循环往复……最后终于把代码编译完成。第一次进入kernel 目录下,进入代码里面,映入眼帘的是 Linus Torvalds 的大名,那是我第二次那么激动(第一次激动的时候是第一次敲出 “Hello, World”)。最后在各种操作之下,各种文件之间来回修改的条件下,我终于让自己自定义的一个系统调用成功运行了起来,那一瞬间,像是打开了潘多拉的魔盒,从此我开始对体系结构、操作系统方向的东西产生了很大兴趣,便也萌生了写一个OS的想法,从此整个想法,便一直根深蒂固着。
遇见rcore
诚恳的说,我是因为心中那个根深蒂固的想法才会有机会遇见rcore,刚开始的时候,我其实知道的是 ucore, 后来因为个人非常喜欢c++,而某段的时间里,网上的各样信息都在告诉我 Rust 是c++的强大竞争者,Rust 是如何的安全,如何的高效。便萌发了我对这一门新型语言的兴趣。
Rust:
第一次用Rust的时候,它的cargo便惊艳了我很久,用c++的时候,每次安装第三方包,亦或是换个平台,编译东西,都会让我折磨很久,总是在各种编译器之间的实现困惑,msvc有的特性,在gcc有时候却无法运行,有时候在gcc能够运行的东西,在clang也无法运行。同样让人痛苦的时候,c++20/c++23都出了很久了,但是不同编译器的支持却是层出不穷……。换到Rust,突然很多东西便让人清爽了许多。也便逐渐开始了学习Rust的旅途。
risc-v:
对于risc-v的了解,在开始rcore之前,我也只知道它是开源的,文档内容少(远没有X86和Arm那样内容复杂和繁冗)。后来了解了一下龙芯,一生一芯等,便也对risc-v有了极大兴趣,恰逢此时,rcore便出现在了我面前。
rcore:
rcore Book 的娓娓道来,特别是以各种史前生物 来描述,增加了一番故事书的趣味。而Guide则能够快速地让我明白了代码地框架,每个文件,每个模块是什么样的功能。但无奈个人基础不好,所以大部分时间还是在看 Book。本次实验让我们实现操作系统核心的几个重要功能:
- 多到程序与分时多道任务
- Lab1 需要完善系统调用。对于 sys_task_info 系统调用,我们在 TCP 添加相应字段处理即
1
2
3
4
5
6
7
8
9
10
11pub fn sys_task_info(ti: *mut TaskInfo) -> isize {
unsafe{
*ti = TaskInfo{
status:get_current_status(),
syscall_times:get_syscall_times(),
time : (get_time_us() - get_current_start_time()) / 1000
};
}
0
}
虚拟内存管理
这部分的内容中,为 Rcore 引入了虚拟内存,为地址空间加上了一层抽象,# 地址空间
刚学计算机的时候,个人非常总喜欢将所有代码放在一个文件里,觉得分开各种代码很是麻烦。后来因为学习深入,开始对分离代码,抽象多了很多体会。特别是在学计算机网络的TCP/IP模型和操作系统的时候,对抽象,加层的思想确实是不断体会,不断明白了那句话“在计算机中,没有什么是不能加一层解决不了的”。现在来好好感受在ch4中的抽象加一层。为什么要添加一层抽象层:
- 从应用开发的角度看,需要应用程序决定自己会被加载到哪个物理地址运行,需要直接访问真实的物理内存。这就要求应用开发者对于硬件的特性和使用方法有更多了解,产生额外的学习成本,也会为应用的开发和调试带来不便
- 从内核的角度来看,将直接访问物理内存的权力下放到应用会使得它难以对应用程序的访存行为进行有效管理,已有的特权级机制亦无法阻止很多来自应用程序的恶意行为。
该抽象层要完成的目标:
透明 :应用开发者可以不必了解底层真实物理内存的硬件细节,且在非必要时也不必关心内核的实现策略, 最小化他们的心智负担;
高效 :这层抽象至少在大多数情况下不应带来过大的额外开销;
安全 :这层抽象应该有效检测并阻止应用读写其他应用或内核的代码、数据等一系列恶意行为。
进程管理
- 对于进程、程序、可执行文件等的了解更加深入了
- 进程是在操作系统管理下的程序的一次执行过程,程序是一个静态的概念。
- 可执行文件是一张“蓝图”:一张编译器解析源代码之后总结出的一张记载如何利用各种硬件资源进行一轮生产流程的 蓝图
- 加载同一个可执行文件的两个进程也是不同的:它们的启动时间、占据的硬件资源、输入数据均有可能是不同的,这些条件均会导致它们是不一样的执行过程。
- 对于创建进程需要fork()和exec()两个系统调用而不只是一个系统调用。两个组合更加灵活,fork是为了 exec 一个新应用提供空间,然后exec可以读取不同的elf文件,执行不同的操作。
- 对于进程、程序、可执行文件等的了解更加深入了
文件系统(未完待续)
并发(未完待续)
但很可惜,因为个人基础和时间还有其他各种各样的原因,个人并没有完成五个实验,前面三个实验也只是勉强完成(虽然运行过了,但还是有很多东西之间还不明白)。接下来的时间,我将好好把先前没有弄明白的知识点再好好梳理一遍。并将继续做完还没有先前没有做完的工作。向训练营各位优秀的同学学习,以后要多写博客,多写博客(这次学到的一个优秀习惯),及时梳理知识。纸上得来终觉浅,绝知此事要躬行!!!。
2023开源操作系统训练营第二阶段总结报告-lwshang
rCore OS 学习总结
本次训练营是 Rust 与操作系统的有机结合。我对 Rust 已经足够熟悉,因此侧重操作系统部分的第二阶段更加吸引我。
从一个裸机程序开始,随着需求的复杂化,我们需要逐步添加各种系统调用的实现,最终完成了一个麻雀虽小五脏俱全的操作系统。
1. 应用程序与基本执行环境
操作系统和应用一样都是软件程序。区别在于,操作系统需要几乎直接与硬件交互,不像普通应用程序可以使用标准库提供的各种功能,毕竟标准库构建在系统调用上,而系统调用又是由操作系统提供的。
在操作系统与硬件之间还有一层 SBI (Supervisor Binary Interface)。在后续的编程实验中,我们直接使用编译好的 RustSBI BootLoader 二进制文件。
RustSBI 规定了 OS 在内存中的位置,那么编译 OS 时需要调整链接器使其生成符合要求的内存布局。
2. 批处理程序
操作系统最主要的作用就是运行应用程序,而且往往是多于一个应用。一种最直接的想法是把操作系统和多个应用打包在一起,输入计算机后,依次执行每一个应用。
我们希望当应用出错时,操作系统可以继续执行剩余的其他任务。由此特权级机制被引入。
3. 多道程序与分时多任务
一个一个运行应用不够灵活,我们希望能够对多任务进行调度。那么必然就需要能够支持任务切换,即保存/加载上下文。
调度则主要有两种情况:一是任务主动让出处理器,sys_yield
;二是通过时钟中断定时触发任务的切换。
4. 地址空间
操作系统和应用全都直接访问物理地址通常是低效的。引入虚拟地址机制,让每个应用都拥有逻辑上连续的大量内存,可以使应用的编写更加灵活。
RISC-V 64 平台采用了 SV39 多级页表机制,通过 satp
CSR 来控制是否启用。
5. 进程及进程管理
此前,所有任务都是操作系统直接管理的。我们很自然的会希望可以由任务来创建子任务,并且可以管理更多物理/虚拟资源。于是引入进程的概念。
理解进程,最核心的就是相关的系统调用:fork
, exec
和 waitpid
。
6. 文件系统与I/O重定向
文件可代表很多种不同类型的I/O 资源。狭义的文件系统可以对持久存储设备 (Persistent Storage) I/O 资源进行管理。
这一节,我们第一次尝试将一组功能从内核中分离出来成为独立的库,交由内核引入使用。
7. 进程间通信
进程间交换数据大大加强了程序的能力。实现进程间通信的主要方式之一是管道。
8. 并发
操作系统通过不断切换任务实现并发,因为进程间资源是相对隔离的,这种并发容易实现但开销较大。
我们想要在进程内,共享资源的情况下实现低开销的并发,这就引入了线程的概念。
一个进程的多个线程共享资源,但需要能互斥地访问资源,避免数据不一致。为此,我们可以用锁、信号量、条件变量等同步原语。
实验感想
rCore 主要目标还是实现一个 Unix-like 的操作系统,编程实验也基本都是实现 Linux 最关键的一些系统调用。
而 Unix/Linux 本就是与 C 语言一体的。这些 C 风格的系统调用接口,用 Rust 实现起来总是很奇怪,有种削足适履的感觉。
我在思考,如果抛弃这些 C 风格的接口,从 Rust 出发重新设计接口,或许可以更好地利用 Rust 的众多优秀特性。