0%

lab1实验总结和疑难点

ch3的实验相对来说较为简单,由于系统调用任务增加任务各自的系统调用数量syscall_times并且要得到任务的相关属性taskinfo,所以在任务控制块TaskControl那里的inner模块中加入相关的变量记录即可。

lab2实验总结和疑难点

ch4的实验开始就有一些难度,特别是mmap和munmap不知道该怎么实现,于是我使用逆推的办法,mmap和munmap都是要对一部分虚拟地址进行alloc和dealloc操作,所以从这一点出发找到了当前任务的memory_set,然后对该任务的memory_set那部分虚拟地址进行insert_frame和unmap即可,然后再用接口暴露给系统调用即可。

lab3实验总结和疑难点

ch5的实验比ch4相对来说简单一点,sys_spawn只需要复制一遍fork改一些参数即可,轮转调度只需要在fetch_task的时候对每个task的当前stride进行比较,挑选得到相应的task即可。

lab4实验总结和疑难点

ch6的实验很难,因为涉及的结构体很多以及各个结构体之间存在相互有关的关系,于是我根据文档整理了一下接口。

File

  1. readable: 返回可读属性
  2. writable: 返回可写属性
  3. read(mut user_buf): user_buf获取输入信息, 返回1
  4. write(user_buf): 打印user_buf信息,返回长度

TaskControlBlockInner

  1. 新增fd_table: Vec<Option<Arc<dyn File + Send + Sync>>>

Syscall

  1. sys_write(fd, buf, len):fd为程序输入到那个文件,将文件的信息输出出来
  2. sys_read(fd, buf, len):将用户输入放到buf中
  3. sys_openat(dirfd, path, sflags, mode):用各种不同的读写方式打开文件

BlockDevice

  1. read_block(block_id, &mut buf):将blockid对应的块内容读到buf中
  2. write_block(block_id, &buf):将buf写到对应block

一个块512字节,512*8bit

BlockCache

  1. 由512个8bit,block_id, block_device:Arc和modified组成
  2. addr_of_offset(offset):返回offset位置的cache元素。
  3. get_ref(offset):获取offset位置的模板T的对应指针。
  4. get_mut(offset):同上但可以修改。
  5. read(offset): get_ref。
  6. modify(offset): get_mut。
  7. drop:如果修改了将结果写到对应block中。

ClockCacheManager

  1. 由Arc<Mutex>组成的VecDeque
  2. get_block_cache(block_id, block_device):从VecDeque中获取对应的block_cache,如果queue满了则弹出强引用计数为1的cache然后存入queue,就是弹出自己再加入。

easy-fs布局

SuperBlock

  1. 由合法性验证的魔数magic,文件系统的总块数total_blocks,索引节点位图,索引节点区域,数据块位图,数据块区域组成。
  2. initialize():有磁盘块管理器传入初始化参数
  3. is_valid():判断是否合法。

Bitmap

  1. 由start_block_id和blocks组成,起始块编号和块数
  2. alloc(block_device):先得到bits64_pos和inner_pos为第blockid块磁盘中未完全分配,然后发现是这一块的第bits64_pos个64位没完全分配,再找到这个第64位的第一个0处于的比特位赋值为1,并返回这一位在整个Bitmap中从startblock一开始处于第几位。

DiskInode

  1. 由文件字节数size,直接块编号,间接块编号组成。
  2. initialze(),初始化。
  3. is_dir(): 是否为目录。
  4. is_file(): 是否为文件。
  5. get_block_id(inner_id, block_device): 获取inner_id对应的文件存储的blockid。
  6. data_blocks(): 得到一共占了多少数据块,向上取整。
  7. total_blocks(): 数据块与索引块的总和。
  8. blocks_num_needed(new_size): 得到从size扩容到new_size需要的额外数据块。
  9. increase_size(new_size, new_blocks, block_Device): 扩容函数。
  10. clear_size(block_Device): 清空文件内容,返回一个Vec重新送给磁盘块管理器进行调度。
  11. read_At(offset, buf, block_device): 将文件内容从offset字节开始的部分读到buf中。返回读到的字节数。
  12. write_at(): 同上实现但是需要increase_size。

DirEntry

  1. 由目录名和inode_number组成。

EasyFileSystem

  1. 由block_device, inode_bitmap, data_bitmap, inode_area_start_block和data_area_Start_block组成。
  2. create(block_Device, total_blocks, inode_bitmap_blocks): 创建efs实例并且将第0快设置为超级块
  3. open(block_device):将编号位0的超级块读出然后构造efs实例。
  4. get_disk_inode_pos(inode_id): 得到inode_id对应的数据块在第几块以及offset。
  5. get_Data_block_id(data_block_id):同上,得到数据块所在的实际位置
  6. alloc_inode(), alloc_Data(): 返回alloc所在的数据块id。
  7. dealloc_Data(block_id): 将cache中blockid的块清零并且释放block_id-data_Area_Start_id的空间。
  8. root_inode(efs): 获取根目录的inode。

