|
| 1 | +--- |
| 2 | +title: 2025s-oscamp-xxxuuu |
| 3 | +date: 2025-05-22 22:54:44 |
| 4 | +mathjax: true |
| 5 | +tags: |
| 6 | + - author:xxxuuu |
| 7 | + - repo:https://github.com/LearningOS/2025s-arceos-xxxuuu.git |
| 8 | +--- |
| 9 | + |
| 10 | +偶然有天看到 Rust 中文社区的公众号转发了一个开源 OS 训练营的消息,点进去一看原来是 rCore,早就听过 rCore 这个项目,但一直没来得及学习,自己也对 Rust 在嵌入式开发和内核中的应用很感兴趣,于是转发到群里忽悠了几个朋友一起组团参加 |
| 11 | + |
| 12 | +<!-- more --> |
| 13 | + |
| 14 | +训练营总体分为四个阶段,前三个阶段是练习和实验为主,第四阶段是偏自主探索的项目部分 |
| 15 | + |
| 16 | +## 第一阶段 |
| 17 | + |
| 18 | +第一阶段是 Rust 基础的学习,基本是完成 rustlings 训练即可,由于之前已经有了一定 Rust 基础,所以没什么障碍很快就做完了 |
| 19 | + |
| 20 | +## 第二阶段 |
| 21 | + |
| 22 | +第二阶段是 rCore 的实验,跑在 RISC-V 架构上。ch1 是如何运行一个裸机程序的简单指导,正式的内容从 ch2 开始 |
| 23 | + |
| 24 | +### ch2 批处理系统 |
| 25 | + |
| 26 | +ch2 实现了一个批处理系统: |
| 27 | + |
| 28 | +- 允许按顺序执行多个应用程序,这些应用在构建时被静态链接到内核 binary 中。启动应用时复制其 binary 到入口地址 `0x80400000` 上,然后保存上下文切换状态跳转执行 |
| 29 | +- 实现了特权级切换机制: |
| 30 | + - 应用在 U 特权级运行,通过 `ecall` 触发 Trap 切换到 S 特权级 |
| 31 | + - 内核处理系统调用或错误后,通过 `sret` 指令返回U特权级继续执行 |
| 32 | +- 提供了错误处理机制: |
| 33 | + - 当应用访问非法地址或执行非法指令时触发 Trap |
| 34 | + - 内核可以终止当前应用并执行下一个 |
| 35 | + |
| 36 | +核心组件包括: |
| 37 | + |
| 38 | +- AppManager:负责应用的加载和运行管理 |
| 39 | +- TrapContext:保存 Trap 发生时的上下文信息,包括通用寄存器和 CSR |
| 40 | +- 两个特权级切换的关键函数:`__alltraps`(保存上下文) 和 `__restore`(恢复上下文) |
| 41 | + |
| 42 | +### ch3 多道任务与分时多任务 |
| 43 | + |
| 44 | +相比 ch2 执行完一个才执行下一个的批处理系统。ch3 实现了分时多任务 |
| 45 | + |
| 46 | +有几个主要变化: |
| 47 | + |
| 48 | +1. 需要能同时加载多个程序,ch2 每个程序被加载到固定的地址中运行。ch3 则为每个程序划分了单独的地址范围 |
| 49 | +2. 任务切换,ch3 需要在多个任务间切换,要为每个任务保存单独的上下文,任务间切换时也是在 Trap 中进行,通过 `__switch` 保存当前任务上下文,设置新任务上下文,`__restore` 再回到用户态。流程变成 `__alltraps` → `__switch` → `__restore` |
| 50 | +3. 提供了一些新的系统调用允许用户态程序自行让出,例如 `sys_yield`,以及为用户态程序添加了对应的状态以进行管理和维护 |
| 51 | +4. 设置时钟中断,在中断中切换任务,实现抢占式调度 |
| 52 | + |
| 53 | +Lab: |
| 54 | + |
| 55 | +- 实现一个系统调用 `sys_trace`,可以读取、修改内存地址,以及追踪系统调用次数 |
| 56 | + |
| 57 | +比较简单,在 `TaskManger` 里加一个 map 统计每个 task 的调用计数即可 |
| 58 | + |
| 59 | +### ch4 地址空间 |
| 60 | + |
| 61 | +ch4 实现虚拟内存,在前面的部分中,我们都是直接读写的物理内存 |
| 62 | + |
| 63 | +虚拟内存并不是完全由内核纯软件实现的机制,而是软硬件结合的。RISC-V 64 中包括 SV39 和 SV48 两个虚拟内存模式 |
| 64 | + |
| 65 | +SV39 模式中,所有 S/U 特权级下的访存都被视为 39 位的虚拟地址。页面大小 4KiB,页表项大小 8 字节,能支持 56 位($2^{26}$ GiB)大小的物理地址 |
| 66 | + |
| 67 | +内核现在需要维护页表本身的信息,即元数据的存储,包括页帧,虚拟页和物理页的映射等 |
| 68 | + |
| 69 | +另外,内核现在读写的也是虚拟内存。rCore 采用了内核和应用地址空间隔离的设计(与 Linux 中应用地址空间和内核映射在一起的设计不同),在特权级上下文切换时需要切换地址空间。过程中必须确保切换前后的正常运行,例如切换后的下一条指令地址也需要能正常访问,这里通过 trampoline 技巧实现了这一点 |
| 70 | + |
| 71 | +Lab: |
| 72 | + |
| 73 | +1. 重写 `sys_get_time` 和 `sys_trace`,参考 `sys_write` 即可 |
| 74 | +2. 实现 `mmap` 和 `munmap`,但不包括文件和 I/O 相关的映射,类似于 linux 中 `mmap` 带上 `MAP_ANON` flag。参考每个 Task 维护的 MemorySet 内容即可 |
| 75 | + |
| 76 | +### ch5 进程及进程管理 |
| 77 | + |
| 78 | +ch5 主要实现了进程管理机制,引入了一个简易的 shell 来运行应用程序。核心改动包括: |
| 79 | + |
| 80 | +- 新增了三个核心系统调用:`fork`、`exec` 和 `waitpid` |
| 81 | +- 进程机制的改进: |
| 82 | + - 链接和加载机制改为按应用名进行 |
| 83 | + - 引入进程控制块(PCB),将应用 id 改为 pid,并维护子进程信息 |
| 84 | + - TaskManager 职责部分转移到 Processor,支持每核心一个 Processor(当前仅实现单核) |
| 85 | + |
| 86 | +Lab: |
| 87 | + |
| 88 | +- 重写 `sys_get_time` 和 `sys_mmap`/`sys_munmap` 系统调用,适配新的进程结构 |
| 89 | +- 实现 `spawn` 系统调用,效果上类似 `fork` 和 `exec` 的组合(但没有复制地址空间的必要了) |
| 90 | +- 实现 `stride` 调度算法,注意到调度时通过 `TaskManager` 的 `fetch()` 来获取任务,在这实现即可 |
| 91 | + |
| 92 | +### ch6 文件系统与 I/O 重定向 |
| 93 | + |
| 94 | +ch6 实现了文件系统与 I/O 抽象。核心包括: |
| 95 | + |
| 96 | +- 使用第三方的 VirtIO 驱动库实现块设备 I/O |
| 97 | +- 设计了单层目录的文件系统 easy-fs,独立为一个单独 crate 实现: |
| 98 | + - `DiskInode` 作为磁盘上的索引节点 |
| 99 | + - `Inode` 作为内存中暴露给用户的结构体 |
| 100 | + - `DirEntry` 维护文件名称和 inode_id 的映射 |
| 101 | +- 应用程序改为以文件形式动态加载,不再直接链接进内核 |
| 102 | + |
| 103 | +Lab: |
| 104 | + |
| 105 | +- 实现 `linkat`/`unlinkat` 系统调用: |
| 106 | + - 只要理解了文件系统的底层数据分布就比较简单了 |
| 107 | + - 在 `DiskInode` 中添加链接计数 |
| 108 | + - 实现文件的硬链接创建和删除 |
| 109 | +- 实现 `fstat` 系统调用: |
| 110 | + - 可以扩展 `File` trait 增加 `stat` 接口,为 `Inode` 添加 `inode_id` 字段以获取文件信息 |
| 111 | + |
| 112 | +另外需要注意文件系统中的锁使用,这里不支持重入,需要避免嵌套 lock 导致死锁 |
| 113 | + |
| 114 | +### ch7 进程间通信 |
| 115 | + |
| 116 | +ch7 扩展了进程间通信机制,包括: |
| 117 | + |
| 118 | +- 管道(pipe)机制的实现,允许进程间通过管道进行数据传输 |
| 119 | +- I/O 重定向功能,使进程能够重定向其输入输出流 |
| 120 | + |
| 121 | +这些功能为进程间的数据交换和通信提供了基础设施支持 |
| 122 | + |
| 123 | +### ch8 并发 |
| 124 | + |
| 125 | +ch8 主要实现了线程管理和锁机制: |
| 126 | + |
| 127 | +- 线程管理:在 `TaskControlBlock` 中实现线程,与进程共用地址空间但有独立栈指针 |
| 128 | +- 锁实现:包含两种锁机制: |
| 129 | + - `MutexSpin`:非阻塞锁,使用自旋等待和 yield |
| 130 | + - `MutexBlocking`:阻塞锁,维护等待队列进行线程阻塞和唤醒 |
| 131 | + |
| 132 | +Lab: |
| 133 | + |
| 134 | +- 实现死锁检测和避免,文档中给的是类似银行家的算法 |
| 135 | + |
| 136 | +### 总结 |
| 137 | + |
| 138 | +阶段 2 更多是读文档和熟悉 rCore 代码,实际开发工作量不高,每个实验基本不到百行。但实验文档写得非常详细,质量很高,每个设计也很不错 |
| 139 | + |
| 140 | +也体会到 Rust 在内核开发上,得益于强大的类型系统和零成本抽象设计,确实比 C 更加具有表现力,体验更好 |
| 141 | + |
| 142 | +## 第三阶段 |
| 143 | + |
| 144 | +三阶段是 ArceOS,包括六个实验 |
| 145 | + |
| 146 | +### **print_with_color** |
| 147 | + |
| 148 | +提供一个带颜色的 `println!` 宏即可,主要是熟悉 ArceOS 整体结构 |
| 149 | + |
| 150 | +### **support_hashmap** |
| 151 | + |
| 152 | +此时内核里已经有了 allocator,所以可以使用 `alloc` 里的 `Vec` 等集合(`std ::vec` 是 `alloc::vec` 的 re-export),但 hash map 的 hash 函数依赖随机数生成器,这需要系统提供,所以不在 `alloc` 中,这里就需要我们添加 `HashMap` 支持 |
| 153 | + |
| 154 | +实现上可以用简单的多项式 hash 套 `Vec<Vec<T>>` 即可,代码百行左右,Trait 的设计可以参考标准库里的 `Hash` 和 `Hasher` |
| 155 | + |
| 156 | +### **alt_alloc** |
| 157 | + |
| 158 | +实现一个简单的 bump 内存分配器,同时作为字节分配器和页分配器。这个分配器很简单,只需要递增分配内存空间即可,甚至不用考虑回收实现 |
| 159 | + |
| 160 | +这里也可以看出 ArceOS 的可插拔设计能够很灵活地替换各个组件 |
| 161 | + |
| 162 | +### **ramfs_rename** |
| 163 | + |
| 164 | +为 ramfs 支持 rename 操作,这个地方比较坑的是跨 mount point 的处理有问题,这导致在 rename 时无法跨 mount point 操作,但涉及底下框架无法修改,只能实现成同目录下的操作 |
| 165 | + |
| 166 | +### **sys_map** |
| 167 | + |
| 168 | +实现 mmap,相比 rCore 的这回是可以映射到文件上了。但实现是让 mmap 时直接把文件内容写到内存里,Linux 里的 mmap 应该是一个特殊的缺页处理函数,只有当缺页时才会去读入这个文件内容 |
| 169 | + |
| 170 | +### **simple_hv** |
| 171 | + |
| 172 | +这里开始涉及虚拟化的内容,要给 vmm/hypervisor 添加 guest 的两个 trap 处理,一个是特权指令,一个是缺页。理论上这里应该要解析 guest 指令然后在 vmm 执行后再把结果设置回去,但实际上只要给 guest 寄存器写死固定值即可 |
| 173 | + |
| 174 | +### 总结 |
| 175 | + |
| 176 | +三阶段难度比预期要低,ArceOS 本身还是非常不错的项目,但实验的测试用例有点过于水了,导致很多不完善甚至错误的实现也能轻松通过测试。从教学角度个人感觉体验不如阶段二,但可以学习 ArceOS 本身的设计和代码,对比 rCore 的差异和改进 |
| 177 | + |
0 commit comments