0%

451846939-rcore-2025-reports

一阶段

用rust其实也有快5年了所以一阶段不是什么特别难的事,因为23年已经做过一次,这次增加了一些数据结构的实现,其实不是特别难,整体数据结构实现对于之前刷过leetcode的人都会比较熟,所以轻松就过了。

二阶段

其实23年最早刷过一遍所以这个比较简单,沿用23年的一些总结,这次lab1和23年的lab1有一些不同,不过核心是不变的

  1. lab1

    其实是一个很简单的lab,系统调用的次数统计
    问答题是很好的问题,也帮助我回忆和加深了risc-v的寄存器的作用,包括trap的流程,这个很重要,直接以代码展现出来,没学rcore的时候平时听到系统调用,其实是很抽象的,并不知道系统调用是怎么从用户态切换到内核态的,而rcore非常精彩的给我解答了这个问题,并且以代码展现,不再抽象。只能说感谢开源!

  2. lab2

    mmap 和 munmap 匿名映射,对我来说其实也不难,不过反而是问答题让我再次加深了SV39的结构,页表,页表项等等这些其实理解很抽象,包括用户态是怎么用到MMU的,MMU和操作系统存储的页表这些是怎么结合的,在这一张再结合linux的一些代码就理解了。其实是riscv 使用 SATP 寄存器来保存 MMU 映射表的根地址

  3. lab3

    spawn和stride 调度算法,这个其实也不算特别复杂,在给予fork和exec代码中只需要理解 spawn和他们的区别,就很容易写出来,而stride调度算法用一个小顶堆实现即可。因为之前看了linux的task_struct的实现,所以比较轻松就能理解。

  4. lab4

    这个要求实现linkatunlinkat 这个加深了我对硬连接的理解,并且文件系统的这章让我对linux的vfs也更加理解。整体来说明显会感受到磁盘读取和内存有异曲同工之妙。

  5. lab5

    死锁检测,这个就非常考察细心了,主要就是资源的分配、分出、释放,需要格外注意,否则都无法通过,顺带这里也有一个坑,就是检测用了sleep,sleep用的是get_time,所以要实现这个api不然程序就会卡在那

三阶段