Inode

  1. 由block_id, block_offset, fs文件系统, block_Device组成。
  2. read_disk_inode()
  3. modify_disk_inode()
  4. find(name): 根据名字得到对应存inode的位置并且构造Inode。
  5. find_inode_id(name, disk_inode): 对文件系统进行遍历查找取出每个DirEntry并得到相应的目录名与name进行比对,返回DirEntry的Inodenumber。
  6. ls同上但是把每个dirent的名字记下来。
  7. create(name):先查有没有当前name的inode,然后alloc一个新inode并且得到存储inode需要的数据块id和偏移,然后将新文件目录项插入到根目录内容中。
  8. clear():
  9. read_at(offset, buf: &mut [u8]): disknode.read_at.
  10. write_at同上,但需要扩容
  11. increa_size(new_size, disk_inode, fs): disk_inode.increase_size。

然后继续使用逆推法得到Inode应该是整个的核心代码编写处,关键之处在于要得到inode_id,因为没有直接的name查询的接口,需要使用inode_id进行linkat和unlinkat的维护,疑难点在于接口太多,这一部分我借鉴了以往同学的报告才得以理解。

lab5实验总结和疑难点

ch8的实验相对来说比较简单,在每个mutex和sem里记录分给了哪些任务的tid以及还有哪些任务tid在等待就可以得到allocation和need,available我两种方式都写了一下,一种是在process里直接维护,还有一种是每个mutex和sem各自维护,最后得到这些之后进行银行家算法即可。唯一的难点就是别忘记写sys_gettime,我因为没写这个系统调用卡了一天,期间使用过trace但是忽略了gettime的报错,一直trace的是我银行家算法里的数据allocation和need等。

前言

首先,非常感谢 rCore 训练营提供的学习机会。在训练营里,我收获颇多,不仅巩固了以往学到的一些技法,还把以往仅在书本上了解的操作系统原理转化成了实际的项目代码,一步步看着以往学过的知识点被串联起来,真是一次难忘的经历!

训练阶段

第一阶段 Rust 学习

因为之前我并没有很深入地学过 rust 语言,想要在两周内初步掌握一门静态强类型语言还是稍微有点挑战性的。
幸好,rust 的核心概念所有权和生命周期在以往学习 C++ 的过程中已有涉猎,因此学习的过程并没有预期中那么 “陡峭”,第一周便通关了 rustlings。

虽然说字面上通关了,但是实际上有些细节部分还是没有掌握得很透彻,于是第二周便打算直接在项目代码中学习,半学半仿写地看了一遍 rCore 的代码。在项目中学习到了不少的 rust 编程技巧,例如各种 trait,from/into 的用法,泛型的使用技巧,函数式编程的思维方式等,可谓收获满满。

整体来说,rust 这门语言给我的观感非常不错,他一方面吸收现代语言的灵活性,另一方面也保留了对底层的控制能力,也 “堵” 上了不少 C++ 的坑点,接下来有计划更进一步深入学习。

第二阶段 rCore Labs

在计算机科学领域,操作系统扮演了重要的角色,它们不仅支持着现代计算设备的正常运行,还提供了深入研究和探索计算机内部工作原理的机会。因此,我一直对操作系统有着浓厚的兴趣,并渴望了解它们的内部机制。在做 Labs 的过程中,我有种逐渐拨开迷雾的明朗感,以往看作黑箱的各种操作系统机制,被具体地呈现到眼前,可谓非常地 “过瘾”。

  1. Lab1
    没有什么特别复杂的地方,主要一个坑的地方是,get_time_us 的精度问题,syscall计数很好地巩固系统调用的流程,明确地展示了特权级别切换的具体过程。

  2. Lab2
    页表的具体实现比较抽象,看的过程中理了几次流程图。但是理解之后发现,其中 RAII 方式管理页帧的方法非常的优雅。在理解 mm 模块之后,mmap 和 munmap 的实现就比较直接明了了,只要做好边界检测和合法性检测,实现的过程比较简单。需要注意的是前面写的 syscall 因为内存地址空间的切换,需要把用户空间的地址转换成操作系统访问的物理地址。

  3. Lab3
    spawn 仿照的 fork 和 exec,实现比较简单,stride 算法实现也比较简单,没有特别复杂的地方。

  4. Lab4
    坑的地方来了,Lab3 写的 spawn 在迁移到 Lab4 后无法正常运作了。在排查了一段时间后发现貌似和内存对齐有关(莫非是编译器bug?),似乎超出了我的能力范围,决定暂时不深入探究。最后解决的方案是使用 TaskControlBlock 提供的 fork 和 exec 函数组合。而 link 和 unlink 的实现需要修改 fs 的代码,通过 DiskInode 记录文件信息,透过 OSInode,Inode 层层调用。link 的本质是两个不同名的文件指向同一个,Inode,这让我切切实实地体会到了操作系统对磁盘的抽象。

  5. Lab5
    又一坑位,Semaphore 的测试点依赖 sys_get_time 函数,没实现会直接死循环。由于这次 Lab 不要求合并前面的代码,于是没考虑这个东西,一直在找算法的 bug,卡了一天多的时间。今天晚上通过群友指点才知道问题所在。。。

