rust基础的总结
一.基本数据类型与所有权
所有权系统核心规则
- 移动语义(Move):
1 2 3
| let s1 = String::from("hello"); let s2 = s1;
|
- 借用规则:
- 任意时刻:一个可变引用 或 多个不可变引用
- 引用必须始终有效(悬垂指针禁止)
- 生命周期标注:
1 2 3
| fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
|
Slice 类型
- 无所有权引用:
1 2 3 4
| let s = String::from("hello world"); let hello: &str = &s[0..5]; let a = [1, 2, 3, 4]; let slice: &[i32] = &a[1..3];
|
二.Crate 与模块系统
Crate 类型
类型 |
文件扩展名 |
特点 |
二进制 Crate |
main.rs |
可执行程序 |
库 Crate |
lib.rs |
可复用代码库 |
模块可见性规则
1 2 3 4 5 6 7 8
| mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } }
crate::front_of_house::hosting::add_to_waitlist();
|
使用外部 Crate
1 2 3
| [dependencies] rand = "0.8.5"
|
1 2 3 4 5 6
| use rand::Rng;
fn main() { let num = rand::thread_rng().gen_range(1..101); }
|
三. Option 与错误处理
Option 枚举
1 2 3 4 5 6 7 8 9 10 11
| enum Option<T> { Some(T), None, }
let x: Option<i32> = Some(5); match x { Some(i) => println!("Value: {}", i), None => println!("Missing value"), }
|
Result<T, E> 错误处理
1 2 3 4 5 6 7 8 9 10 11
| fn read_file(path: &str) -> Result<String, io::Error> { fs::read_to_string(path) }
fn read_config() -> Result<String, io::Error> { let mut file = File::open("config.toml")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }
|
错误处理最佳实践
- 优先使用
Result
而非 panic
- 使用
?
操作符传播错误
- 自定义错误类型实现
std::error::Error
四. Trait 与泛型
Trait 定义与实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| trait Summary { fn summarize(&self) -> String; }
struct NewsArticle { headline: String, location: String, }
impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{} ({})", self.headline, self.location) } }
|
泛型函数
1 2 3 4 5 6 7 8 9
| fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest }
|
Trait Bound 语法糖
1 2 3
| fn notify<T: Display + Clone>(item: &T) {...} fn notify(item: &(impl Display + Clone)) {...}
|
生命周期进阶
生命周期标注必要性:
- 结构体持有引用时必须显式标注生命周期,确保引用的有效性
- 方法实现中:
&self
参数隐含 &'a self
生命周期
- 返回值关联结构体生命周期(通过生命周期消除规则第三项)
- 遵循Rust生命周期消除三规则:
- 每个输入引用自动获得独立生命周期
- 单个输入引用时所有输出引用与其生命周期对齐
- 方法签名中
&self
使输出引用与结构体生命周期对齐
错误:
1 2 3 4
| fn dangling_reference() -> &str { let s = String::from("temporary"); &s[..] }
|
1 2 3 4 5 6 7 8 9 10
| struct ImportantExcerpt<'a> { part: &'a str, }
impl<'a> ImportantExcerpt<'a> { fn announce_and_return(&self, announcement: &str) -> &str { println!("Attention: {}", announcement); self.part } }
|
五. 智能指针
常用智能指针对比
类型 |
所有权 |
线程安全 |
使用场景 |
Box<T> |
单一 |
是 |
堆分配、递归类型 |
Rc<T> |
共享 |
否 |
单线程引用计数 |
Arc<T> |
共享 |
是 |
多线程引用计数 |
RefCell<T> |
可变 |
否 |
运行时借用检查 |
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| enum List { Cons(i32, Box<List>), Nil, }
use std::rc::Rc; let a = Rc::new(5); let b = Rc::clone(&a);
use std::cell::RefCell; let c = RefCell::new(42); *c.borrow_mut() += 10;
|
六.迭代器与闭包
闭包类型推断
1 2
| let add_one = |x| x + 1; let print = || println!("hello");
|
闭包捕获模式
捕获方式 |
关键字 |
所有权 |
不可变借用 |
` |
|
可变借用 |
` |
mut |
值捕获 |
move |
转移 |
迭代器适配器
1 2 3 4 5 6 7
| let v = vec![1, 2, 3, 4];
let sum: i32 = v.iter() .map(|x| x * 2) .filter(|x| x % 4 == 0) .sum();
|
自定义迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct Counter { count: u32, }
impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }
|
七.并发与异步编程
线程创建
1 2 3 4 5 6 7
| use std::thread;
let handle = thread::spawn(|| { println!("From spawned thread"); });
handle.join().unwrap();
|
通道通信 (mpsc)
1 2 3 4 5 6 7 8 9
| use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || { tx.send("Message").unwrap(); });
println!("Received: {}", rx.recv().unwrap());
|
共享状态 (Mutex)
1 2 3 4 5 6 7 8 9 10 11
| use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10).map(|_| { let c = Arc::clone(&counter); thread::spawn(move || { let mut num = c.lock().unwrap(); *num += 1; }) }).collect();
|
异步编程 (async/await)
1 2 3 4 5 6 7 8 9 10 11 12
| async fn fetch_data() -> Result<String, reqwest::Error> { reqwest::get("https://api.example.com/data") .await? .text() .await }
#[tokio::main] async fn main() { let data = fetch_data().await.unwrap(); println!("Data: {}", data); }
|
八.常用集合类型
Vec 动态数组
1 2 3 4 5 6 7 8 9 10 11
| let mut v = Vec::with_capacity(10); v.extend([1, 2, 3]);
if let Some(val) = v.get(1) { println!("Second element: {}", val); }
let first = &v[0];
|
HashMap<K, V> 哈希表
1 2 3 4 5 6 7 8
| use std::collections::HashMap;
let mut scores = HashMap::new(); scores.insert("Blue", 10);
scores.entry("Yellow").or_insert(50); scores.entry("Blue").and_modify(|e| *e += 1);
|
阶段二,os基础
lab1
与上下文, 特权级有关的寄存器
- sstatus:包含了处理器的状态信息,包括特权级别和中断使能状态。恢复 sstatus 的值确保在返回用户态时,处理器的特权级别和中断状态与陷阱发生前一致。
- sepc:保存了中断或异常发生时的程序计数器值。恢复 sepc 的值确保在返回用户态时,处理器能够从中断或异常发生的地方继续执行。
- sscratch:保存了用户栈指针。在切换到用户态之前,将用户栈指针保存到 sscratch 寄存器中,以便在用户态下使用。
- sret根据sstatus中的SPP位指示切换为用户态。(寄存器中的一个位,0,u_mode;1_,s_mode,s_mode)
- scause: Trap原因/种类
- stvec: trap_handle地址
lab2
SV39
- virtual page 39位, 38-12为虚拟页号
- 页表项PTE: Reserver: 10, PPN2: 26, PPN1: 9, PPN0: 9, RSW: 2, DAGUXWRV
分页
- MMU地址转换
- kernel address space最高位为 “跳板”, app ks, guard page
- app address space, 最高位为跳板, TrapContext, UserStack, GP, Framed
跳板意义:
satp, 切换后,地址映射不同, 例如:上下文切换的restore, 在更改satp指令后, 保证下一条指令在不同的地址映射下能被正确寻址,保证指令的连续执行
TrapContext新增字段
在进行特权级转换时, 需要相应的sp以及satp的token
- pub kernel_satp: usize, 内核地址空间的 token
- pub kernel_sp: usize, 当前应用在内核地址空间中的内核栈栈顶的虚拟地址
- pub trap_handler: usize, 内核中 trap handler 入口点的虚拟地址
lab3
fork
- 获得父进程的地址空间
- sepc + 4
- a0返回参数更改,父子进程不相同
- 维护父子进程关系
- fd, 死锁检测等
功能实现
stride算法:
- 为TCB加上schedule块(struct), 同时预留了pass设置的接口
- 为sys_set_priority加入了对priority的设置
- 将TaskManager块改为了用binaryheap存储, 并为TCB分配了Ord特性,每次选取都会取stride最小的调度
向前兼容
- 重写mmap和munmap(用到了remove_area_with_start_vpn)
- 重写了sys_get_time,用到了translate_va
lab4
文件系统
文件系统本质上是一堆块上的抽象, 在内存中有缓存块对其进行映射.
进程维护一个文件描述符表,可映射到对应的缓存块
1 2 3 4 5 6 7 8
| pub struct BlockCache { cache: [u8; BLOCK_SZ], block_id: usize, block_device: Arc<dyn BlockDevice>, modified: bool, }
|
easy-fs磁盘布局
超级块 (Super Block),用于定位其他连续区域的位置,检查文件系统合法性。
索引节点位图,长度为若干个块。它记录了索引节点区域中有哪些索引节点已经被分配出去使用了。
索引节点区域,长度为若干个块。其中的每个块都存储了若干个索引节点。
1 2 3 4 5 6 7 8
| #[repr(C)] pub struct DiskInode { pub size: u32, pub direct: [u32; INODE_DIRECT_COUNT], pub indirect1: u32, pub indirect2: u32, type_: DiskInodeType, }
|
数据块位图,长度为若干个块。它记录了后面的数据块区域中有哪些已经被分配出去使用了。
数据块区域,其中的每个被分配出去的块保存了文件或目录的具体内容。
lab5
在引入线程后, 调度机制本质上是在线程块上进行切换. 会区分主线程和子线程
- 创建线程不需要要建立新的地址空间
- 能够访问到进程所拥有的代码段, 堆和其他数据段
- 专有的用户态栈
实现功能
- 在ProcessControlBlockInner加入了对mutex和sem的死锁检查块(all[], ava[], need[])
- 检测前对相应资源的need[] + 1
- 实现is_safe检测函数, 对finish==false和need <= work的块, 回收allocation和finish=true,对标记flag=true, 当finish没有任何改变, 即本次循环flag==false时退出loop, 利用闭包all,检测finish所有线程是否全是true
- 若为unsafe, 则回退need, 返回-0xdead
- 若为safe, 则在down和lock之前drop(process_inner),防止线程堵塞无法释放资源, 在down和lock之后同时更新检查块中的矩阵
- 为up和unlock加上检查块的更新
Mutex实现问题
第三阶段
一, 组件化内核基础与 Unikernel 模式
组件化内核介绍
Unikernel 模式
- 特点:
- 应用与内核合一:编译为一个 Image,共享同一特权级(内核态)和地址空间。
- 无用户态 / 内核态切换:简单高效,但安全性较低(应用可直接访问内核资源)。
核心组件
组件名称 |
功能描述 |
在实验中的作用 |
axhal |
硬件抽象层,屏蔽不同架构差异(如 Riscv64/ARM) |
初始化串口、内存等硬件,提供底层 IO 接口 |
axruntime |
内核运行时环境,负责引导流程、内存初始化、任务调度框架 |
执行内核启动流程,调用应用层代码 |
axstd |
内核态标准库,提供基础数据结构和工具函数(如 println!) |
实现字符终端输出功能 |
arceos_api |
内核公共接口,定义组件间通信协议 |
统一组件间调用规范 |
Unikernel 的启动链
- 硬件启动:通过 OpenSBI(Riscv 固件)加载内核 Image 到内存。
- 引导阶段(axhal):
- 初始化 CPU 寄存器、MMU 分页(早期恒等映射)。
- 建立内核栈,为 Rust 运行时做准备。
- 运行时阶段(axruntime):
- 初始化内存分配器、日志系统。
- 调用应用层 main 函数,执行具体功能。
实验
1. 主函数 src/main.rs
1 2 3 4 5 6 7 8
| #![cfg_attr(feature = "axstd", no_main)] #[cfg(feature = "axstd")] use axstd::println;
#[cfg_attr(feature = "axstd", no_mangle)] fn main() { println!("Hello, ArceOS!"); }
|
2. 依赖管理 Cargo.toml
1 2 3
| [dependencies] axstd = { workspace = true } // 引入 axstd 组件,支持标准库功能 arceos_api = { workspace = true } // 引入内核公共接口
|
3. features 动态配置
- 作用:通过编译参数控制组件的启用,实现 “按需构建”。
- 示例:
- axstd 组件通过 feature = “axstd” 控制是否包含。
- 实验中默认启用 axstd,因此能使用 println!。
println!
通过更改ulib下axstd,macros文件中的println!
hashmap
1 2
| #[cfg(feature = "alloc")] pub mod collections;
|
暴露自己写的collections
1 2 3
| [dependencies.hashbrown] version = "0.14" default-features = false
|
用了官方库的core版本
二, 内存管理与多任务基础
1. 分页的两个阶段
阶段 |
目标 |
实现方式 |
关键组件 |
早期启用(必须) |
快速建立基本映射,保证内核启动 |
1GB 恒等映射(虚拟地址 = 物理地址) |
axhal 中的 BOOT_PT_SV39 页表 |
后期重映射(可选,需 paging feature) |
扩展地址空间,支持设备 MMIO |
细粒度权限控制(如只读、可执行) |
axmm 中的 AddrSpace、PageTable |
2. 算法
算法 |
原理 |
TLSF |
两级 Bitmap + 链表管理空闲块 |
Buddy |
基于 2 的幂次分裂 / 合并空闲块 |
Slab |
为特定大小对象创建缓存池 |
3.
全局分配器:通过 #[global_allocator]
声明,实现 GlobalAlloc
trait。
1 2
| #[cfg_attr(all(target_os = "none", not(test)), global_allocator)] static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new();
|
任务数据结构 TaskInner
1 2 3 4 5 6 7 8
| struct TaskInner { id: TaskId, name: String, state: AtomicU8, kstack: Option<TaskStack>, ctx: UnsafeCell<TaskContext>, }
|
协作式调度
FIFO 队列:任务按 “先到先服务” 原则执行,当前任务需主动让出 CPU(调用 yield_now()
)。
组件
组件 |
功能 |
axsync |
同步原语(自旋锁、互斥锁) |
axtask |
调度接口(spawn/yield_now 等) |
实现
EarlyAllocator实现要求比较低
byte
- alloc
- 注意每次分配内存时候的对齐
- 预分配,检查是否与p_pos重叠
- 为count++
- 注意每次分配内存时候的对齐
- 预分配,检查是否与p_pos重叠
- 为count++
- dealloc
- 单纯的count–
- count==0时,就可以重置b_pos了
page
- alloc
- 检查alignment是否有效
- 获取分配的size进行对齐,同时检查是否越界
- 更新数据
- 检查alignment是否有效
- 获取分配的size进行对齐,同时检查是否越界
- 更新数据
- dealloc
三、调度,块设备,文件系统
时钟中断:
代码(Riscv64 中断初始化)
1 2 3 4 5 6
| axhal::irq::register_handler(TIMER_IRQ_NUM, || { update_timer(); axtask::on_timer_tick(); }); axhal::arch::enable_irqs();
|
块设备驱动:
Trait:BlockDriverOps
1 2 3 4 5
| trait BlockDriverOps { fn num_blocks(&self) -> u64; fn block_size(&self) -> usize; fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult; }
|
文件系统:
抽象
文件系统(FileSystem):如 FAT32、EXT4。
目录(Dir):存储文件 / 子目录元数据。
文件(File):存储具体数据,支持读写操作。
接口
1 2 3 4
| trait VfsOps { fn root_dir(&self) -> &DirNode; fn lookup(&self, path: &str) -> Option<FileNode>; }
|
加载流程
块设备读取:通过 VirtIO Blk 驱动读取磁盘前 512 字节(引导扇区)。
解析 BPB:获取 FAT 表起始地址、簇大小等参数。
挂载文件系统:将 FAT32 的根目录挂载到 VFS 的 / 节点。
应用加载示例(U.8 实验)
1 2 3 4 5 6
| fn load_app(path: &str) -> Result<Vec<u8>> { let root = vfs.root_dir(); let file = root.lookup(path).ok_or("文件不存在")?; file.read_to_end() }
|
实验实现
寻找ing
在axfs_ramf中实现
1
| impl VfsNodeOps for DirNode{...}
|
文件时通过封装的BTreeMap管理的, 替换相应键值对即可
注意
1
| fn rename(&self, src_path: &str, dst_path: &str)
|
src和dst_path路径层级不一样
我使用了split_path_to_end来获取最终的文件名
四, 地址空间管理
缺页异常处理
1 2
| let ustack_top = init_user_stack(&mut uspace, false).unwrap();
|
缺页异常处理流程
- 异常触发:用户态访问未映射地址(如栈写入),CPU 陷入内核。
- 处理逻辑:
- 通过handle_page_fault函数申请物理页帧(alloc_frame)
- 在页表中建立虚拟地址与物理页帧的映射(pt.remap)
1 2 3 4 5
| fn handle_page_fault(...) -> bool { let frame = alloc_frame(true); pt.remap(vaddr, frame, orig_flags); tlb.flush(); }
|
ELF 格式解析
关键段:
LOAD 段:包含代码段(R E标志)和数据段(RW标志)。
BSS 段:未初始化数据,ELF 文件不存储,内核需预留空间并清零。
加载逻辑:
1 2 3 4 5 6 7 8 9 10 11 12
| for segment in elf.segments { if segment.type == LOAD { let vaddr = segment.virt_addr; let phys_frame = alloc_frame(segment.mem_siz); map_virtual_to_physical(vaddr, phys_frame, segment.flags); if segment.has_data { copy_file_data(vaddr, segment.file_offset, segment.file_siz); } else { zero_memory(vaddr, segment.mem_siz); } } }
|
实验实现
得到aspace->分配内存->将文件信息写入
1 2 3 4 5 6
| const MAP_SHARED = 1 << 0; const MAP_PRIVATE = 1 << 1; const MAP_FIXED = 1 << 4; const MAP_ANONYMOUS = 1 << 5; const MAP_NORESERVE = 1 << 14; const MAP_STACK = 0x20000;
|
这里只处理MAP_PRIVATE,
同时addr.is_null(),可通过aspace.find_free_area寻找内存
五, Hypervisor
Hypervisor
1.1 定义
Hypervisor(虚拟机监控器)是运行在物理硬件与虚拟机之间的虚拟化层软件,允许多个虚拟机共享物理资源,每个虚拟机拥有独立的虚拟硬件环境(如vCPU、vMem、vDevice)。
1.2 核心功能
- 资源虚拟化:模拟CPU、内存、设备等硬件资源
- 隔离与调度:确保虚拟机之间资源隔离,并高效调度物理资源
- 模式切换:在Host(Hypervisor)与Guest(虚拟机)之间双向切换
1.3 与模拟器的区别
维度 |
Hypervisor |
模拟器(Emulator) |
ISA一致性 |
虚拟环境与物理环境ISA一致 |
可模拟不同ISA(如x86模拟ARM) |
指令执行 |
大部分指令直接在物理CPU执行 |
全部指令需翻译/解释执行 |
性能目标 |
高效(虚拟化开销低) |
侧重仿真效果,性能要求低 |
1.4 虚拟化类型
- I型Hypervisor:直接运行在硬件上(如Xen、KVM),性能高
- II型Hypervisor:运行在宿主OS上(如VirtualBox),依赖宿主资源管理
二. Riscv64虚拟化扩展(H扩展)
2.1 特权级扩展
新增特权级:
- HS(Hypervisor Supervisor):Host域的管理级,负责虚拟化控制
- VS(Virtual Supervisor):Guest域的内核级,运行Guest OS内核
- VU(Virtual User):Guest域的用户级,运行Guest应用
特权级关系:
1 2
| 物理机:M(最高) > HS > U 虚拟机:VS(Guest内核) > VU(Guest用户)
|
2.2 关键寄存器
- hstatus:控制Host与Guest的模式切换
- SPV位:指示进入HS前的模式(0:非虚拟化模式;1:来自Guest的VS模式)
- SPVP位:控制HS是否有权限操作Guest的地址空间
- vs[xxx]/hs[xxx]:分别用于Guest和Host的上下文管理
- misa:标识是否支持H扩展(bit7=1表示支持)
3. 模式切换机制
3.1 从Host到Guest(run_guest函数)
1 2 3 4 5 6
| sd ra, (hyp_ra)(a0)
ld sstatus, guest_sstatus(a0)
sret
|
可参考guest.s
- a0指向的guest_reg区域 与 当前reg的替换
3.2 VM-Exit处理(以SBI调用为例)
1 2 3 4 5 6 7 8 9
| match scause.cause() { Trap::Exception(Exception::VirtualSupervisorEnvCall) => { let sbi_msg = SbiMessage::from_regs(ctx.guest_regs.gpr); if let Some(SbiMessage::Reset(Shutdown)) = sbi_msg { ax_println!("Shutdown vm normally!"); } } }
|
实现
根据结果硬编码,更改guest_reg的值