0%

前言

我自己之前参加过ysyx,有一点操作系统的相关基础知识和riscv指令集基础,pa4后面还没做完,对于很多操作系统基础概念理解还是不够深刻,通过rcore的学习也是补上了这一部分。操作系统作为最和硬件联系紧密的软件,特权、异常、中断等概念的引入,让cpu具有了安全的连续不停运行各种各样程序的能力,这非常美妙😄,这也是我学习操作系统的目的。

个人感悟

通过近一个月的学习,对于操作系统的相关概念和rust这门编程语言有了更深入的了解,之前对于操作系统也只是停留在很基础的部分,对于操作系统的基础概念的认识也是非常模糊,也没有上过操作系统的相关课程,通过这次的学习,也是对操作系统的基础概念(进程管理,地址空间,文件系统,并发等等)有了基础的理解。
在rcore的实验课程中,最让我印象深刻的就是虚拟地址部分,由于之前接触的操作系统全部都是nommu类型,直接访问物理地址的概念非常不容易被打破,并且ch4也是一个门槛,需要非常深入的理解rcore的整体代码框架,对虚拟地址的映射,软硬件协同操作,多级页表,地址空间的概念理解了很久,在不断查找资料,不断理解的情况下,才对虚拟地址的概念有了很浅显的理解,把任务完成。
在完成任务的过程中,其实rcore的整体框架非常完善,作业的部分也就是一些扩展功能,整体还是对rcore的实现的理解,在完成任务时,也是对已有实现进行模仿,理解整体框架,之后调用已有函数或自己根据数据结构实现函数,实现功能。

rCore 总结

这个阶段大约花了3周左右的时间,更多是对OS概念抽象risc-vCPU架构的一些了解。跟rust的关系在我看来不算特别大。
核心的找地址、上下文切换都是c-like/asm实现的,当然这部分代码占比很小很小。这里面内存frame等等几个地方,充分利用了RAII机制,
大大减少了内存释放的心智负担,感觉是rust语言优于传统C的典型佐证。

这个阶段花时间较久是在ch8银行家/死锁检测算法上,因为概念的不熟悉,套用模型产生的死胡同。一直没有找到各个线程对于初始化资源的需求表,所以也就套不进银行家。中间放弃准备用循环图检测,发现不支持数量(权重)。最后看了一下向勇老师的在线课《操作系统-20.4 死锁检测》,找到了灵感,按照这个思路写算法,大概又花了几个小时调试通过。

下面按照实验内容分章节进行总结。

Read more »

前言

本人2024年8月份的时候,工作需要一部分图像处理代码,最后选择用Rust实现,并通过krpc-rust跟java完成调用。后面发现rust的一些理念尤其是RAII确实挺不错,就关注起来了。

后来在9月份的上海Rust大会上,了解到清华rCore项目训练营,感觉挺有意思的,既能学习os又能学习rust,就种草了

Rustlings 总结

基础阶段相对简单,却也兴奋,熟悉了各种rust语言的特征。比如下阶段会频繁用到的extern "C"#[no_mangle]
算法阶段,通过unsafe处理指针、链表,对unsafe有了一些熟悉,尤其是类型转换。还有就是熟悉了堆的实现,加上rust的运算符重载机制,可以灵活的在大小堆之间切换。
另外整个过程中发现rust并没有传说中的所谓曲线陡峭,秉着实用主义的原则,先进行一下元学习,也即了解这门语言的层次结构,函数传值、内存模型等大的层面,其余部分,用多少学多少,在出现问题的时候知道去研究哪部分就可以了。
所以我觉得学习也像小马过河,重在实践。

本人自己其实也在用 c 重写 linux 0.11 的内容,但越往后走,越觉得自己在闭门造车,担心最后做出来的东西已经与目前主流方向脱轨,
偶然的机会接触到 opencamp 这个社区,了解到这个社区现在在做什么。发现与自己所需要的东西强吻合,故毫不犹豫的加入了这次活动。
本人是一名软件工程师,主要语言是c和go,学习rust这门相对比较新的语言其实还是有点吃力。尤其是rust语言中的一些语法糖以及trait,更是
让我这个初学者苦不堪言。由此,我把学习过程记录到了这儿。

rust学习记录

第一阶段,让我对rust的基础语法有了了解,发现rust编译器一直在思考c工程师常考虑的两个事情:这个内存什么时候被申请,这个内存什么时候要释放。
弄清楚这个之后,使用rust就变得不那么困难了,尽管学习完这个课程后还不能写出很优美的rust语法糖,但至少可以保证代码的正确性了。