总结

通过训练营,我初步地了解了操作系统的内部工作原理,学习了如何编写高效且安全的系统级代码。Rust编程语言的高效与抽象让我对代码的可维护性和可靠性有了更深刻的认识。

与训练营的导师和同学合作是一个非常愉快的经历。我有机会与有经验的人合作,学习他们的技能和思考方式。团队合作让我明白了众人拾柴火焰高的道理,锻炼了我的沟通和合作技能。希望通过参加训练营,提高我的编程技能、系统级知识和解决问题的能力。此外,我希望通过与训练营导师和同学的合作,不断进步,并将所学应用于实际工作中,并对 rCore 开源社区作出贡献,未来积极参与 rCore 和 rust 社区,并期待在未来可以贡献于开源操作系统项目。

最后,我想向rCore开源操作系统训练营的所有人表示由衷的感谢!rCore开源操作系统训练营是一次宝贵的学习经历,不仅提高了我的技术水平,还拓宽了我的视野,感谢导师和组织者为我们提供了这个机会!

2023秋冬季rCore训练营报告

Rust

参加这次训练营的初始目的就是因为曾经多次学习 Rust ,但是因为工作和个人项目使用很少,感觉使用不是很熟练,想借用这次机会加深理解。对 Rust 的兴趣来源于网上将其与 C/C++ 的对比以及称其可以替代 C/C++ 的相关文章,其中让我在意的是它的“零开销抽象”“内存安全性”及 Cargo 。最吸引我的是 Cargo ,相对于 C/C++ 几乎没有包管理,没有集成工具链,偶尔一个开源库的构建让人焦头烂额,Cargo 让我的编程体验有了很大的提升,非常的高效和便捷,rustup 作为工具链管理,非常方便地完成了跨平台编译的工作。相对的,生命周期和借用检查是比较复杂的部分,需要多加学习、实战才能熟练的掌握运用。在本次相关实验中,让我对生命周期、借用检查有了更深的理解,对 unsafe 的相关内容有了新的认识。

rCore

RISC-V

很久就听闻过 RISC-V,诸如阿里的“平头哥”,但是只知道是一套基于精简指令集的开源指令集架构,并不曾深入了解,借本次训练营正好熟悉了相关内容。使用下来发现,RISC-V 的 SBI 提供了很大的便利性,在之前 x86 架构上的 OS 实验未曾体验过相似功能,感觉启动流程似乎没有那么复杂(或者是未使用 grub 等相关 Bootloader)。这次仅使用了 SBI 的少量功能,诸如物理内存探测等其他能力。相较于 x86 , RISC-V 的指令简洁,类似于 pop push 这样的指令都不存在,文档也相对简洁了不少。本次实验中个人对 RISC-V 的相关投入比较少,但非常感兴趣,希望 RISC-V 的生态越来越好,后续能有机会设计一款基于 RISC-V 的 CPU。

实验内容

从实验文档的第零章 “实验环境配置” 开始搭建 rCore 实验环境,个人采用 wsl2。在编译 qemu 的过程中发现编译非常慢,一度以为是电脑硬件或操作系统本身导致的原因,后续经过查阅,发现是 wsl2 的问题,在 wsl2 中访问 Windows 分区中的内容会很慢。后来尝试将 qemu 的源码移动到 wsl2 中,编译速度大幅提升。

从第一章开始 Rust 的实际代码编写,了解了 Rust 除了 std 标准库以外的另一个核心库 core,使用 ![no_mangle] 防止编译器修改函数名称的方法,以及 Rust 的裸机开发能力。第二章讲解了如何实现应用程序以及基于RISC-V的特权级切换完成任务批处理功能,RISC-V 的机器模式(machine mode)、为 Linux、Windows 提供支持的监管者模式(supervisor mode)以及用户模式(user mode)。rCore 的用户程序设计运行在用户模式。从用户模式发起系统调用的操作,特权级寄存器的处理、栈切换、Trap 上下文的保存恢复等相关汇编内容为后续的学习内容的重点。

