0%

此次训练营需要依照提供的os代码框架,完成一些练习,主要是补全函数的实现,整体难度并不大。比较困难的点,一是实验环境的配置,对git,qemu,makefile,cargo等工具的陌生,很容易导致无法理解工作环境。例如实验要拷贝的仓库并不是按照官方仓库的readme里的git clone https://github.com/LearningOS/rCore-Tutorial-Code-2023A.git,而是自己的作业仓库,ci-user这个测试文件夹应与os文件夹平级;二是刚上手代码框架时容易迷失在繁多的文件和文件夹里;三是部分代码比较晦涩,比如涉及到trap的部分,而这些对于理解整个操作系统是至关重要的。

练习中的几个实验只要求实现基本的功能,只要熟悉框架代码就很容易写出来,部分更困难的部分并没有作为必做的练习,比如实现写时复制、缺页异常等等。某些关键细节比如开启分页并不要求自己填写,因此,完成这次训练营只能算是一张入门系统的入场券。

不同于xv6,rcore的实验框架是递进式的,从裸机应用到批处理系统到多道程序系统,相当后面才有了文件系统。我对这种安排是否是合适保持一点疑问,因为链接时陌生的,在有文件系统前把用户应用链接到内核反而增添了很多复杂性。我觉得rcore的框架像是把用c翻译成的rust代码写成的,缺乏一些用rust重写的必要性,反而增添了很多复杂性。比如虚拟内存那章的MapArea等抽象我琢磨了很久,但是由c写成的xv6的虚拟内存代码我却很容易看懂,直至现在我依然觉得这种抽象是不必要的(也可能是个人水平欠缺,尚不能理解)。rcore的多进程、多线程也是仿linux的经典设计,这部分能否更有一些rust特色,比如让语言自身来管理一部分并发的功能呢?我希望今后能探索这部分的答案

第一阶段总结

在第一阶段,我主要进行的工作是学习Rust语言,同时也了解学习了一些RISC-V指令集的知识。

我对Rust的学习主要参考了Rust语言圣经。不过,由于时间问题,我只阅读了其中的知识点部分,还未阅读过实战项目部分。由于之后需要进行系统编程,我也粗略地看了一下Rust 秘典(翻译版本)

学习这些知识之后,完成该阶段的Rustlings任务比较简单。只有少数几道题涉及的知识点较冷门,查询对应知识点时花费了一些时间。

第二阶段总结

ch0 ~ ch2

这几章的实践内容主要是配置实验环境。按照书上的操作,配置起来比较顺利。为了之后开发更方便,我也完成了vscode通过ssh远程登录虚拟机的配置。

第1、2章的学习内容主要是对特权级切换过程和rcore的结构有一个基本的了解,(和之后相比)我觉得算是较为简单的一章。

ch3

本章需要实现多道程序和分时多任务。这一章对我来说是一个难点,因为任务切换、特权级切换的过程比ch2更加复杂了。好在最后还是大致理解了这一过程。

本章的实验是第一个实验,(和之后相比)算是比较简单的,但因为当时我对系统架构还不甚熟悉,该实验对当时的我来说还是有难度的。

我在任务管理块TaskControlBlock中增加了记录系统调用次数的字段syscall_times和记录程序开始时间的字段start_time。将这两个字段初始化,并分别在任务开始时和进入系统调用时更新这两个字段。之后,我实现了系统调用处理函数sys_task_info,将需要更新的TaskInfo指针传递到TASK_MANAGER内部进行处理,让TASK_MANAGER根据TCB中新增的字段更新TaskInfo的相应字段。

ch4

本章学习rcore对于地址空间的实现。在阅读过程中,我对本章内容还有一些不理解之处,之后在开发的过程中也慢慢熟悉了。

从本章开始,需要前向兼容。当时我还不知道git cherry-pick这个命令,因此只能将上一章的代码复制粘贴过来。同时,我在实现两个新的系统调用的时候,原本想把功能代码都写在系统调用函数内,因此也在函数内获取了系统很多资源的引用。结果,我编写的代码怎么修改也通不过Rust编译器的引用检查和生命周期检查,只能重写。这次,我将和不同数据结构有关的功能代码封装成那些数据结构对应的方法,让系统调用函数去使用这些方法,这才通过了编译。这一经历也让我养成了更好的编码习惯。