本人是一名网络软件工程师,从事高性能网络相关的工作,从而有机会接触到内核相关的东西。但工作中学到的东西往往犹如隔靴搔痒,不得痛快。
于是自己也尝试在用c 重写linux 0.11。这里推下我个人的仓库,目前进行到inode操作,与rcore的进度其实高度吻合。

JOS

这个项目还是基于i386架构,在做的过程中让我感受到了与现实世界的差距。也看到过一句话,一个人可以走的快,但一群人可以走的更远。
为了更远的目标以及更贴近生产环境,我参加学习了这个基于risc-v架构rcore的几个基础课程。

虚拟内存管理

个人认为操作系统中最复杂的就是虚拟内存管理,在rcore的课程中,学习了 risc-v 的页表结构,pa 到 va的转换过程,以及va和pa的管理。

调度

这里主要讲了怎么进行上下文的切换(两个kernel stack) 的切换,特权级的切换(S to U/ U to S)。还实现了一个简单的stride调度算法。

并发

学到了怎么创建线程,以及线程间的资源管理逻辑。实现了mutex,信号量,con_var,还实现了一个简单的死锁检测。

文件系统

学了 easy-fs 的文件管理方式,以及通过高速缓冲区实现对磁盘的访问。

Rustlings

关于第一阶段的Rustlings,还是花了很多时间去学习Rust。一开始是直接去看《Rust程序设计语言》,看了大概大半个月吧,把一些较为简单的概念和程序过了一遍。也是第一次接触这类内存安全类语言,第一次看到所有权,引用的时候还有点畏惧,对于没怎么深入学习过C++的人来说学起来还是有些吃力的。后面又去看了《Rust圣经》,发现有趣多了,提供了很多代码案例,很有意思。最后也跟着写了一个rust小项目minigrep。rustlings也是边学边查文档边做,做起来很有意思很有成就感。

rcore实验

rcore批处理系统编译逻辑

  • link.ld链接脚本将程序分成.text、.rodata、.data、.bss。
  • build.py会将app目录下的bin文件进行编译,将程序的text段加载到以0x8040000开始的用来存放app代码的内存空间,且规定每块app空间为0x2000。
  • build.rs会遍历user目录下的build文件夹中刚才通过objcopy生成的bin文件,然后生成对应的link_app.S。其实就是将app下的bin文件进行装载,在每个app的内存空间开始和结尾设置标号,并暴露给os以供调用。

页表机制

单页表:一块地址空间分为用户虚拟地址和内核虚拟地址,内核虚拟地址映射到内核物理地址

单页表会出现熔断漏洞

比如在用户虚拟空间中有一段代码需要访问内核数据空间的页面,因为cpu流水线机制,数据可能已经被放在cache中。但如果这时候我们取值失败了但是由于已经把数据放在了cache中。下一次我们从用户态直接访问这几个页面的时候,总有那么一个页面访问的速度远比其他的页面快。

双页表:分为用户地址空间和内核地址空间,用户地址空间又分内核态代码和用户态代码。

那么当我们在用户态访问内核数据时,其实是不知道数据放在哪的,这样就可以避免熔断漏洞。

exec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// Load a new elf to replace the original application address space and start execution
pub fn exec(&self, elf_data: &[u8]) {
// memory_set with elf program headers/trampoline/trap context/user stack
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT_BASE).into())
.unwrap()
.ppn();

// **** access current TCB exclusively
let mut inner = self.inner_exclusive_access();
// substitute memory_set
inner.memory_set = memory_set;
// update trap_cx ppn
inner.trap_cx_ppn = trap_cx_ppn;
// initialize base_size
inner.base_size = user_sp;
// initialize trap_cx
let trap_cx = inner.get_trap_cx();
*trap_cx = TrapContext::app_init_context(
entry_point,
user_sp,
KERNEL_SPACE.exclusive_access().token(),
self.kernel_stack.get_top(),
trap_handler as usize,
);
// **** release inner automatically
}

步骤 1:创建新的 MemorySet

1
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
  • 调用 MemorySet::from_elf 解析传入的 ELF 数据,并创建一个新的 MemorySet,即新的地址空间。
  • 该函数返回以下三个值:
    • memory_set:表示该进程的新内存映射集合,包含代码段、数据段、用户栈等信息。
    • user_sp:新用户栈的栈顶地址。
    • entry_point:新程序的入口地址,表示从此处开始执行新的 ELF 程序。