lab1

第三章实现简单的任务功能,完成任务的加载与切换,使用 RISC-V 提供的时钟中断实现时间片轮转算法来进行任务调度。时钟中断的的开启与中断触发间隔由 RustSBI 提供的相关接口完成设置。

lab1 的实验内容相对简单,在任务控制块中加入相关字段存储任务首次启动时间及相关任务的系统调用次数,在每次发生中断和系统调用时更新相关系统调用的调用次数。在 sys_task_info 中返回当前时间减去任务首次启动时间及存储的系统调用次数即可。

lab2

第四章开始学习物理内存管理相关内容。使用 Rust 的全局的动态内存分配器的实现 buddy_system_allocator 完成在内核中使用堆空间。
基于 RISC-V 硬件支持的 SV39 多级页表实现地址空间、物理页的分配与回收。开始划分内核地址空间及应用地址空间,实现应用程序不在需要关注应用起始地址及存放位置。相比上一章节的中断和系统调用处理过程添加了跳板页面,来避免在用户模式进入内核模式或内核模式退回用户模式时导致的地址不一致。

lab2 实验中 lab1 实验的相关内容需要重新实现,因为内存分页后在用户态传递进来的参数地址是虚拟地址,内核的访问地址映射和物理地址一致,无法通过虚拟地址对传递进来的参数赋值,所以需要将虚拟地址转换为物理地址,才能完成赋值。
sys_mmap 的实现参考系统中 insert_framed_area 的实现,添加逻辑校验给定的地址中是否包含已经映射的地址即可。sys_munmap 根据 sys_mmap 的实现反推即可实现。

lab3

第五章将前面章节中的任务进化为“进程”,讲解了进程的含义、进程的核心数据结构及进程调度。在本章中进程 (Process) 的含义是在操作系统管理下的程序的一次执行过程,是系统进行资源分配的基本单位,进程标识符是进程的id,表示进程的唯一性。内核栈保存着进程运行期间的数据,进程控制块是内核对进程进行管理的单位,等价于一个进程。任务管理器仅负责管理所有进程,处理器管理用于进程调度,维护进程的处理器状态。同时实现了使用 SBI 的 console_getchar 从用户键盘读取输入的用户程序 shell,以及fork 的实现要注意子进程的返回值。

la3 spawn 的实现调用任务控制块(TaskControlBlock)的 new 方法基于 elf 创建一个新的任务控制块,然后将其添加到当前任务的子线程集合,最后调用 add_task 将其添加到任务调用队列。

lab4

第六章着手文件系统,讲解了文件与文件描述符、标准输入与标准书输出。rCore 对文件系统的设计进行了大量的简化,在进程中结构中添加了文件描述符表,用于语录所有它请求内核打开并可以读写的文件集合。进程通过文件描述符在自身的文件描述符表中招到对应的文件进行操作。rCore 将文件系统独立为 easy-fs 模块,设计感觉比 fat12 简单,分为五层,磁盘块设备接口、块缓存、磁盘数据结构、磁盘块管理器、索引节点,采用 virtio-drivers 库完成了对 qemu 虚拟磁盘的驱动。

lab4 sys_link 的实现参照 Inode 的 create 方法,创建 DirEntry 指向同一个 inode_id,不同的文件名称。sys_unlinkat 选择使用 modify_disk_inode 方法遍历根目录,在所有的 DirEntry 中查找名称一致的,将其赋值为 empty。ys_stat 主要关注 nlink, 在 DiskInode 中添加 nlink 字段用于记录文件对应的硬链接数量,并且在 DiskInode 初始化、添加硬链接以及删除硬链接时,对应修改其数值。

第七章开始实现 Linux 中常用的管道符,管道是一种由操作系统提供的进程间通信机制,可通过编程或在shell程序的帮助下轻松地把不同进程的输入和输出对接起来,实现不同进程功能的组合。同时还有基于“事件通知”需求的信号机制,信号是操作系统间通信的一种异步机制,用来提醒某进程一个特定事件已经发生,需要及时处理。与硬件中断进行比较,我们可以把信号描述为软件中断,它们都可以用某种方式屏蔽,还细分为全局屏蔽和局部屏蔽。

lab5