我重写了sys_get_timesys_task_info函数,使它们恢复正常功能。为了做到这点,我编写了map_user_va_to_pa函数,将用户空间的虚拟地址转化为物理地址。通过该函数,我将sys_get_timesys_task_info函数的输入的指针参数进行了地址变换,再完成之前的工作。

我实现了mmapmunmap系统调用。它们首先检查输入参数的合法性,再通过TASK_MANAGER获得当前进程的pcb,进而获得当前进程的用户地址空间。之后,调用我实现的地址空间的map_va_rangeunmap_va_range方法,完成对应的操作。

ch5

本章学习rcore的进程管理。有了前几章的铺垫,这章我感觉学起来较为轻松。

在本章,我学到并使用了git cherry-pick命令。由于这一章涉及进程的代码结构变换较大,而我之前修改的代码也有很多涉及进程TCB,因此出现了大量的合并冲突,其中一部分还只能手动解决。我花费了一些时间解决这些冲突,并且一边解决一边尝试编译,看能否正常运行。

我实现了spawn系统调用,根据目标程序新建子进程,并且维护它和调用进程的父子关系。参考forkexec的代码,该系统调用较容易实现。我实现了stride调度算法,在TCB的inner字段中增加了记录stride调度算法所需的数据项prioritystride。增加了设置进程优先级的系统调用set_priority。最后,重写了TaskManager::fetch函数,让其每次选取stride值最小的进程进行调度,并且维护被调度的进程的stride值。

时间关系,目前只完成了这些实验。在晋级之后,我也会继续对rcore的学习,继续完成实验。例如,我目前阅读完了第6章,准备开始第6章的实验。

rcore秋季训练营总结

原因

之前有听说过rcore,然后在发现群友发了这次的活动,
刚好又因为现在是无业,就想着说报个名写写看,然后就报名

实验

rcore的实验内容主要是5个Lab,是二阶段的主要任务

大概就是这样,下面就是一些个人的碎碎念

Read more »

2023秋冬季rCore训练营报告

ch0 & ch1 & ch2

  • 跟这 ch0 进行了一些环境配置,然后边看 ch1 边找着文档一起移除标准库依赖,构建用户态执行环境,构建裸机执行环境,并用 qemu 和 gdb 模拟和监测程序运行的状态,让我对什么是操作系统,操作系统内核运行的环境的有了一个较为深刻的理解。而阅读 ch2,令我理解了早期的操作系统是如何来处理程序的。

ch3

  • 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 时能够准确获得程序的运行时长。

ch4

  • ch4 中我详细学习了虚拟内存与 SV39 多级页表机制。在练习中我重写了 sys_get_time,我利用 get_mut 封装了一个将当前任务指定地址usize大小的数据修改的函数,然后将Timeval分为两个指向值的指针传入函数进行修改,以修正ti指向的地址为虚拟地址的问题,并避免了两个值在两个不同虚拟地址的情况,而对与 sys_task_info 我借鉴了前面 sys_get_time 的写法,利用已有的 translated_byte_buffer 将结构体分片为 u8 数组进行取址修改。而对于要求实现 mmap 与 munmap,我先检查函数参数是否出错和指定虚拟页是否有映射,然后调用 insert_framed_area / shrink_to 来实现要求的功能(虽然我认为单纯使用 shrink_to 有点小问题)。

ch5

  • ch5 我学习了进程管理机制的实现。在练习中我将前面的功能全部移植了过来(虽然后来发现有些好像不用),对于 spawn 我参考了 fork(exec()),只是新建 TCB 时改为直接 TaskControlBlock::new(elf_data),然后修改其中的 parent。对于 stride 调度算法,我在 TaskControlBlockInner 中添加 stride 与 pass 字段,set_prio 时 pass改为 BIG_STRIDE / prio。

总结

  • 我跟着 rCore-Tutorial-Book-v3 3.6.0-alpha.1 文档进行了第二阶段的实验,感觉收获还是比较大的,就是环境配置等前置我感觉还是有些混乱,没有一个比较好的教程,希望可以添加一个。最后,感谢老师能供提供一个这么好的平台。