步骤 2:获取新的 trap_cx_ppn

1
2
3
4
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT_BASE).into())
.unwrap()
.ppn();
  • 通过 translate 方法,将 TRAP_CONTEXT_BASE 这个虚拟地址转换为物理页号(trap_cx_ppn)。
  • trap_cx_ppn 表示陷入上下文(TrapContext)所在的物理页号,用于进程的系统调用或异常处理。

步骤 3:独占访问当前进程控制块(TCB)

1
let mut inner = self.inner_exclusive_access();
  • 通过 inner_exclusive_access 方法独占访问当前进程的 TaskControlBlockInner 结构体,确保在以下步骤中可以对进程的内部状态进行修改。

步骤 4:替换 MemorySet

1
inner.memory_set = memory_set;
  • 将当前进程的 memory_set 替换为新创建的 memory_set,这样新加载的 ELF 程序就成为该进程的地址空间。
  • 这一步实现了对原应用程序地址空间的替换。

步骤 5:更新 trap_cx_ppn

1
inner.trap_cx_ppn = trap_cx_ppn;
  • 更新 trap_cx_ppn 字段,设置新的 trap_cx_ppn,确保进程的陷入上下文指针正确指向新的物理页。

步骤 6:初始化 base_size

1
inner.base_size = user_sp;
  • 更新 base_size 字段为新的用户栈顶地址 user_sp
  • base_size 用于保存用户栈的初始栈顶,便于栈空间管理。

步骤 7:初始化 trap_cx

1
2
3
4
5
6
7
8
let trap_cx = inner.get_trap_cx();
*trap_cx = TrapContext::app_init_context(
entry_point,
user_sp,
KERNEL_SPACE.exclusive_access().token(),
self.kernel_stack.get_top(),
trap_handler as usize,
);
  • 调用 get_trap_cx 获取当前进程的陷入上下文指针。

  • 使用

    1
    TrapContext::app_init_context

    函数重新初始化陷入上下文,设置新程序的执行信息:

    • entry_point:新程序的入口地址。
    • user_sp:用户栈顶地址。
    • KERNEL_SPACE.exclusive_access().token():内核空间的访问令牌,确保正确的权限。
    • self.kernel_stack.get_top():内核栈的栈顶地址,用于中断或系统调用时的上下文切换。
    • trap_handler as usize:陷入处理函数的地址,用于异常处理。

结尾:释放 inner

inner 独占访问结束时,inner_exclusive_access() 产生的独占访问会自动释放,允许其他任务对该进程进行访问。

总结

exec 方法执行以下步骤来加载和执行一个新的 ELF 程序:

  1. 从 ELF 数据中构建新的 MemorySet、用户栈顶地址、程序入口点。
  2. 获取并设置新的陷入上下文物理页号 trap_cx_ppn
  3. 独占访问当前进程控制块,并逐步替换内存集、更新陷入上下文等信息。
  4. 重新初始化陷入上下文,确保该进程从新的程序入口执行。

rcore调度策略

TaskManager任务管理器管理着一个任务就绪队列(先进先出策略),os初始化过后会在run_tasks中无限循环,取出任务及任务保存的寄存器task_cx,然后通过__switch切换idle_tasknext_task(实际就是task_cx中寄存器的切换),如果没有任务或当前任务释放控制权则会调用schedule切换到idle_task

进程间通信

管道(Pipe)

可表示为两个文件描述符加一段内核空间中的内存

1
2
// 传入数组,转换成管道的读写端的文件描叙符
int pipe(int pipefd[2]);

通过操作文件描述符来分别操作读写端进行进程间通信

  • 如何实现shell中管道符“|”功能

可以fork两个子进程,pid1的执行流可以使用dup2函数将stdout重定向到pipefd[1] (写端),并关闭管道的读写端,执行第一条命令

pid2的执行流使用dup2函数将stdin重定向到pipefd[0] (读端),关闭管道的读写端,执行第二条命令

最后父进程关闭读写端并wait两个子进程

匿名管道:只能是具有血缘关系的进程之间通信;它只能实现一个进程写另一个进程读,而如果需要两者同时进行时,就得重新打开一个管道。

为了使任意两个进程之间能够通信,就提出了命名管道(named pipe 或 FIFO)。
1、与管道的区别:提供了一个路径名与之关联,以FIFO文件的形式存储于文件系统中,能够实现任何两个进程之间通信。而匿名管道对于文件系统是不可见的,它仅限于在父子进程之间的通信。
2、FIFO是一个设备文件,在文件系统中以文件名的形式存在,因此即使进程与创建FIFO的进程不存在血缘关系也依然可以通信,前提是可以访问该路径。
3、FIFO(first input first output)总是遵循先进先出的原则,即第一个进来的数据会第一个被读走。