第八章开始为 rCore 支持线程,线程的实现依托于进程,是进程的组成部分,进程可包含 1 – n 个线程,属于同一个进程的线程共享进程的资源。在有了线程后,进程是线程的资源容器,线程成为了程序的基本执行实体。基于前面进程的设计将进程重构,将线程相关数据结构整理转移到线程控制块中,并用进程管理线程控制块,完成相关结构的设计,沿用第三章的任务上下文切换及特权级上下文切换即可。互斥锁、信号量以及条件变量相关机制的引入解决了由多线程引起的线程同步问题,完成了对共享资源的临界区保护,实现的方案有基于原子指令 CAS 及 CAS,以及关闭中断。同时需要考虑实现让权等待防止忙等待占用 CPU 资源。

lab5 使用银行家算法完成死锁检测,对相关逻辑封装为结构体 DeadlockChecker,在 mutex 和 semaphore 分配时设置可用资源,在申请锁和释放锁时相应的进行资源申请的安全检测和资源的释放。根据提示使用银行家算法完成相关测试,个人感觉有待商榷,实现的有些取巧,后续在整理优化一下。


本次训练营收获颇丰,对操作系统的原理有了更多的理解,巩固了理论知识,也加强了我对 Rust 的掌握。本次课程未能加入多核相关内容,稍有遗憾。非常感谢训练营的各位老师以及LearningOS的所有贡献者们提供的机会及相关资料。

起源

之前就看到了rcore训练营,但是报名后没有没加到群里,群满了无法再加,工作忙和后续的几次时间也没对上,所以就自己按照rcore的教程进行学习一步一步的写出了自己的一个os。

这次刚好新开营时间对的上,看到最后阶段有新东西,很兴奋所以加入了这次训练营

rust

其实个人学rust已经学了很久了,在工作里也在用rust写项目,也实现过网络框架,所以这次rust语言对我来说并不是什么问题,个人的技术站主要是java、go、rust和一些c(主要是一些ebpf和CRIU是c写的,所以需要看懂)

用了c和rust以后很明显的感觉就是内存管理rust编译器可以帮忙管理,而减少悬垂指针等等非法内存异常,rust也会对数组进行检测,当然有时候为了速度优化的话也可以用unsafe去除这些下标检测

整体来说就是rust提供了半自动化的内存管理和编译器的生命周期检测来帮助我们减少内存安全问题,让问题尽量暴露在编译器而不是运行期,外带cargo和rust的高级抽象能力,这些都是rust的生产力,也很适合多人协作大型项目。

二阶段

其实对于代码整体结构来说,已经看过一次了,并且跟着教程写了一个,所以代码结构对我来说并不是太大的问题,主要是时间的问题,对于上班党来说时间是真的很紧,基本下班以后就在做,而且会做到比较晚

  1. lab1

    其实是一个很简单的lab,taskinfo和系统调用的次数统计,对于了解代码结构的我来说不是什么特别难的事,很轻松就完成了
    问答题是很好的问题,也帮助我回忆和加深了risc-v的寄存器的作用,包括trap的流程,这个很重要,直接以代码展现出来,没学rcore的时候平时听到系统调用,其实是很抽象的,并不知道系统调用是怎么从用户态切换到内核态的,而rcore非常精彩的给我解答了这个问题,并且以代码展现,不再抽象。只能说感谢开源!

  2. lab2

    mmap 和 munmap 匿名映射,对我来说其实也不难,不过反而是问答题让我再次加深了SV39的结构,页表,页表项等等这些其实理解很抽象,包括用户态是怎么用到MMU的,MMU和操作系统存储的页表这些是怎么结合的,在这一张再结合linux的一些代码就理解了。其实是riscv 使用 SATP 寄存器来保存 MMU 映射表的根地址

  3. lab3

    spawn和stride 调度算法,这个其实也不算特别复杂,在给予fork和exec代码中只需要理解 spawn和他们的区别,就很容易写出来,而stride调度算法用一个小顶堆实现即可。因为之前看了linux的task_struct的实现,所以比较轻松就能理解。

  4. lab4

    这个要求实现linkatunlinkat 这个加深了我对硬连接的理解,并且文件系统的这章让我对linux的vfs也更加理解,以及操作系统是如何读取磁盘的数据的不过这个lab有一个坑,那就是在spawn以后再次加载文件就无法读出来文件,需要去修改TaskControlBlock 结构体上加#[repr(align(64)] 不知道具体原因还。整体来说明显会感受到磁盘读取和内存有异曲同工之妙。

  5. lab5

    死锁检测,其实一开始我看到那个要求我是懵的,看了一眼算法,大概理解想干啥,但是比较抽象,后来才知道这就是银行家算法,我觉得这里不如直接先说这是银行家算法,再给出后续的数据结构的定义,这个就非常考察细心了,主要就是资源的分配、分出、释放,需要格外注意,否则都无法通过,顺带这里也有一个坑,就是检测用了sleep,sleep用的是get_time,所以要实现这个api不然程序就会卡在那