感谢老师和主教们提供的这么一次宝贵的学习操作系统的课程,不管是老师们上课时的专业程度,还是文档的详细度,或是练习的代码,都体现出了老师们对于这门课付出的大量的时间和心血。在第一阶段的学习中,我了解和熟悉了Rust语言的使用方法,并在rustlings中尝试解决各种问题。当然,rust语言也并没有那么容易上手,加上时间有限,在学习第一阶段时候我对rust语言本身还是只有一个概念的雏形,有些rust的语法特性由于缺乏实践没有真正掌握。当然,在第二阶段的学习中,我开始真正尝试使用rust实现系统调用,同时努力学习吸收老师们的代码和思路,不仅能够更加熟练的使用rust语言,还对操作系统有了更清晰的概念。

Read more »

Rust语言学习

  • Rust 编程语言是一个专注于安全、并发和实用性的系统编程语言,语法简洁先进

  • 内存安全:无需垃圾回收器即可保证内存安全。

  • 所有权系统:所有权、借用和生命周期是 Rust 独有的特性,用于在编译时管理内存。

  • 类型系统:Rust 拥有强类型系统和类型推导。

  • 并发:Rust 通过所有权模型来避免数据竞争。

  • 构建系统: Rust拥有强大且易用的构建系统和包管理器cargo

  • 第三方库: 强大的生态系统支持,可以找到几乎任何类型的库。

  • 学习资源丰富,做完Rustlings的一系列小练习可以了解rust的各个方面,有官方中文文档

rcore

课程很硬核,实验较简单

实验一

实验一让我了解了rcore基本的进程调度实现,以及RV64汇编语法,寄存器特性,调用约定,RISC-V的指令集很简洁,总共就100多条指令.

实验二

实验二让我了解了以下内容

  • SV39分页机制: 开启SV39分页后所有的内存访问都变成虚拟地址形式,通过MMU将虚拟地址转成物理地址

  • rcore内存管理: rcore为所有物理地址做了一个恒等映射,可以通过虚拟地址访问所有物理地址,先实现了一个分配物理页帧的FrameAllocater,然后实现了一个MemorySet来实现分配地址空间和地址空间对应的物理内存

  • 双页表OS: 系统为用户态专门设计了一张没有内核模式代码的页表,内核模式区域只包含了trap入口代码,用户态发生trap就跳到Trampoline切换到内核态页表,内核态返回用户态的时候又切换回用户态的页表,这样子做每次trap会多出两次切页表导致TLB清空的性能消耗

    为了防范可能出现的类似”幽灵熔断”漏洞的攻击,牺牲一些性能来换安全性还是有必要的.

实验三

实验三学习了rcore的进程管理机制,fork,exec的实现,Elf映射到内存的过程,以及支持进程优先级的stride调度算法

fork: 按照一个父进程创建一个子进程,子进程拷贝父进程的地址空间

exec: 清空进程的地址空间来执行新的可执行文件

stride调度算法: 支持优先级的进程调度算法

原理是给每个进程添加priority和stride的成员,调度器每次都挑选stride最小的进程来执行,进程执行一个时间片就加上一个大常量BIG_STRIDE除以priority的值

priority要求必须>=2,因为如果=0的话除法就会发生异常,=1的话就不支持溢出处理,>=2的情况下STRIDE_MAX – STRIDE_MIN <= BigStride / 2,这样可以做溢出判断

实验四

实验四学习了easyfs文件系统的实现,这个文件系统可以以模块的形式添加进内核里.

这个内核模块的开发流程可以在用户模式做测试,测试稳定之后放到系统内核里面,这样子做有很多好处

  1. 让内核设计更模块化
  2. 可以把这个文件系统模块放到用户态,如果以后要实现一个用户态解析这个OS文件系统的工具,比如ch6中的easy-fs-fuse可以在测试环境的用户态生成一个fs.img给虚拟机内的操作系统使用

easy-fs的实现:

把磁盘的线性空间分块,每个块大小为常量BLOCK_SZ

第一个块的信息是超级块,包含文件系统的标识,大小,inode区域,数据分布信息

inode用来描述文件和目录,根目录的inode编号是0,相当于linux中的/根目录,通过根目录来实现索引文件系统中的所有文件

Rust & RISC-V & Rcore