消息队列

信号(Signal)

在rCore中,当trap发生进入trap_handler函数,其中会调用handle_signals,循环调用check_pending_signals检测进程结构体中的成员来判断是否有signal到来,如果是内核信号,则在内核执行处理函数call_kernel_signal_handler(signal),如果是用户信号则需要返回用户态执行处理函数call_user_signal_handler(sig, signal)

一、前言

参加这次训练营时,我刚刚才进入大三,大三是个非常关键的时期,在之前的时间里已经积累了很多的基础,我在之前所学习到的知识将在这个时间段被检验被拷打,而翻过这座山后,学习的高度将建立在这个这个时期的过渡,所以不仅为了巩固与纠正之前的知识还为了能在这个过渡期将知识储备量进一步提高层次。我选择了这个训练营。我不仅能学到一门强大,前沿,更现代的技术,我还能对硬件与程序之间的桥梁——操作系统有更加深入的了解。能够得到丰富收获的同时也面临一系列的挑战。但是每当遇到困难与压力,我都够挺过来。难题来临,不得不顶上去,敢问路在何方,路就在脚下。

二、第一阶段

1.总述

第一阶段主要涉及的是Rust这门语言的学习,初次接触到Rust这门语言,可能最直观的感受是它的要求很多,比如可变引用于不可变引用、所有权、trait、mod等等。但是这也是Rust与其他语言不同的地方,它会在错误发生之前就尽力扼杀全部错误,这是其他语言所不具备的,这得益于Rust强大的编译器。另外,Rust编程的主要思想仍然是面向对象编程。这使得Rust在大型工程里会使代码更加模块化。

2.特殊机制

Rust的基础特性与其他语言类似这里不做描述,比如基本数据类型、结构体、枚举。这里只列举一些,有待补充。

(1) Option类型

这是一个枚举的变体(是基于枚举类型实现的),它在各种语义里发挥作用,其根本特别之处在于它的None和Some(),它与传统意义的Null不同,例如C语言里的Null表示为空值。它的设计能对Null做出处理而不简单停留在标识空值,他提供了更多选项。

(2) 生命周期

这是Rust强大的编译器带来的特性,它会检查在程序里每处被定义的内存的存在周期,以在编译阶段就避免悬垂指针的出现,这样的话内存的安全性将大大提高。

(3) 所有权机制

这是Rust独一档的特性,它会让Rust每处内存同一时间只会所属于一个变量,而其他变量只能引用这处内存,这样的定义使得Rust制定出了所有权规则,为内存提供了特殊的定义机制,这使得引用内存变得更加安全可靠。

(4) 模式匹配

针对模式匹配,Rust提供了match、if let、while let等来实现模式匹配,这使得我们在Rust可以对变量进行类型判断,增加了每条判断语句灵活性。

三、第二阶段

1.总述

第二阶段主要聚焦于基于risc-v架构的类Unix操作系统,我们采用了Rust语言来编写rcore内核,这是因为Rust在这方面有得天独厚的优势。操作系统在各领域都有所涉及,例如计算机体系架构、计算机组成、计算机网络、数据结构等。所以学习操作系统更够窥探更多计算机的秘密,因此我操作系统有非常浓厚的兴趣。

从裸机运行到单道批处理系统

  • 没有任何操作系统,能够直接硬件的程序运行被称为裸机程序。这样做有一点历史原因,虽然这样做对程序员来说十分自由、没有限制。但这样会造成许多问题,例如安全问题、不具通用性难以移植以及对硬件资源的利用率很低等等。
  • 于是有了单道批操作系统,在rcore表现为加入了初步的简陋的操作系统。为了安全性,借助硬件上划分的运行特权级将程序的执行环境人为与操作系统分隔开,同时也添加了系统调用,因此可以将多个程序按照一定的批次放在内存里约定的位置,以达到运行多个程序的目的。
  • 此时会将所有的程序编译并链接后,生成bin文件,将bin文件先加载到内存里,然后操作系统执行时会将需要执行的程序加载到0x80400000这个约定的地址。