总结

整体来说,rcore是很好的一个课程,让人对操作系统的实现理解更加深刻,包括和cpu的互动,trap、switch、磁盘、内存、甚至在问答题里还加入了扩展Kernel page-table isolation 这个让我又一次掌握了新姿势,也理解了为什么要创建跳板。

学完收获很大,怀着对底层知识的渴望打开rcore,理解底层知识,对我来说感到非常的开心,理论不再是抽象,而是具体的实现。

感谢开源!

Rust

Rust 是一种现代的、安全的系统编程语言,它提供了许多强大的工具,帮助我们编写高性能、可维护和安全的代码。以下是我从 rCore 学习中获得的 Rust 相关经验:

  • Ownership 和 Borrowing: Rust 强制实施的所有权和借用规则使代码更加健壮,防止了许多常见的内存错误。我学会了如何正确地管理内存,避免了悬垂指针和数据竞争等问题。
  • Pattern Matching: Rust 的模式匹配语法非常强大,使代码更加清晰和易于理解。在 rCore 项目中,我频繁地使用模式匹配来处理不同的系统调用和中断。
  • 并发编程: rCore 需要处理并发和多线程编程,Rust 提供了强大的并发工具,如 std::thread 和 std::sync,帮助我编写线程安全的代码。
  • Unsafe Rust: 尽管 Rust 鼓励安全编程实践,但在系统编程中,有时需要使用 unsafe 关键字来绕过 Rust 的安全检查。我学习了如何谨慎使用 unsafe,以确保代码的正确性。

RISC-V

rCore 项目选择了 RISC-V 架构作为操作系统的目标平台。学习 RISC-V 架构为我提供了对计算机硬件的深刻理解,并且在操作系统开发中也起到了关键作用。以下是我从 rCore 学习中获得的 RISC-V 相关经验:

  • 指令集架构: RISC-V 是一种精简的指令集架构,易于学习和理解。我学习了 RISC-V 汇编语言,包括指令集的基本操作和寄存器管理。
  • 中断处理: 在 rCore 中,我需要编写中断处理程序来处理时钟中断和外部中断。了解 RISC-V 中断处理的原理对于操作系统开发非常重要。
  • 内存管理: RISC-V 提供了虚拟内存管理的支持,我学习了如何配置页表和虚拟内存空间,以实现内存隔离和保护。
  • 系统调用: rCore 需要与硬件交互,执行系统调用。了解 RISC-V 中的系统调用约定和 ABI 对于操作系统的正确实现至关重要。

rCore-Tutorial

rCore-Tutorial 是一个非常有用的资源,提供了有关 rCore 项目的详细教程和示例代码。通过 rCore-Tutorial,我学到了很多关于操作系统开发的实际知识,包括以下内容:

  • 操作系统结构: rCore-Tutorial 教导了我操作系统的基本结构,包括内核、文件系统、进程管理和设备驱动等方面。
  • 实践项目: 通过跟随教程,我实际编写了操作系统的各个组成部分,这帮助我巩固了理论知识。
  • 社区支持: rCore-Tutorial 社区非常友好和支持,我在学习过程中得到了很多帮助和反馈。

总的来说,rCore 项目是一个令人兴奋的学习机会,我从中学到了大量有关 Rust、RISC-V 和操作系统开发的知识。通过 rCore-Tutorial,我不仅提高了编程技能,还加深了对计算机系统的理解。我鼓励其他学习者积极参与这个项目,探索更多有关操作系统和系统编程的奥秘。

本次训练营对我最大的帮助在于,让我深入了解了操作系统底层的工作原理,通过教程的辅助和源码的阅读,我了解到了操作系统在进程线程管理、任务调度、内存分配、文件系统管理等方面的知识,对其实现的机制有了初步的认识。

从操作系统环境的搭建开始,我不断的给系统增加功能,引入进程、页表、地址空间、文件系统、同步机制,让操作系统能够运行一个个进程与线程,处理并发场景的任务,这是一场具有挑战却又精彩的旅程。

从进程或线程的创建和调度角度来说,我了解到了操作系统如何把一个elf格式的文件转换为一个进程,为它分配地址空间、栈空间等,以及如何在进程中执行任务,以及创建新的线程。