第一次知道 rcore 这个项目是在一个 CS 的开源交流群里,因为一直是开发小白,想要找一些项目来写,提升自己的实力。

暑假开始学 xv6,但是因为旅游计划搁置了,刚好开学后听闻 rcore 的项目,脑袋一拍就想参与,并也是进入以后才了解到 rust 语言。

起初参加的原因只是希望找人和我一起写课程来督促我成长,因为本人很容易分心。但是这个问题在开始写 lab 以后就眼小云散了,发现自己在写实验的时候还是很专注,能够投入时间的。

在学习了第一阶段的 rustlings 后,我了解到了很多独属于 rust 本身的魅力,比如所有权,又比如unsafe,同时 rust 的编译器比较严格,能够在编译层面上杜绝一大部分难以在代码层面上发现的 BUG,

同样的,riscv 架构是一个新兴架构,指令集简洁且开源,易于掌握和学习。

下面简单总结一下在第二阶段我完成的各个实验情况。

ch3

本次实验在一个分时多任务系统架构的基础上实现了一个当前任务信息查询调用。

从系统调用的层面上(S态)实现了该功能。

sys_task_info调用的实现建立在对TaskManager的修改的基础上。

我在TaskManagerInner中的TaskControlBlock里添加了syscall_times来统计该任务的各个系统调用次数的统计(在Trap_handler中增加贡献),并且在Inner中添加start_time来计算Task开启的时间长度,任务块实现函数中统计。

由于本次实验框架基于数组的实现方式,是建立在系统体量较小的基础上,所以能够理解。但是如果系统逐渐庞大,系统调用和任务数量的增加,会导致TaskManager内存部分乘法增长(因为是数组套数组)。

任务数量应该远小于系统调用的情况下使用HashMap来替代TaskControlBlock内部的统计数组会好一些。

ch4

本次实验重写了ch3中的sys_write和sys_task_info,因为在分页机制启用的基础上原有代码不再可用。

上面两个系统调用涉及到用户空间中的内存使用,所以我设计了一个modify_byte_buffer函数,用于将src(启用分页的内核空间地址)中的len长度的内容拷贝到ptr(启用分页的内核用户地址)相同长度的物理内存中去。

本次实验还实现了sys_mmap和sys_mummap的匿名映射的内存申请和解绑系统调用。

这两个函数都需要对所映射的地址范围进行检查,其中有一个关键点在于框架内所有的区间都使用左闭右开的形式。而且在使用虚拟地址来找到对应的物理地址时,框架(mm::page_table::find_pte)在第0和第1层遇到没有下一层映射的情况时返回None,但是到第2层时会直接返回,所以我们在返回值不是None的情况下也要判断是否Vaild。

上面这个 Vaild 的问题我真的是调了两个小时才发现的,真的需要知道每一块代码的运行方式和功能。

ch5

本次实验继承了ch4中的所有功能。

同时实现了spawn创建进程以及通过设置priority优先级和stride参数来对进程进行调度。

本次实验较为简单,使用 git cherry-pick 能简单的得到前面提交的代码,方便了功能的继承。

同时这次实验的调试也并不容易,其中在 syscall 统计其次数时,我像之前一样获取了TaskManager的所有权后增加,但是这样导致我在 waitpid 中的一个 assert 一直未能通过,后才发现改正。

总结

本次 rcore-os 训练营的第二阶段,让我巩固和学习掌握了很多操作系统的知识,并且亲身实践了功能,受益匪浅。

第二阶段将较难的一些概念和功能拆分,让我们不至于对一个东西很难上手,同时实验循序渐进,能够在原来写好东西的基础上增加对系统的理解修改代码,让我们的思维不断蜕变;而且增加功能的实验方式让我感觉像是在写自己的系统,就会更认真的写,学到更多东西,有强烈的正反馈。

同时第二阶段的实践也让我的 rust 语言熟练读更上一层楼。

如果还有时间我将继续学习 ch6 和 ch8 两个实验。

写在前面

接触到 rust 断断续续有两年的时间了,始终相信它是未来编程语言的方向(虽然我不是程序,只是一只菜鸡)。没有正经用 rust 去做过项目,参加训练营是机缘巧合,满足了我一直以来的需要:一是给自己一个机会训练 rust 的实际运用。二是本身是 linux 爱好者,使用 linux 超过 20 年,想深入了解一下操作系统的组成及运作原理。