从单道批处理到进程调度

  • 单道批处理系统已经明显的区别于裸机运行,但是这样的系统仍然还有进步空间,对硬件资源的利用率还是不高,比如说是cpu的处理能力没有充分利用以及内存资源的分配等等。
  • 因此在单道批处理系统的基础上,我们不只是将程序加载进内存后让其滞留在那里而是每个程序都可以运行起来,于是提出了时间片的概念,让每个程序尽量都能被cpu执行,这样cpu的空闲时间会下降,相反性能将大幅提高,同时还让持有内存资源的程序能够及时得到执行,提高了内存利用率。
  • 在rcore里,他是risc-v架构,使用内部的定时器来对运行时间进行切分,同时为了能成功切换程序,建立了新的数据结构TCB来维护与更新每个程序的关键信息。

内存地址空间的管理策略

  • 以往所提到的地址或是所使用的地址都是物理地址,意思为所有数据都是直接承载在物理地址上的,且所存与所访问的地址是一一对应的。但是这样的话会带来很多问题,例如内存空间的不安全以及局限性等等。
  • 因此提出了多种存储管理策略来使程序能够重定位,这极大地提高了内存地址空间的灵活性,同时使访问内存变得更加安全。实现这一算法需要硬件上的支持,因为如果在软件层面上让操作系统来翻译性能会大幅降低,对于硬件资源更适合去干这份工作。因此诞生了mmu。
  • 这里的rcore使用的是SV39这种内存管理策略。依赖的是页机制,且使用的是多级页表。这我们对整个物理内存划分为一个很小的页面,这样我们对内存的分配更加灵活、安全、高效。

对外部存储空间的抽象与管理

  • 内存虽说是直接与cpu交互,但内存的大小十分有限,我们不能够仅仅依靠内存来存储数据。我们使用计算机通常会产生、使用以及删除十分庞大数据,因此我们不得不依靠外部存储器件来存储数据,但这部分器件往往无法被cpu直接访问,因此我们需要操作系统来操作接口与设备来使数据放在内存里。
  • 但是管理一个庞大的存储空间不是一件简单的事,并且内存与外存的存储结构通常是有很大差别的,所以我们需要实现对存储空间进行抽象然后再建立新的数据结构进行管理。这里引入的就是文件系统。
  • rcore里的文件系统将每个文件(站在人的视角看叫做文件)抽象成了多个存储节点(站在存储器件的角度上),每个节点的数据结构里含有数据索引,名称,权限,文件关系等重要信息。这样我们访问文件时,操作系统会根据我们的需要去检索节点然后去访问数据。
  • 在类Unix操作系统里文件系统采用了统一的框架“”fd(文件描述符)”去描述每个“文件”。由于许多外部设备的访问操作时相似的,例如“打开”、“读”、“写”等,因此对许多类似的设备重定向,建立适配该框架的数据结构,使得可以利用这个框架去访问外部设备。

线程与进程

  • 之前我们通常将每个程序作为进程受操作系统管理然后运行,操作系统分配资源的对象为进程,cpu运行的单位也是进程。但是这样的设计仍然会带有一定的局限性,因此我们进程再度细分,将进程分为了线程,进程可以申请线程。
  • 进程本身就是一个最大的主线程,它作为线程但又区别于子线程。每个线程都在在进程的环境里运行,所得到的资源也为该进程。因此操作系统分配资源的对象仍为进程,但cpu运行的单位变成了线程。这样提高了进程的运行响应,同时优化了操作系统的调度运行。
  • 但这样也带来了一定的问题,会出现一些线程间的冲突,因此我们需要调和线程之间的运行。这里rcore就使用到了信号量和互斥。

四、总结

在这两个阶段里,我学到了很多,一门新的语言Rust、一种类Unix基于risc-v架构的操作系统。从9月29日开始到现在第二阶段11月10日的结束,无论是知识上的或是技术上的,我收获到了许多。我知道这只是基础,我渴望能在第三阶段学习到更成熟的操作系统,以此能加强我对Rust的学习、对risc-v架构的理解、对类Unix系统的理解以及更前沿的编程技术。感谢。

第一阶段的Rustlings

关于第一阶段的Rustlings,还是花了很多时间去学习Rust。一开始是直接去看《Rust程序设计语言》,随后还看了《Rust圣经》,把一些较为简单的概念和程序过了一遍。可是发现做题时还是难以掌握,随后只能带着疑问和不懂的地方边做边查,做完一套后终于有点入门的感觉了,我感觉rust对我或者别的语言的用户来说,一大难点就是自造的概念太多了+ 第一次接触时的api暴露太多了,有一点不知如何下手。然后再结合上生命周期,就更难上手了。