elf文件中定义了程序的内存布局,而操作系统将为它创建地址空间,给它分配进程号并将各个逻辑段存放在合适的位置,之后将进程添加到调度队列中,由CPU执行。其中很重要的数据结构为TrapContext,它记录了进程或者线程的上下文信息,在切换任务时发挥着巨大的作用。而其中又涉及到了内存的分配,当进程需要分配内存时,由页表完成从虚拟地址到物理地址的映射,整个映射的过程也是我学习过程的一个重难点,从分配物理页号到创建多级页表进行映射,以及如何使用虚拟地址向物理内存中写入数据,通过思考、绘图去展示整个过程,我慢慢的理解了其中的所以然,感受到了虚拟内存系统的精妙的哲思。

文件系统部分,我学习到了一个简易文件系统的实现,对于文件系统如何管理磁盘文件、以及如何在内核中使用文件有了一个新的认识。首先最令我费解的是描述文件的一系列数据结构,难点在于DiskINode和INode的关系和作用。经过一遍遍理解和梳理,我认识磁盘上的第一个分块将会用于存储整个磁盘的元信息,包括各类块的数量,之后的块分别记录索引位图、索引、数据位图和数据。索引中记录了一个文件的类型、大小以及在磁盘上的块号,通过索引可以找到文件所在的位置,对文件进行读写,这个索引即是DiskINode,而INode用于记录DiskINode的位置,该数据结构将暴露给内核,方便在内核中对文件进行操作。

在并发的学习过程中,我对锁的实现机制有了更多的了解。当某个线程去访问被上锁的临界区时,线程将会从任务调度队列中移入到等待队列,由此陷入等待(休眠),在锁释放的时候再从等待队列中唤醒线程,以达到同步的效果,从而避免数据不一致问题。也试着去实现了一个简单的死锁检测机制,但对该机制的原理理解的还不够,无法证明为何该算法能够实现死锁检测。

教程和练习引导着我的学习与进步,让我对操作系统有了更多的理解,但它们无法为我解释所有的问题,也无法让我看懂所有的代码。当前的学习虽说已告一段落,但整个项目中还有很多细节值得我去细细品味。那些我未阅读或未理解的源码,以及关于os的设计原理–为什么要这样设计,是否有更加合适的设计方式,这对于我来说都是未完成的任务,也是将来需要探索的。

最后,我想感谢训练营,能提供这么好的机会和资料来帮助我学习os,今后我也希望能有更多的机会去深入理解系统级别的设计与开发,加油!

第二阶段总结

学习感受

  • 在参加rCore训练营之前,对于操作系统的相关知识我是比较模糊的,在平时的编程中往往只是简单用到一些比如协程、多线程、原子计数、互斥锁等等编程语言提供的编程模型,对于其内在实现基本一知半解,而在这次阅读了rCore相关教材之后,对这些函数背后所隐藏的机制有了更深的了解。在训练营的过程中,因为之前有用Rust经验,所以第一阶段Rustlings能够比较快速的解决,但是到了第二阶段,因为对于相关知识比较陌生所以做的比较慢一些,unsafe Rust、risc-v汇编这些平时接触非常少,所以在阅读相关代码的时候要查阅较多资料。目前虽然完成了第二阶段晋级的目标,但是感觉前方未知的领域更加宽广。
  • rCore依托于Github的授课方式也让我大开眼界,第一次知道Gihub Classroom这个服务,老师们围绕OS的学习建立的这一整套学习方式(Github Actions、排行榜、直播课网站),不仅仅局限于书本,把计算机专业和教学的完美结合到一起。同时rCore的社区感觉也已经很庞大了,已经有许多人做出了自己的贡献,虽然我目前还没有厉害到可以给rCore提PR,但是希望以后可以。

学习总结

  • 其实我学习Rust的开端比较早,就是看到各大视频网站有很多关于Rust如何安全的宣传视频,当时也没有适用场景和刚需,单纯出于好奇就开始了自学,在学完之后也就仅限于用来做一些比如Advent of Code的编程题,但是其间经常由于Rust严格的检查而被Rust折磨🤣。rCore把Rust用到了系统级的编程领域,难度更是加大了,因为要理解这些代码,首先要理解其背后隐含的OS设计思想,这些就是我对于Rust学习的感受。

Lab总结

Ch3 Lab

第一次做OS相关Lab,在起步时熟悉整个代码框架是比较重要的,这样在做题时才能找到相关的接口,Ch3 lab主要关于task info的获取,需要编写函数来获取每个task的相关信息。

  • 为TaskControlBlock增加field

    • start_time: usize
    • syscall_count: [u32: MAX_SYSCALL_NUM]
  • os/src/task/mod.rs

  1. 为TaskManager实现syscall_count_increment()
    主要逻辑:
    • 使用match来匹配syscall id,从而决定增加conut的对象。
    • 并在trap_handler中调用。
  2. 在run_first_task()中使用get_time_ms()获取当前时间
  • os/src/syscall/mod.rs
  1. 为TaskInfo实现reveal()
    主要逻辑:
    • 计算运行时间
    • 从Task Control Block中获取task info
      在sys_task_info()调用