一阶段总结

之前对 rust 的关注,还是带了起手的优势,一阶段完成得比较顺利。印象深刻的是clippy3这样,大家踩的坑都踩过。目前看题目的要求是程序能按设计的逻辑正确实现,而不仅仅是设置属性宏让编译能通过。

二阶段总结

二阶段对我而言几乎是全新的知识。以学习为目的,尽量在理解清楚的基础上做题。题目的完成也以通过测试为目的,未考虑错误处理等等细节。

ch1 & ch2

这两章从应用执行环境说起,引入了程序执行环境和特权级切换这两个基础概念。第二章的批处理系统和后续的操作系统,本身的角色就是在硬件和应用直接充当桥梁的角色。用 M 态抽象硬件的能力,用 S 态提供接口,供 U 态的应用调用。

开始阶段研究了一下实验环境在我的 Gentoo Linux 下执行的可能性,做过一些改动来适配。事实证明,直到目前为止( ch5 )都能正常运行。具体过程记录在 我的准备工作【腾讯文档】Qemu常见问题Q&A 的第 2 ~ 4 个问题。

ch3

题目要求:实现 sys_task_info 系统调用,统计任务信息:包括状态、当前任务所用到的系统调用ID及对应的调用次数,任务开始到目前为止的时长(只考虑开始到现在的时长)

思路

  1. task只会以两种方式开始执行,run_first_task 或run_next_task,只在一个地方退出:mark_current_exited。所以在任务开始时保存当时的时间,退出的时候再取一次时间,二者的差就是当前任务的运行时长。
  2. 系统调用最终都是通过 sys_call 进行调用,它的入参就有该次调用的 syscall_id 只需要在这里统计当前任务的系统调用 ID和次数即可。

小结

具体实现时为了不影响正在执行的任务,直接从当前任务的 TaskInfo 中克隆了它的副本来计算目前为止的运行时间,应该可以有更优的方式。

ch4

个人觉得是前三个里面最难的,内存管理的部分非常抽象并有一定复杂度。主要是实现 map 和 unmap 功能,能让应用正常申请释放内存。

思路

  1. map 时需要遍历当前 task 的 MemorySet.areas,从而判定需要分配的虚拟地址范围 start ~ start + len 有没有跟 areas 里面的 某个 area 有重叠,如果没有,才能创建新的 MapArea,并将它推入 MemorySet.areas。 最后这步不能忘记!

  2. 因为 map 的机制,areas 中不会出现部分重叠的区域,所以 unmap 的时候,找到的 area 的 范围和 start ~ start + len 一定是重合的。还有就是记得要将它从 areas 中移除。

  3. map 和 unmap ,start ~ start + len 覆盖的虚拟和物理地址页面都可能不止一个

小结

理解映射关系花了很多时间和心思,最后总结的结果是:map 时,经过一系列检查,先创建了一个 area,此时它的 VPNRange 是 start ~ start + len, 但 data_frames 还是空的,直到我们将它 push 到 MemorySet.areas 时,在 push 函数的实现里,才为每个vpn 分配了一个 ppn ,并将二者的映射插入到 data_frames 中,完成映射。unmap 时我采取的方法是直接遍历 area.vpn_range, 将 vpn 和 ppn 的映射关系断开,然后从 MemorySet.areas 中去掉当前的area,被分配的物理节点会在映射断开时自动释放,area 在从 areas 中移除后,也会自动被释放,从而完成 unmap 操作。

ch5

相对ch4, 这个让人稍微能缓缓。

思路

spawn 的实现,在提示里面已经给出了,可以参考 fork + exec。区别就是建立 MemorySet 时,改成从 path 读取,最后按照 exec 的方式来设置 trap_cx。

stride 调度算法仅仅只是过关。实现了当 priority 变化时,重新 设置当前任务的 stride。 在 task 管理中,添加任务时根据 stride 添加到适当的位置。当 pop_front 取任务时,自然就取到最小的。没有考虑溢出或其他情况。

小结

Stride 考虑溢出的话会更公平,调度队列考虑使用 BtreeMap,利用它的天然有序性,以 stride 为 key,应该表现会更好。

未完待续