rcore实验

Lab 1

这个实验主要是实现一个简单sys_task_info多任务系统. 通过这个实验, 了硬件是如何在不同的特权级之间切换的, 以及操作系统是如何管理这些特权级的,知道什么是系统调用和特权级的应用

Lab 2

lab2是实现在启用虚拟地址的情况下重写sys_get_time和sys_task_info, 并实现sys_mmap和sys_munmpa系统调用, 因为启用了分页机制, 学习到了地址空间的概念, 应用程序只需要关心自己的地址空间, 而不需要关心其他应用程序的地址空间, 在实现过程中我对操作系统对代码中的地址空间, 页表的地址转换有了更深的理解

Lab 3

lab3是实现sys_spawn和stride调度算法, 学习到了进程是如何创建的, 以及进程是如何执行的

Lab 4

lab4是实现硬链接和获取文件信息的系统调用, 需要对inode和disk_inode有较深的理解, 学习了文件系统是如何与物理存储设备交互的

Lab 5

lab5是实现死锁检测, 需要理解死锁检测算法 need矩阵

第一阶段

补充第一阶段的内容:主要就是学习rust的使用,基本上问题都能够解决

在阶段二让我对操作系统和rust有了更加深刻的理解

操作系统部分

其实我是有linux内核和驱动的开发经验的,所以对虚拟地址、物理地址、页表、等等知识都是有接触的,但是主要还是停留在书本或者内核提供的api,这五次作业,让我用rust这门最开始接触的语言来实现底层,也还是难度不小。

最开始的页表啊、映射啊、以及一些环境搭建还有git使用对我来说基本没有难度,但是到了第四个lab和第五个lab就开始难了。

文件系统:这个本身我对其就没有太清楚的了解过内部结构,只是会用,这次lab4的作业,强制我要去看源码实现,对超级区块,以及bitmap管理分配的索引,以及bitmap管理内容区块,再到系统层面通过OSInode来管理每个文件的inode,以及管理到物理磁盘上的inode,以及文件名啥的相互联系,让我从底层的磁盘到上层OS的管理链路有了比较清晰的认识,并且对rust的使用有了更加深刻的理解(没错,特指生命周期,和mut ref),因为这两个没少出现借用的问题。

死锁检测:这个部分也是耗时很长,其实我认为这部分的理解不难,因为我很早就接触了锁、信号量这些,我觉得这部分最难的在于调试。由于是并发编程,遇到的情况千奇百怪,以及难以定位。所以我写了很多的log,并且以非常人性化的输出来查看,虽然这部分耗时比较久,但是打好log对后续的开发是如虎添翼。也不会有陷入log中无法自拔的问题。并且通过实现了这个算法,算是开辟了新的知识点,以前从来没有想到还能死锁检测。

对同学的建议

总的来说难度不小,并且其实资料是比较有限的(对于无从下手的新手来说),所以如果卡关了还是要多去搜索资料,以及看源码(这个很重要),去看具体的实现,以及画流程图和结构体之间的关系图,以及标注重要的api(找到合适的api可以事半功倍)

对项目建议

总的来说操作系统的重点都有涉及,知识面还是很充足的!!

但是在编写过程中明显感觉到由于前向兼容导致测试用例的编译越到后期越慢,每次run光编译就会花费一分钟,其实可以优化makefile来单独编译某些测例来减少测试开发的时间。

以及最后两个lab难度上升有点大,并且文章 中是对功能的描述居多(很多对于实验有用的点会被淹没在其中)虽然在边做lab边反复查看资料时可以发现,但是对于没有接触过的同学可能很多api看过就没印象了,在后续做lab中也找不到,所以可以适当增加一下强调或者提示,来减少难度增加坡度。

最后还可以收集一下大家的调试方法,这样对于后续对debug无从下手的同学也有参考意义。

前言

因为我已经工作有12年了, Rust也写了有几万行, 所以第一阶段对我来说没什么难度. 但是我还是按照训练营的要求, 完成了rustlings的练习.

rustlings

rustlings的练习很简单, 但是对于新手来说, 是一个很好的入门练习. 通过这些练习, 可以很快的了解rust的基本语法和特性.

不过我仍然从中学到了一些新东西, 比如 BinaryHeap. 之前我在编写应用程序的时候一直用 Vec 来实现优先队列, 现在我知道了 BinaryHeap 这个更好的实现.

总结

理解了 Drop 就入门了 Rust.
理解了 Trait 就熟悉了 Rust.