这次是新增的一个阶段,主要是为了让大家先熟悉arceos,在熟悉rcore以后其实再看arceos是比较轻松的,组件化操作系统的思想是一个很好的思想,同时也比较考验抽象能力如果做到高性能高抽象的同时又可以随意的扩展操作系统的个个组件,使用一个create引入开箱即用,个人认为是一个未来需要的方向。随着整体社会需求的发展大家对底层性能的要求越来越苛刻定制化需求也越来越多,对于操作系统也是百花齐放,同时在写一个新的操作系统的时候也总是需要重复造轮子,这个工作量其实也不算小,所以个人认为组件化操作系统在当前是一个很好的想法,高质量的组件化操作系统可以帮助个人和初创企业降低开发操作系统的难度还可以获得定制操作系统的优势来满足一些特殊的场景需要。

  1. print_color
    这个其实很简单,因为不想修改println的宏所以这里新增了一个print_color的宏来实现对颜色的打印,并再用log来包装print_color宏实现不同等级打印不同颜色的日志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #[macro_export]
    macro_rules! print_color {
    ($color:expr, $($arg:tt)*) => {{
    use axstd::io::Write;

    let mut out = $crate::io::stdout().lock();
    let _ = write!(out, "\x1B[{}m", $color);
    let _ = write!(out, $($arg)*);
    let _ = write!(out, "\x1B[0m");
    }};
    }
    pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    }

    #[macro_export]
    macro_rules! log {
    (error, $($arg:tt)*) => {
    $crate::print_color!("31", concat!("[error] ", $($arg)*, "\n"));
    };
    (warn, $($arg:tt)*) => {
    $crate::print_color!("33", concat!("[warn] ", $($arg)*, "\n"));
    };
    (info, $($arg:tt)*) => {
    $crate::print_color!("32", concat!("[info] ", $($arg)*, "\n"));
    };
    (debug, $($arg:tt)*) => {
    $crate::print_color!("34", concat!("[debug] ", $($arg)*, "\n"));
    };
    }
  1. hashmap
    这个其实可以参考rust的std的rust实现,然后改一下就可以了,当然也可以从0自己实现一个最后别忘了这样才能使用std::map

    1
    2
    3
    4
    5
    6
    7
    mod map;

    #[cfg(feature = "alloc")]
    pub mod collections {
    pub use crate::map::HashMap;
    pub use alloc::collections::*;
    }
  1. bump_alloc
    这个主要需要了解什么是bump算法,在实现ByteAllocator和PageAllocator的时候需要注意b_pos和p_pos的验证,还有要注意对齐,还要理解一下align_pow2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
    let align = layout.align();
    let size = layout.size();
    let aligned = (self.b_pos + align - 1) & !(align - 1);
    let new_b_pos = aligned + size;

    if new_b_pos > self.p_pos {
    return Err(AllocError::NoMemory);
    }

    self.b_pos = new_b_pos;
    self.byte_alloc_count += 1;
    self.byte_alloc_total += size;
    Ok(NonNull::new(aligned as *mut u8).unwrap())
    }

    fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
    let align = layout.align();
    let size = layout.size();
    let aligned = (self.b_pos + align - 1) & !(align - 1);
    let new_b_pos = aligned + size;

    if new_b_pos > self.p_pos {
    return Err(AllocError::NoMemory);
    }

    self.b_pos = new_b_pos;
    self.byte_alloc_count += 1;
    self.byte_alloc_total += size;
    Ok(NonNull::new(aligned as *mut u8).unwrap())
    }
  1. rename
    这个需要修改一下axfs_ramfs组件大致思路是 获取当前节点(即当前目录)-> 查找要重命名的原始节点 old_node->拆解 new 路径,获得新文件名新父目录路径->获取根节点,并从中查找新父目录->从原目录中移除旧路径->将 old_node 插入新父目录,使用新文件名

  2. mmap file
    这个修改较多,在Backend新增了一个FileBacked

    1
    2
    3
    4
    5
    6
    /// File-backed mapping backend (lazy load).
    FileBacked {
    reader: ::alloc::sync::Arc<dyn crate::MmapReadFn>,
    file_offset: usize,
    area_start: VirtAddr,
    },

    在page_fault的时候进行实际内存申请

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    Self::FileBacked {
    reader,
    file_offset,
    area_start,
    } => {
    let va = vaddr.align_down(PAGE_SIZE_4K);
    let offset = file_offset + (va.as_usize() - area_start.as_usize());

    let vaddr = match global_allocator().alloc_pages(1, PAGE_SIZE_4K) {
    Ok(vaddr) => vaddr,
    Err(_) => return false,
    };

    let paddr = virt_to_phys(VirtAddr::from(vaddr));
    let buf = unsafe {
    core::slice::from_raw_parts_mut(axhal::mem::phys_to_virt(paddr).as_mut_ptr(), PAGE_SIZE_4K)
    };
    if !(reader)(offset, buf) {
    return false;
    }

    page_table
    .map_region(
    va,
    |_| paddr,
    PAGE_SIZE_4K,
    orig_flags,
    false,
    false,
    )
    .map(|tlb| tlb.ignore())
    .is_ok()
    }

    mmap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    fn sys_mmap(
    addr: *mut usize,
    length: usize,
    prot: i32,
    flags: i32,
    fd: i32,
    offset: isize,
    ) -> isize {
    let binding = current();

    let mut aspace = binding.task_ext().aspace.lock();

    let vaddr = match aspace.find_free_area(
    VirtAddr::from(addr as usize),
    length,
    VirtAddrRange::from_start_size(aspace.base(), aspace.size()),
    ) {
    Some(base) => base,
    None => return -1,
    };

    let prot_flags = MmapProt::from_bits_truncate(prot);
    let mut map_flags = MappingFlags::USER;

    if prot_flags.contains(MmapProt::PROT_READ) {
    map_flags |= MappingFlags::READ;
    }
    if prot_flags.contains(MmapProt::PROT_WRITE) {
    map_flags |= MappingFlags::WRITE;
    }
    if prot_flags.contains(MmapProt::PROT_EXEC) {
    map_flags |= MappingFlags::EXECUTE;
    }

    let aligned_len = (length + PAGE_SIZE_4K - 1) & !(PAGE_SIZE_4K - 1);
    let hint = if addr.is_null() {
    aspace.base()
    } else {
    VirtAddr::from(addr as usize)
    };

    let file_obj = match get_file_like(fd) {
    Ok(f) => f,
    Err(_) => return -1,
    };

    let reader = alloc::sync::Arc::new(move |_offset: usize, buf: &mut [u8]| {
    file_obj.read(buf).is_ok()
    });

    if let Err(e) = aspace.mmap_file(vaddr, aligned_len, map_flags, offset as usize, reader) {
    return -1;
    }

    vaddr.as_usize() as isize
    }
  3. simple_hv
    这个其实蛮有意思的可以体验到guest操作自己没有权限的指令时候的一个流程以及体验的到page_fault的流程,第一个需要自己在VM中模拟 CSR 访问

    1
    2
    3
    ctx.guest_regs.gprs.set_reg(A1, 0x1234);
    ctx.guest_regs.sepc += 4;
    return false;

    第二个可以直接写成成对应的值

    1
    2
    3
    ctx.guest_regs.gprs.set_reg(A0, 0x6688);
    ctx.guest_regs.sepc += 4;
    return false;

这个是对Hypervisor很好的一个体验也有了一个初步的认识,包括整个项目中的实验设计是非常好的,每一个新的功能都有一个简单的实验来上手体验,而且正是因为有了arceos也免去了很多最开始操作系统要处理的事情,直接进入体验Hypervisor代码量非常少,实验在risc-v指令集下,整个Hypervisor的体验很丝滑,代码结构很好,可以立马就对VM_ENTRY和VM_EXIT这个有点抽象的概念进行了具象

总结

新增的三阶段arceos是非常棒的,整体实验设计也很不错,在有了rcore的基础以后再看arceos是不困难的,一步一步的迈向抽象度更高的操作系统,大家在arceos里已经做了非常多的事情了,使得我们可以如此简单的启动一个os,并且体验到最小化的一个Hypervisor以及宏内核,也再次加深了对操作系统的理解,以及发现软硬协同的重要性。