第一次参加开源训练营,给我的感觉很不错,感谢老师和助教们的付出。这也是我第一次写rust语言,刚开始写的时候一路想弃坑最后还是坚持下来了。

第一阶段

第一阶段内容还是比较简单文档也很清晰,照着做就能够完成。第一阶段遇到的最大难题是加入训练营时还有五天就开第二阶段了,然后就拼命爆肝rustling。

第二阶段

第二阶段就不得不吐槽一下实验文档,刚开始只知道有guide,后面发现还有一本book v3, 感觉还得优化下实验说明, 很容易一开始就找不到方向。还有就是实验测试样例说明不够,只能靠自己摸索。不过指导书写的很不错,之前有上过操作系统课,学习起来还是比较轻松。

ch1 ch2 ch3

这部分给我开了个好头,os课上学的知识还是比较零碎的。rcore的操作系统发展脉络很清晰,操作系统上的程序怎么运行起来的,如何构建用户态向内核态的系统调用请求,如何构建裸机上的操作系统,都讲述的很清楚。实验部分完成task_info系统调用难度也不大。

ch4

地址空间这章感觉是这几章中最难的一章,之前对虚拟内存没什么概念,v3这里也是好长的几章字。学完了现在感觉也是似懂非懂的,
不过好在这章实验不是很复杂只需要实现4种系统调用,前面两种还是重写上一章的只不过加入了虚拟地址转换。不过这里有一个神奇的地方在于ch4里面get_time_us()和get_time_ms()产生了很大的误差,而在ch3里面却通过了。

ch5

进程这章比较简单。不过让我学习到的地方在于,指导书上面介绍了
为什么要实现进程。这章实验要求在新增进程功能下,不仅让代码能通过之前的测试,还要新实现sys_spawn,以及进程调度算法,不过总的不算困难。

rCore 二阶段总结

第一次看到训练营的通知是在 Rust 中文社区,本着尽可能的抓住所有学习的机会就随手报了训练营,
结果被 Rust 的语法的优雅,还有操作系统的复杂性,功能性深深吸引,奈何我只有一些通识导论课的基础,没有关于 Risc-v 或计算机体系结构等的课程基础,所以
学期二阶段的课程非常的慢,不过还好,赶上了截止日期。

第一第二章是教学演示用的,但是为了能跑起来我还是花了很长时间,在 mac 下配置 docker,在 docker 里配置实验环境也是踩了不少的坑。
rCore 以一种计算机操作系统历史的教学方法,从简单到困难,从底层的基础到高深的抽象,使得我虽然没有什么计算机基础(只有简单的编程基础)也能学的津津有味。

ch1 讲的是一个最小用户态的执行环境操作系统,使得可以在裸机上,不调用任何标准库,通过编写的 kernel 以及实现的 sys_write 系统调用接口,来打印属于 rCore 的 hello world!
这一章给我带来的极大的兴趣,让我对操作系统的认知从之前的一个深不可测的黑盒到现在的“最基本的是一个执行环境”。

ch2 则针对操作系统的任务执行过程来讲,实现了从第一章的只能运行一个应用程序的最小用户态执行环境到可以一次性将多个任务加载到内存,并分别处理多个任务的批处理操作系统,提高了运行效率。

ch3 则更进一步,引入了程序自动放弃cpu的控制权 yeild 和系统在时间片下主动暂停程序执行的时钟中断,更进一步的提高了运行效率。

ch4 引入了内存空间这一概念,个人觉得这一章是我看到现在为止最复杂的一章,多级页表的设计,任务管理器与页表的连接,以及跳板的作用,如何处理陷入,非常复杂,第一次看完还是很蒙,但是头铁直接去做 lab2,边做边看源码,也慢慢的理解了其设计逻辑。

ch5 引入了进程的概念,更加细化了应用执行过程,代码逻辑中将 TaskManager 二分为处理 保存系统中存在的进程的容器 和 cpu 正在执行的进程。在从 ch4 迁移到 ch5 中,对我来说还是有一点难度。

rCore 的文档对于像我这样的新手来说非常好,实验难度也适中,给我的感觉是实验本身要实现的逻辑不是很难,但是实验的要求应该也不是仅仅通过测试用例,还要了解阅读 rCore 的核心代码和逻辑。
才能得到更好的收获。