Caiwen 的 2025 年春季操作系统训练营一二阶段的学习笔记
2025春夏操作系统训练营第二阶段总结-heirish
Rust补完计划
Rust补完计划
src:rust官网,rust官文,rust官仓,crates.io,rust-wiki,卡狗圣经
Rust可看作一个在语法层面(编译时)具有严格检查和限制的C语言上位。且扩展了面向对象的便捷方法绑定。编译和运行方式类似于C/C++,可以rustc xxx.rs编译,./xxx运行。有约定的项目目录格式,可使用Cargo配置toml进行包管理、编译、运行、测试等等。包资源网站为CratesIO,见src↑。不支持运算符重载,支持多态。其中语句为:表达式+;,语句的值是()。
为了安全,几乎所有的方法/变量/属性都是私有的,除非使用pub进行显式公用声明。
说到底,编程语言就是人类用来快速生成机器码以便于执行的模板引擎,有的语法层面(编译时/解释时)有强约束,有的仅仅是把特定字符串替换成另外的字符串或二进制,属于弱约束或者无约束。所有你在编程语言所看到的抽象,在机器码的层面本来就是一场幻月楼阁。比如你在编程语言层面,继承多态面向对象权限生命周期搞的花里胡哨的,但是在机器码看来,就仅仅是把PC变一下,或者某数据/指针变一下而已,你所关心的语法层面,语义特性,都是高层编译时/解释时的语法约束。这些约束让你写正确的高级语法的同时,最重要的是保证执行的结果符合预期。所以学底层的,一定要层层解耦,梳理层层抽象!
快速开始
安装:
1 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
推荐开发环境:
VSCode + rust-analyzer,VIM,RustRover
见面礼:
1 | // main.rs |
1 | rustc main.rs -o main && ./main |
使用包管理器:
1 | cargo new my_project_name # 生成项目目录结构,包括`toml`、`lock`、`src`、`test`等等 |
包管理器代理:vim ~/.cargo/config.toml
1 | [source.crates-io] |
初次接触
语法语义一览表:
标识符:
^[a-Z0-9_][a-Z0-9_$]*其中,命名习惯为:CONST_VAL,StructName,ImplName,method_name,val_name注释符:
// 单行注释,/* 多行注释 */,/// 文档注释,支持MD语法以及文档测试以及自动生成文档运算符:
+ - * / % += -= *= /= %= ! ~|& ^ [] ; , >> << == != < <= > >= && ||变量声明:
const CONST_VAL: i32 = 123;static STATIC_VAL: u32 = 321;let emm = 233;let mut var = 0;类型别名:
type word = u64类型转换:
var as type_nametype_name::from(var)分支:
if 条件必须是布尔值 { ... } else { ... }match obj { case xx => { ... }, _ => { ... } }循环:
loop { ... }for i in 0..10while n < 233,breakcontinue支持循环标签来跳出指定的循环:
tag1: loop { tag2: while true { break: tag1 } }函数格式:
fn add(a: i32, b: i32) -> i32 { a + b }默认返回值是最后一条表达式的值,等同于:return a+b;匿名函数:
|a: i32, b: i32| -> i32 { a + b }如果只有一条执行语句,可以省略大括号类和对象:采用结构体
struct(存数据)和特质trait(类似于抽象interface存方法)抽象,数据方法分离的思想方法多态:方法和数据的关系是多对多,支持采用数据/特质签名来访问匿去的数据/方法:
TraitA::fun1(&obj)基本类型:i8 u8 i16 u16 … 有无符号+位数,str,bool,f64 … 同整型
类型变体:
&i32- 不可变引用,&mut- 可变引用,*const- 不可变裸指针,*mut- 可变裸指针容器类型:
[1, 2, 3]-Array- 定长同类型,(1, "heke", 1228)-Tuple- 定长不可变数据容器:
struct Person { age: u8; name: &str; }struct Bag (i32, i32, u8)枚举类型:
enum State { StateA, StateB, State233=233, ,PA(ch) ... }详见特殊部分与模式匹配↓其他容器:
Vec,Deque,Queue,BTreeSet,BTreeMap…导入管理:
mod package_emm;mod mode_name { fn emm() { ... } }use mode_name::emm;异步支持:
async,await,std::thread,以及异步的通道和异步智能指针
快速迁移(将采用Py作为对比语言):
1 | def emm(a: int, b: int) -> float: |
1 | pub fn emm(a: i32, b: i32) -> f64 { |
1 | def emm(op) -> (int, str, int): |
1 | pub fn emm(op) -> (i32, &str, i32) { // op会在编译时自动根据所调用时的类型生成对应类型标签的多态方法 |
1 | class A: |
1 | struct A { |
1 | # method.py |
1 | # __init__.py |
1 | // method.rs |
1 | // lib.rs / mod.rs |
rcore-camp-2025S-stage1&2-颜熙炆.md
2025年春夏开源操作系统训练营三阶段总结-自由
2025年春夏开源操作系统训练营三阶段总结
第一阶段总结
第一次写rust,被所有权干烂了,不停和编译器搏斗+不停拷问AI终于磕磕绊绊写完了,以前习惯于C++的引用和指针乱飘了,写rust确实比C和C++安全,因为有编译器的所有权机制保障。
第二阶段总结
因为以前写过NJUPA(极其推荐),所以对上下文切换有一点概念。rcore-os的上下文切换采用了xv6类似的方式,在内核内部做控制流切换来切换执行的用户程序。对于操作系统中常用的锁,rcore采用了单核中的使用rust所有权机制来实现,上锁的时候将所有权转交给函数,尝试二次获取的时候就会因为所有权引发rust错误,避免了内核的死锁的情况。cargo的编译链接功能确实比GNU那一套方便,rust的属性语法支持也比C++要好很多。
地址空间和页表中极多的用来表示地址组成的结构体,充分体现了rust面向对象的特性和into的语法,增加了代码的可读性,以前用C系的写也就使用各种宏。rcore用了额外的BTreeMap来支持快速查询页表。作业题的实现也大多是调用现有的函数就差不多可以了。
到文件系统这里比较新鲜,因为以前没有怎么接触过,文件系统的实现层层嵌套,层层抽象,一个功能实现往往要修改多层,理解起来比较困难。
并发的作业是用Coffman提出的方法做死锁检测,开始的时候理解成了银行家算法,读作业提示的时候也没有读懂,卡了很长时间,在群u的提示下才通过。后来在上操作系统课的时候翻到书上才知道这是Coffman的死锁检测算法(书上的描述其实和作业里写得差不多)。
第三阶段
第三阶段换了个内核模式叫unikernel,不太习惯这样的组件化操作系统,还是习惯以前的传统宏内核结构。
print_with_color很简单,理解一下arceos提供的axstd模块,为内核应用提供了运行时环境。
support_hash_map在理解了rust可以自定义堆分配器后,唯一要解决的就是rust中如何得到一个hashable值的hash值,然后做简单的映射即可,测试不需要特别追求效率。
alt_alloc主要是实现一下需要的接口,了解如何向arceos添加自己的分配器,bump allocator可以说是最简单的分配器了。
ramfs_rename要把cargo里的包换成arceos下面的(不然文件系统的包永远是网上拉下来的),找到对traitVfsOps和VfsNodeOps的实现之后,搞清楚记录文件的数据结构,实现rename就可以了。
sys_map同样搞清楚task_ext提供的接口就可以直接调库了,第一次实现忘记输入地址是NULL的话要由内核自己找地址,遂发现了一个叫做find_free_area的妙妙工具。
simple_hv最简单的一次,同样的由操作系统代为执行一些指令的手法可以用来帮用户程序加载一些未对齐的数据(如果处理器不支持)。
初探操作系统
第二阶段总结:操作系统学习的感悟与理解
经过这一阶段对操作系统核心内容的系统学习和实践,我对操作系统的本质、关键机制以及与 Rust 结合的优势有了更深刻的理解。以下是我的主要体会和总结:
一、操作系统的本质与核心任务
操作系统是管理硬件资源、为上层应用提供抽象和隔离的基础软件。它的核心任务包括:
- 进程与线程管理:通过进程(资源分配单位)和线程(调度单位)实现多任务并发,保障系统的响应性和资源利用率。PCB(进程控制块)和 TCB(线程控制块)是操作系统调度和管理的核心数据结构。
- 内存管理:通过虚拟内存、分页机制和权限控制,实现进程隔离、内存高效分配与回收。页表、TLB、懒分配、写时复制等机制极大提升了系统的安全性和性能。
- 进程调度:采用多种调度算法(如时间片轮转、优先级、MLFQ等)实现公平与高效的 CPU 分配。上下文切换和调度策略直接影响系统吞吐和响应速度。
- 系统调用接口:为用户程序提供受控访问硬件和内核资源的通道,实现用户态与内核态的安全切换。
- 硬件抽象与性能优化:通过缓存、TLB、ASID 等机制优化访问速度,利用中断和异常机制实现高效的事件响应。
OrangeQi
之前对操作系统有一定理论基础,rcore 和 arceos 项目对我最大的挑战主要包括:
- risc-v 体系结构的知识,尤其是特权架构。这对理解 trap、context_switch、地址空间相关的代码极其重要。
- arceos 项目的组织构建。最底层是 axhal,抽象了硬件架构和运行平台,往上是各个 module 例如 axtask 等,再向上是 axapi 乃至 ulib。这种组件化的设计思想充分利用的 rust 语言的优势,极大方便构建。
unikernel 架构是没有特权级切换的,应用程序也运行在 s 态。刚开始没有仔细理解 ppt,给我造成了挺大的困扰。
hashmap 的实验我并没有自己手写代码,而是直接引入了 hashbrown 库。但手撕一下代码应该能更加锻炼能力。
此外,hypervisor 给我带来了挺大的困难,参考其他同学的经验我才得以通过。
rcore-handnote-1
Chapter 1
Execution Environment

The execution environment is defined by the Target Triplet, which specifies the platform, CPU architecture, and library required for the build. For example: x86_64-unknown-linux-gnu.
Components of the Target Triplet:
- Platform: The specific operating system or runtime environment.
- CPU Architecture: The underlying hardware architecture (e.g., x86_64, ARM).
- Library: The standard library or runtime support required.
If the target platform contains no std or any support syscall, such platform called bare-metal, Rust contains a core lib independent of any platform support.
If we change .cargo/config s.t.:
1 | # os/.cargo/config |
it called cross compile because the running platform is different form execution platform.
No Std and No Main
The basic functionality provided by std and start semantic is panic_handler and main entry.
To toggle it off with:
1 | #![no_std] |
RISCV
As for riscv, thing will be tough in here, we need to complete our own entry point, exit, and basic functionality like print/println.
First, we need to define linker and entry for stack allocation.
Linker:
1 | # os/src/linker.ld |
Stack Space:
1 | # os/src/entry.asm |
For riscv, we need to call RustSBI(a underlying specification for rust in riscv).
After implement sbi_call, we could construct put_char:
1 | const SBI_CONSOLE_PUTCHAR: usize = 1; |
With a formal interface for write:
1 | struct Stdout; |
Now we construct basic functionality in println, you could also handle panic_handler and others…
rcore-handnote-3
Chapter 3
Introduction
We need to place multiple app to multiple memory address to run app in cycle. Rather run once and clear for next.
First We want to place each app to each isolated addr, due to our kernel restriction, we need to load it with build.py.
Task
Task: a workflow process
Define every time slice of Task as Task Slice
Define the switch between app as Task switching
We need to store Task Context
Design:
We will store these register in ctx:
1 | // os/src/task/context.rs |
1 | .altmacro |
Expose to Rust
1 | // os/src/task/switch.rs |
We will design TaskManager:
- Store each App state array and current running app.
- each state store
TaskContextandTaskStatefor running or exited etc… - Init and ready by store the
__restorectx toTaskContext - Run for switch cx if needed.
1 | let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext; |
Dispatch Design
Collaboration
Manually design interface yield for App to use
1 | pub fn sys_yield() -> isize { |
But it can be inefficient for some case that app already done its work but reluctant to exit.
Preemptive
We will design interrupt clock in a fixed time bound to force switch between app.
- Set timer design and get time
- Set timer for trigger
- enable timer and handle the interrupt cause of Timer in
ecall
You should know this as a pre-knowledge:

1 | const SBI_SET_TIMER: usize = 0; |
rcore-handnote-2
Chapter 2
Introduction

It corresponds to riscv:
- Privilege for
S(guaranteed bySupervisor Execution EnvironmentofRustSBI) - User for
U(constructed in current chapter asApplication Execution Environment)
Reason:
- Safety(Prevent app from accessing kernel)
- Recoverable
Workflow:
- Start application and user-mode context
- Trap(Called by system level) to handle system
- Goes wrong! Kill it!
- Finish! Next!
- Restore to user-mode context
riscv designs following CSR(Control and Status Register) to handle this:
CSR

Begin Trap:
- sstatus:
SPPseg to the current level of CPU. - sepc: next addr after Trap finished.
- scause/stval: Trap cause and additional info.
- stvec: storage of entry addr of Trap
stvec is a 64-bit CSR, with:
- MODE(Direct/Vectored)
[1:0](read from right to left): 2-bits- BASE
[63:2]: 62-bits
finally, it will return by instruction sret which will change level and jump by sepc.
Construct Trap
Design:
- General register will be shared by U-level and S-level.
- Maintain a reasonable state of
CSR. - Separate workflow of U-level and S-level by stack
Construct:
- build
KernelStackandUserStackfor separation - in
KernelStack, we storeTrapContextin it, by asm and rust to control dispatch and handle, then store the code tostvecas the entry of Trap. - restore register for
UserStackby push a new context refer toUserStack.
build stack and push context:
1 | // stack struct ... |
1 | // os/src/trap/context.rs |
We will design __alltrap and __restore for operation by asm and part of rust:
1 | .altmacro |
To handle Trap context, we will use riscv lib:
1 | // os/Cargo.toml |
restore operation:
1 | extern "C" { fn __restore(cx_addr: usize); } |
Construct User App
- Link app binary to kernel with specify memory layout
- Read the layout, use
AppManagerto maintain and store - Load app from memory layout, copy consecutively to
APP_BASE_ADDRESS(Currently we have no ability to dynamically read address) - AppManager will run each app
1 | # os/src/link_app.S |
Design it!
1 | // os/src/batch.rs |
Load App:
1 | // part of code of copying to kernel |
Run each app!
1 | // os/src/batch.rs |