Ch4 Lab

在初读关于内存空间的内容时经常一头雾水,各种术语经常忘了意思,需要经常翻速查表、文档和笔记。
因为地址空间的跳转,所以之前的trap的实现也要修改。

  • os/mm/memory_set.rs
    • mmap
      • 将vpn与ppn关联,从遍历整个VirtualPageNum,若已存在对应pte,则返回失败,若不存在则分配ppn
    • munmap
      • 将vpn从page table移除
  • os/syscall/process.rs
    • sys_get_time()
      • 用 translated_byte_buffer 将用户地址转换为对应的物理地址,然后读取TimeVal大小的内存
    • sys_task_info()
      • 同理先转化为对应物理地址,然后读取TaskInfo大小的内存,就可以获取task_info

Ch5 Lab

Lab 5实现spawn和stride调度算法,spawn一个子线程需要创建一个新的TaskControlBlock,然后再push到当前任务下

  • os/syscall/process.rs

    • sys_spawn()
      • 首先获取app data(get_app_data_by_name), 再根据data生成新的task
    • sys_get_priority()
  • os/task/manager.rs

    • 实现stride算法
      • 对TaskManager的ready_queue进行迭代,使用min_by_key()找到当前最优先的TaskControlBlock

首先得说,rCore-Tutorial-Guide-2023A教程真的太棒了,我敢说,这可能是我遇到的最清晰明了的操作系统教程!

回忆起第一次听到rcore训练营的经历,那是在2023年的Rust大会上,李明老师的精彩演讲为我打开了新世界的大门。尽管我主要在应用层开发上浸淫,但Rust的魅力让我难以抗拒,所以,当知道2023秋季的rcore开营消息后,我和好友毫不犹豫地跃跃欲试。

rustlings部分我觉得挺熟悉的,之前已经玩转了好几回。但真正进入第二阶段,我有点像被冻住的鹿在大灯下,感到前所未有的压力。工作和时间的双重压迫让我迟迟无法启动,但最后的决心驱使我开始啃这块硬骨头。哗啦哗啦地一页页翻,直到lab1的系统调用实现,我才有那种“原来是这样!”的豁然开朗。这也让我真切体会到,教程的设计对新手真的非常友好。

再往后,lab2就开始考验我的耐心了。我承认,我对sys_get_timesys_task_info的实现确实走了点弯路。原先,我采取了translated_byte_buffer来复制数据,试图仿照sys_write。但和友人交流后,我发现其实可以直接通过用户虚拟地址查找物理地址来进行数据复制,真是巧妙极了!同时,mmap和munmap的相关内容,虽然看似复杂,但仔细读一读,原来也不是那么的玄乎。

到了lab3,真的觉得有点上瘾了。这部分主要是spawn的实现。参考了exec和fork,再结合man posix_spawn的文档,整个流程其实很流畅。至于stride调度算法,哦,那真是让人爱不释手。一个简洁的lambda表达式,搞定!虽然说,我的实现可能有点粗糙,但它确实工作得很好。这也提醒了我,在软件开发中,有时候“简洁”和“完美”之间需要做出平衡。这也是我接下来要努力提升的方向。

虽然我对这三个实验的时间安排有点后悔,但回首这次训练营的经历,真的获益匪浅。对于未来的实验,我更加期待,希望能够带给我更多的启示和惊喜。

总之,这次短暂的OS训练营给我留下了深刻的印象。高质量的教程、热心的群友,真的是让人难以忘怀的体验!

前言

偶在某个群里听说了开源公开课的存在,讲OS就带你实现一个小型OS,讲计网就带你实现计网的5层架构。作为一个对底层感兴趣,又忍受不了学校课程的照本宣科的人,心想这样的课程也太有意思了吧,于是开启了我自学公开课的旅途。

关于开源操作系统训练营

说来很巧,在我将要把6.s081刷完的时候,在相关互助群里听说了这个开源操作系统训练营,于是自然地抱着进一步学习的想法就报了名。现在看来这门课确实符合我的期待:

  • 和学校课程最大的不同也是开源课程共同的优点,不会单讲理论,而是结合实践真正地实现一个能够使用的程序
  • 能够学习更加现代化的rust语言,其中的包管理器和所有权确实很实用
  • 由于xv6和rCore都是基于riscv的,很多功能的实现方式都是差不多的,所以学习难度很平缓,又能巩固之前所学
Read more »