2025 春夏季开源操作系统训练营 第三阶段题解
这是 2025 春夏季开源操作系统训练营(第三阶段)的题解。
print_with_color
在 ulib/axstd/src/macros.rs 中修改 print 和 println 两个宏的展开即可,代码如下:
1#[macro_export]
2macro_rules! print {
3 ($($arg:tt)*) => {
4 $crate::io::__print_impl(format_args!("\x1b[31m{}\x1b[0m", format_args!($($arg)*)));
5 }
6}
7
8/// Prints to the standard output, with a newline.
9#[macro_export]
10macro_rules! println {
11 () => { $crate::print!("\n") };
12 ($($arg:tt)*) => {
13 $crate::io::__print_impl(format_args!("\x1b[31m{}", format_args!("{}\x1b[0m\n", format_args!($($arg)*))));
14 }
15}
我改成了红色的字体。需要注意的是,println 中,\x1b[0m\ 不能放在 \n 之后(
support_hashmap
我在 ulib/axstd/src 下新建了 hashmap 这个 module:
1#[cfg(feature = "alloc")]
2pub type HashMap<K, V> = alloc::collections::BTreeMap<K, V>;
这里用了一个比较投机取巧的方式实现 Hashmap<K, V>。
在 ulib/axstd/src/lib.rs 中加上
1pub mod hashmap;
同时,由于我们的 Hashmap 不属于 collections 这个 crate,而是 axstd::hashmap,所以需要修改 exercises/support_hashmap/src/main.rs 中的导入语句:
1use std::hashmap::HashMap;
alt_alloc
按照要求实现 bump_allocator 即可,三个 trait 中有很多用不上的函数,直接 unimplemented!() 就好。
1#![no_std]
2
3use allocator::{AllocError, AllocResult, BaseAllocator, ByteAllocator, PageAllocator};
4use core::alloc::Layout;
5use core::ptr::NonNull;
6
7/// Early memory allocator
8/// Use it before formal bytes-allocator and pages-allocator can work!
9/// This is a double-end memory range:
10/// - Alloc bytes forward
11/// - Alloc pages backward
12///
13/// [ bytes-used | avail-area | pages-used ]
14/// | | --> <-- | |
15/// start b_pos p_pos end
16///
17/// For bytes area, 'count' records number of allocations.
18/// When it goes down to ZERO, free bytes-used area.
19/// For pages area, it will never be freed!
20///
21pub struct EarlyAllocator<const PAGE_SIZE: usize> {
22 /// Start address of the memory range
23 start: usize,
24 /// End address of the memory range
25 end: usize,
26 /// Current position of the bytes area
27 b_pos: usize,
28 /// Current position of the pages area
29 p_pos: usize,
30 /// Number of bytes used
31 count: usize,
32}
33
34impl<const PAGE_SIZE: usize> EarlyAllocator<PAGE_SIZE> {
35 pub const fn new() -> Self {
36 Self {
37 start: 0,
38 end: 0,
39 b_pos: 0,
40 p_pos: 0,
41 count: 0,
42 }
43 }
44}
45
46impl<const PAGE_SIZE: usize> BaseAllocator for EarlyAllocator<PAGE_SIZE> {
47 fn init(&mut self, start: usize, size: usize) {
48 // Initialize the allocator with the given start address and size
49 self.start = start;
50 self.end = start + size;
51 self.b_pos = start;
52 self.p_pos = start + size;
53 self.count = 0;
54 }
55
56 fn add_memory(&mut self, _start: usize, _size: usize) -> AllocResult {
57 unimplemented!()
58 }
59}
60
61impl<const PAGE_SIZE: usize> ByteAllocator for EarlyAllocator<PAGE_SIZE> {
62 fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
63 let start = (self.b_pos + layout.align() - 1) & !(layout.align() - 1);
64 let next = start + layout.size();
65 if next > self.p_pos {
66 return Err(AllocError::NoMemory);
67 } else {
68 self.b_pos = next;
69 self.count += 1;
70 NonNull::new(start as *mut u8).ok_or(AllocError::NoMemory)
71 }
72 }
73
74 fn dealloc(&mut self, _ptr: NonNull<u8>, _layout: Layout) {
75 self.count -= 1;
76 if self.count == 0 {
77 self.b_pos = self.start;
78 }
79 }
80
81 fn total_bytes(&self) -> usize {
82 self.end - self.start
83 }
84
85 fn used_bytes(&self) -> usize {
86 self.b_pos - self.start
87 }
88
89 fn available_bytes(&self) -> usize {
90 self.p_pos - self.b_pos
91 }
92}
93
94impl<const PAGE_SIZE: usize> PageAllocator for EarlyAllocator<PAGE_SIZE> {
95 const PAGE_SIZE: usize = PAGE_SIZE;
96
97 fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult<usize> {
98 let next = (self.p_pos - PAGE_SIZE * num_pages) & !(PAGE_SIZE * align_pow2 - 1);
99 if next <= self.b_pos {
100 return Err(AllocError::NoMemory);
101 } else {
102 self.p_pos = next;
103 Ok(next)
104 }
105 }
106
107 fn dealloc_pages(&mut self, _pos: usize, _num_pages: usize) {
108 unimplemented!()
109 }
110
111 fn total_pages(&self) -> usize {
112 (self.end - self.start) / PAGE_SIZE
113 }
114
115 fn used_pages(&self) -> usize {
116 (self.end - self.p_pos) / PAGE_SIZE
117 }
118
119 fn available_pages(&self) -> usize {
120 (self.p_pos - self.b_pos) / PAGE_SIZE
121 }
122}
ramfs_rename
首先采用 patch 的方式,让项目临时使用本地组件仓库,修改 Cargo.toml:
1axfs_ramfs = { path = './axfs_ramfs' }
然后实现 rename 函数,我采用的是递归策略,对于 src_path 和 dst_path 而言,分别 split 并拆分路径上的目录,目录树中的叶节点,然后将 dst_path 最后的节点插入进 src_path 的父亲的孩子中。
1fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult {
2 let dst_path = dst_path.trim_start_matches("/tmp");
3 let (src_name, src_rest) = split_path(src_path);
4 if let Some(src_rest) = src_rest {
5 match src_name {
6 "" | "." => self.rename(src_rest, dst_path),
7 ".." => self.parent().ok_or(VfsError::NotFound)?.rename(src_rest, dst_path),
8 _ => {
9 let subdir = self
10 .children
11 .read()
12 .get(src_name)
13 .ok_or(VfsError::NotFound)?
14 .clone();
15 subdir.rename(src_rest, dst_path)
16 }
17 }
18 } else if src_name.is_empty() || src_name == "." || src_name == ".." {
19 return Err(VfsError::InvalidInput);
20 } else {
21 let (dst_name, dst_rest) = split_path(dst_path);
22 if let Some(dst_rest) = dst_rest {
23 match dst_name {
24 "" | "." => self.rename(src_name, dst_rest),
25 ".." => self.parent().ok_or(VfsError::NotFound)?.rename(src_name, dst_rest),
26 _ => {
27 let subdir = self
28 .children
29 .read()
30 .get(dst_name)
31 .ok_or(VfsError::NotFound)?
32 .clone();
33 subdir.rename(src_name, dst_rest)
34 }
35 }
36 } else if dst_name.is_empty() || dst_name == "." || dst_name == ".." {
37 return Err(VfsError::InvalidInput);
38 } else {
39 let mut children = self.children.write();
40 let node = children
41 .remove(src_name)
42 .ok_or(VfsError::NotFound)?
43 .clone();
44 children.insert(dst_name.into(), node);
45 Ok(())
46 }
47 }
48}
sys_map
像 m_1_0 到 m_2_0 的例子中的那样,首先在 exercises/sys_map/src/main.rs 中注册 Page Fault 的 trap handler:
1use axtask::TaskExtRef;
2use axhal::trap::{register_trap_handler, PAGE_FAULT};
3
4#[register_trap_handler(PAGE_FAULT)]
5fn handle_page_fault(vaddr: VirtAddr, access_flags: MappingFlags, is_user: bool) -> bool {
6 if is_user {
7 if !axtask::current()
8 .task_ext()
9 .aspace
10 .lock()
11 .handle_page_fault(vaddr, access_flags)
12 {
13 ax_println!("{}: segmentation fault, exit!", axtask::current().id_name());
14 axtask::exit(-1);
15 } else {
16 ax_println!("{}: handle page fault OK!", axtask::current().id_name());
17 }
18 true
19 } else {
20 false
21 }
22}
之后,在 exercises/sys_map/src/syscall.rs 中实现 System Call:
- 拿到 User Space
uspace - 用
find_free_area找到空余的存储空间 - 用
map_alloc分配空间 - 读取
fd对应的文件内容,放进buf - 将
buf写入刚刚已分配的空间
1use arceos_posix_api::{self as api, get_file_like};
2use memory_addr::{MemoryAddr, VirtAddr, VirtAddrRange};
3use alloc::vec;
4
5fn sys_mmap(
6 addr: *mut usize,
7 length: usize,
8 prot: i32,
9 flags: i32,
10 fd: i32,
11 _offset: isize,
12) -> isize {
13 debug!("sys_mmap: addr: {:#x}, length: {}, prot: {}, flags: {}, fd: {}, _offset: {}", addr as usize, length, prot, flags, fd, _offset);
14 let binding = current();
15 let mut uspace = binding.task_ext().aspace.lock();
16 let length = length.align_up_4k();
17 if let Some(vaddr) = uspace.find_free_area(
18 VirtAddr::from(addr as usize),
19 length,
20 VirtAddrRange::from_start_size(uspace.base(), uspace.size())) {
21 if let Ok(_) = uspace.map_alloc(vaddr, length, MappingFlags::from(MmapProt::from_bits(prot).unwrap()), true) {
22 if let Ok(file) = get_file_like(fd) {
23 let mut buf = vec![0u8; length];
24 if let Ok(_) = file.read(&mut buf) {
25 if let Ok(_) = uspace.write(vaddr, &buf) {
26 return vaddr.as_usize() as isize;
27 }
28 }
29 }
30 }
31 }
32 -1
33}
simple_hv
在 exercises/simple_hv/src/main.rs 中添加对 Illegal Instruction 和 Load Guest Page Fault 的处理,代码如下:
1Trap::Exception(Exception::IllegalInstruction) => {
2 // panic!("Bad instruction: {:#x} sepc: {:#x}",
3 // stval::read(),
4 // ctx.guest_regs.sepc
5 // );
6 ctx.guest_regs.gprs.set_reg(A1, 0x1234);
7 ctx.guest_regs.sepc += 4;
8},
9Trap::Exception(Exception::LoadGuestPageFault) => {
10 // panic!("LoadGuestPageFault: stval{:#x} sepc: {:#x}",
11 // stval::read(),
12 // ctx.guest_regs.sepc
13 // );
14 ctx.guest_regs.gprs.set_reg(A0, 0x6688);
15 ctx.guest_regs.sepc += 4;
16},
在从 Guest OS 退出到 Host OS 的时候,需要修改 sepc 寄存器的偏移量,这是因为 sepc 指向的是导致异常的指令地址。如果不调整偏移量,Guest OS 会重复执行导致异常的指令,陷入死循环。通过增加偏移量,可以跳过异常指令,确保程序继续正常运行。
执行 riscv64-linux-gnu-objdump -D ./target/riscv64gc-unknown-none-elf/release/skernel2 的结果如下:
1./target/riscv64gc-unknown-none-elf/release/skernel2: 文件格式 elf64-littleriscv
2
3
4Disassembly of section .text:
5
6ffffffc080200000 <_start>:
7ffffffc080200000: f14025f3 csrr a1,mhartid
8ffffffc080200004: 04003503 ld a0,64(zero) # 40 <_percpu_load_end+0x40>
9ffffffc080200008: 48a1 li a7,8
10ffffffc08020000a: 00000073 ecall
这说明指令是 字节对齐的,故需要 ctx.guest_regs.sepc += 4。