2025 春夏季开源操作系统训练营 第三阶段题解

这是 2025 春夏季开源操作系统训练营(第三阶段)的题解。

ulib/axstd/src/macros.rs 中修改 printprintln 两个宏的展开即可,代码如下:

rust
 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:

rust
1#[cfg(feature = "alloc")]
2pub type HashMap<K, V> = alloc::collections::BTreeMap<K, V>;

这里用了一个比较投机取巧的方式实现 Hashmap<K, V>

ulib/axstd/src/lib.rs 中加上

rust
1pub mod hashmap;

同时,由于我们的 Hashmap 不属于 collections 这个 crate,而是 axstd::hashmap,所以需要修改 exercises/support_hashmap/src/main.rs 中的导入语句:

rust
1use std::hashmap::HashMap;

alt_alloc

按照要求实现 bump_allocator 即可,三个 trait 中有很多用不上的函数,直接 unimplemented!() 就好。

rust
  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

toml
1axfs_ramfs = { path = './axfs_ramfs' }

然后实现 rename 函数,我采用的是递归策略,对于 src_pathdst_path 而言,分别 split 并拆分路径上的目录,目录树中的叶节点,然后将 dst_path 最后的节点插入进 src_path 的父亲的孩子中。

rust
 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:

rust
 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:

  1. 拿到 User Space uspace
  2. find_free_area 找到空余的存储空间
  3. map_alloc 分配空间
  4. 读取 fd 对应的文件内容,放进 buf
  5. buf 写入刚刚已分配的空间
rust
 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 的处理,代码如下:

rust
 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 的结果如下:

bash
 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

这说明指令是 44 字节对齐的,故需要 ctx.guest_regs.sepc += 4