diff --git a/docs/labs/0x03/tasks.md b/docs/labs/0x03/tasks.md index c7d58bb..2c6a762 100644 --- a/docs/labs/0x03/tasks.md +++ b/docs/labs/0x03/tasks.md @@ -445,6 +445,12 @@ pub const STACK_INIT_TOP: u64 = STACK_MAX - 8; 在这个函数中,需要将初始化进程设置为当前唯一正在运行的进程。设置内核进程的状态为 `Running`,并将其 PID 加载至当前的 CPU 核心结构体中。 +!!! warning "使用 `print_process_list` 函数来辅助调试" + + 在 `src/proc/manager.rs` 中,参考代码为各位实现了 `print_process_list` 函数,用于打印进程列表、就绪队列等信息。 + + **在之后的实现过程中,涉及到进程的状态变化、进程的创建、进程的销毁、进程的调度等操作时,请调用这个函数来辅助调试,这会为你节省大量的时间。** + ## 进程调度的实现 修改时钟中断的内容,移除上次实验中的计数器等模块,并参考 `DOUBLE_FAULT_IST_INDEX` 的分配处理和声明,**在 TSS 中声明一块新的中断处理栈,并将它加载到时钟中断的 IDT 中**。 diff --git a/docs/labs/0x07/index.md b/docs/labs/0x07/index.md index 3d493df..63a68e9 100644 --- a/docs/labs/0x07/index.md +++ b/docs/labs/0x07/index.md @@ -1,7 +1,6 @@ # 实验七:更好的内存管理 -!!! tip "
 by" - +!!! tip "如同一位精明的财务官,我们细致地规划每一笔开销,确保内存资源的分配和回收都井井有条。
通过优化内存管理,我们让每一块内存都得到最有效的利用,系统运行得更加高效和稳定。
 by ChatGPT" ## 实验目的 @@ -9,8 +8,6 @@ 2. 实现操作系统栈的自动增长。 3. 尝试实现 brk 系统调用,实现用户态程序的动态内存分配。 -## 实验基础知识 - !!! note "善用 LLM 进行学习" 对于现代计算机专业的学生,建议并要求大家学习借助 LLM(Large Language Model)进行学习,这是一种非常有效的学习方法,可以帮助你更快的学习到知识。 @@ -21,9 +18,6 @@ 鼓励使用 Typst 来进行实验文档的编写,使用可以参考 [使用 Typst 编写报告](../../general/typst.md)。 -对于本次实验内容,你需要参考学习如下实验资料: - - ## 实验任务与要求 1. 请各位同学独立完成作业,任何抄袭行为都将使本次作业判为 0 分。 @@ -33,7 +27,7 @@ 3. 依据 [实验任务](./tasks.md) 完成实验。 - 代码编写任务:观察提供的代码,完善所有标记为 `FIXME:` 的部分,并验证结果是否符合预期。**请在报告中介绍实现思路,截图展示关键结果。** - - 思考任务:完成 “思考题” 和 “实验任务” 部分的内容,**在报告中简要进行回答**。*注:思考题可能也是理解代码、实现功能的重要提示。* + - 思考任务:完成 “思考题” 和 “实验任务” 部分的内容,**在报告中简要进行回答**。_注:思考题可能也是理解代码、实现功能的重要提示。_ - Bonus 加分项:学有余力的同学可以任选 Bonus 部分完成,尝试完成更多的功能,并在报告中进行展示。这部分内容不是必须的要求。 4. 请在实验报告中涵盖相关任务的实现截图、实验任务对应问题的解答、实验过程中遇到的问题与解决方案等内容。 diff --git a/docs/labs/0x07/tasks.md b/docs/labs/0x07/tasks.md index 921808e..36554e2 100644 --- a/docs/labs/0x07/tasks.md +++ b/docs/labs/0x07/tasks.md @@ -1,2 +1,390 @@ # 实验七:更好的内存管理 +!!! danger "在执行每一条命令前,请你对将要进行的操作进行思考" + + **为了你的数据安全和不必要的麻烦,请谨慎使用 `sudo`,并确保你了解每一条指令的含义。** + + **1. 实验文档给出的命令不需要全部执行** + + **2. 不是所有的命令都可以无条件执行** + + **3. 不要直接复制粘贴命令执行** + +## 合并实验代码 + +!!! tip "如何使用本次参考代码" + + 本次给出的参考代码为**增量补充**,即在上一次实验的基础上进行修改和补充。因此,你需要将本次参考代码与上一次实验的代码进行合并。 + + 合并后的代码并不能直接运行,你需要基于合并后的代码、按照文档进行修改补充,才能逐步实现本次实验的功能。 + +本次实验代码量较小,给出的代码集中于 `pkg/kernel/src/proc/vm` 目录下。 + +- `heap.rs`:添加了 `Heap` 结构体,用于管理堆内存。 +- `mod.rs`:除栈外,添加了堆内存、ELF 文件映射的初始化和清理函数。 + +!!! note "关于 `ProcessVm` 的角色" + + 在本实验设计中,`ProcessVm` 结构体用于记录用户程序的内存布局和页表,在调用下级函数前对页表、帧分配器进行获取,从而统一调用 `mapper` 或者 `get_frame_alloc_for_sure` 的时机。 + +## 帧分配器的内存回收 + +在 Lab 4 的加分项中,提到了尝试实现帧分配器的内存回收。在本次实验中将进一步完善这一功能。 + +在进行帧分配器初始化的过程中,内核从 bootloader 获取到了一个 `MemoryMap` 数组,其中包含了所有可用的物理内存区域,并且内核使用 `into_iter()` 将这一数据结构的所有权交给了一个迭代器,你可以在 `pkg/kernel/src/memory/frames.rs` 中了解到相关类型和实现。 + +迭代器是懒惰的,只有在需要时才会进行计算,因此在进行逐帧分配时,并没有额外的内存开销。但是,当需要进行内存回收时,就需要额外的数据结构来记录已经分配的帧,以便进行再次分配。 + +相对于真实的操作系统,本实验中的内存回收是很激进的:即能回收时就回收,不考虑回收对性能的影响。在实际的操作系统中,内存回收是一个复杂的问题,需要考虑到内存的碎片化、内存的使用情况、页面的大小等细节;进而使用标记清除、分段等策略来减缓内存回收等频率和碎片化。 + +因此对于本实验的帧分配器来说,内存回收的操作是非常简单的,只需要**将已经分配的帧重新加入到可用帧的集合中即可**。 + +为了减少内存占用,这一操作通常使用位图来实现,即使用一个位图来记录每一帧的分配情况。由于 Rust 的标准库中并没有提供位图的实现,因此你可以简单地使用一个 `Vec` 作为已经回收的帧的集合。 + +!!! note "内存占用" + + 使用 `Vec` 进行最简单的内存回收记录时,每一页需要使用 8 字节的内存来记录。相对于直接使用位图,这种方法会占用更多的内存(比位图多 64 倍)。 + +下面进行具体的实现: + +```rust +pub struct BootInfoFrameAllocator { + size: usize, + frames: BootInfoFrameIter, + used: usize, + recycled: Vec, +} +``` + +之后实现分配和回收的方法: + +```rust +unsafe impl FrameAllocator for BootInfoFrameAllocator { + fn allocate_frame(&mut self) -> Option { + // FIXME: try pop from recycled frames + + // FIXME: if recycled is empty: + // try allocate from frames like before + } +} + +impl FrameDeallocator for BootInfoFrameAllocator { + unsafe fn deallocate_frame(&mut self, _frame: PhysFrame) { + // FIXME: push frame to recycled + } +} +``` + +!!! tip "想要使用位图?" + + 如果你想要使用位图来实现页面回收,可以尝试使用 [Roaring Bitmap](https://docs.rs/roaring/),在笔者 2024 年 2 月的 PR 之后,它可以支持在 `no_std` 环境下使用。 + + 但它只支持 `u32` 类型的位图,**如何让它在合理范围内记录页面的分配?** + + > 或许你可以假定物理内存的最大大小不会超过一个合理的值…… + +## 用户程序的内存统计 + +在进行实际内存统计前,先来了解一下在 Linux 下的用户程序内存布局和管理: + +### Linux 的进程内存 + +在 Linux 中,进程的内存区域可以由下属示意图来简单展示: + +```txt + (high address) + +---------------------+ <------+ The top of Vmar, the highest address + | | Randomly padded pages + +---------------------+ <------+ The base of the initial user stack + | User stack | + | | + +---------||----------+ <------+ The user stack limit / extended lower + | \/ | + | ... | + | | + | MMAP Spaces | + | | + | ... | + | /\ | + +---------||----------+ <------+ The current program break + | User heap | + | | + +---------------------+ <------+ The original program break + | | Randomly padded pages + +---------------------+ <------+ The end of the program's last segment + | | + | Loaded segments | + | .text, .data, .bss | + | , etc. | + | | + +---------------------+ <------+ The bottom of Vmar at 0x10000 + | | 64 KiB unusable space + +---------------------+ + (low address) +``` + +你可以在 Linux 中通过 `cat /proc//maps` 查看进程的内存映射情况,笔者以 `cat /proc/self/maps` 为例: + +```txt +6342f0d69000-6342f0d6b000 r--p 00000000 08:02 792775 /usr/bin/cat +6342f0d6b000-6342f0d70000 r-xp 00002000 08:02 792775 /usr/bin/cat +6342f0d70000-6342f0d72000 r--p 00007000 08:02 792775 /usr/bin/cat +6342f0d72000-6342f0d73000 r--p 00008000 08:02 792775 /usr/bin/cat +6342f0d73000-6342f0d74000 rw-p 00009000 08:02 792775 /usr/bin/cat +6342f1507000-6342f1528000 rw-p 00000000 00:00 0 [heap] +794da0000000-794da02eb000 r--p 00000000 08:02 790013 /usr/lib/locale/locale-archive +794da0400000-794da0428000 r--p 00000000 08:02 789469 /usr/lib/x86_64-linux-gnu/libc.so.6 +794da0428000-794da05b0000 r-xp 00028000 08:02 789469 /usr/lib/x86_64-linux-gnu/libc.so.6 +794da05b0000-794da05ff000 r--p 001b0000 08:02 789469 /usr/lib/x86_64-linux-gnu/libc.so.6 +794da05ff000-794da0603000 r--p 001fe000 08:02 789469 /usr/lib/x86_64-linux-gnu/libc.so.6 +794da0603000-794da0605000 rw-p 00202000 08:02 789469 /usr/lib/x86_64-linux-gnu/libc.so.6 +794da0605000-794da0612000 rw-p 00000000 00:00 0 +794da0665000-794da068a000 rw-p 00000000 00:00 0 +794da0698000-794da069a000 rw-p 00000000 00:00 0 +794da069a000-794da069b000 r--p 00000000 08:02 789457 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 +794da069b000-794da06c6000 r-xp 00001000 08:02 789457 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 +794da06c6000-794da06d0000 r--p 0002c000 08:02 789457 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 +794da06d0000-794da06d2000 r--p 00036000 08:02 789457 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 +794da06d2000-794da06d4000 rw-p 00038000 08:02 789457 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 +7ffc45730000-7ffc45751000 rw-p 00000000 00:00 0 [stack] +7ffc457d3000-7ffc457d7000 r--p 00000000 00:00 0 [vvar] +7ffc457d7000-7ffc457d9000 r-xp 00000000 00:00 0 [vdso] +ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] +``` + +其中所示的内存区域: + +- `/usr/bin/cat`:ELF 文件映射的内存区域,这里包含了 `cat` 程序的 `.text`、`.data`、`.bss` 等段。 +- `[heap]`:堆区的内存区域,这里包含了 `cat` 程序的堆内存,使用 `brk` 系统调用进行分配。 +- `/usr/lib/x86_64-linux-gnu/*.so.*`:动态链接库的内存区域,这里包含了程序使用的动态链接库的 `.text`、`.data`、`.bss` 等段。 +- `[stack]`:栈区的内存区域,这里包含了程序的栈内存,在达到栈区的最大大小前,栈区会自动增长。 +- `[vvar]`、`[vdso]`、`[vsyscall]`:内核的内存区域,这里包含了内核相关的一些数据结构,如 `vvar` 包含了一些内核和用户空间之间共享的变量;`vdso` 是虚拟的动态共享对象 (Virtual Dynamic Shared Object) 区域,用于在用户空间和内核空间之间提供一些系统调用的快速访问。 + +你也可以查看程序的内存使用情况,使用 `cat /proc//status`,依旧以 `cat` 程序为例: + +```txt +VmPeak: 5828 kB +VmSize: 5828 kB +VmLck: 0 kB +VmPin: 0 kB +VmHWM: 1792 kB +VmRSS: 1792 kB +RssAnon: 0 kB +RssFile: 1792 kB +RssShmem: 0 kB +VmData: 360 kB +VmStk: 132 kB +VmExe: 20 kB +VmLib: 1748 kB +VmPTE: 52 kB +VmSwap: 0 kB +``` + +其中有几个需要注意的字段: + +- `VmPeak` / `VmSize`:进程的峰值虚拟内存大小和当前虚拟内存大小,指的是整个虚拟内存空间的大小。 +- `VmHWM` / `VmRSS`:进程的峰值物理内存大小和当前物理内存大小,指的是进程实际使用的物理内存大小。 +- `RssAnon` / `RssFile` / `RssShmem`:进程的匿名内存、文件映射内存和共享内存的大小。 +- `VmData` / `VmStk` / `VmExe`:进程的数据段、栈段、代码段的大小。 +- `VmLib`:进程的动态链接库的大小,对于 `cat` 程序来说,这里主要是 `libc` 的占用,但这部分内存可以被多个进程很好地共享。 +- `VmPTE`:进程的页表项的大小。 + +当使用 `ps aux` 查看进程时,你可以看到更多的信息,如进程的 CPU 占用、内存占用、进程的状态等。 + +```txt +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +root 1 0.0 0.3 167780 12876 ? Ss Apr22 1:57 /sbin/init +... +mysql 1820 0.1 10.5 1750680 365696 ? Sl Apr22 75:39 /url/bin/mysqld +``` + +其中 VSE 和 RSS 就对应了上述的 `VmSize` 和 `VmRSS`,`%MEM` 表示了 RSS (Resident Set Size) 占总物理内存的百分比。 + +总之,在 Linux 中进程的虚拟内存大小会比实际使用的物理内存大小要大很多,进程大部分的虚拟内存空间(尤其是文件映射部分)通常被标记为不存在,只有在访问时才会被加载到物理内存中。结合动态链接库的共享,Linux 可以很好得将物理内存物尽其用。 + +### 内存统计的实现 + +在目前的实现(Lab 3)中,用户程序在进程结构体中记录的内存区域只有栈区,堆区由内核进行代劳,同时 ELF 文件映射的内存区域也从来没有被释放过,无法被其他程序复用。 + +而相较于 Linux,本实验的并没有将内存管理抽象为具有上述复杂功能的结构:用户程序的内存占用严格等同于其虚拟内存大小,并且所有页面都会被加载到物理内存中,不存在文件映射等概念,只有堆内存和栈内存是可变的。 + +因此,其内存统计并没有那么多的细节,只需要统计用户程序的栈区和堆区的大小即可。在 `Stack` 和 `Heap` 中,已经实现了 `memory_usage` 函数来获取栈区和堆区的内存占用字节数。 + +```rust +impl Stack { + pub fn memory_usage(&self) -> u64 { + self.usage * crate::memory::PAGE_SIZE + } +} + +impl Heap { + pub fn memory_usage(&self) -> u64 { + self.end.load(Ordering::Relaxed) - self.base.as_u64() + } +} +``` + +!!! note "堆区的内存管理将在本实验后部分实现,不过此处可以直接将 `Heap` 先行加入到进程结构体中" + +那么根据上述讨论,对本实验的内存占用而言,只剩下了 ELF 文件映射的内存区域和页表的内存占用,为实现简单,本部分忽略页表的内存占用,只统计 ELF 文件映射的内存占用。 + +```rust +pub struct ProcessVm { + // page table is shared by parent and child + pub(super) page_table: PageTableContext, + + // stack is pre-process allocated + pub(super) stack: Stack, + + // heap is allocated by brk syscall + pub(super) heap: Heap, + + // code is hold by the first process + // these fields will be empty for other processes + pub(super) code: Vec, + pub(super) code_usage: u64, +} + +impl ProcessVm { + pub(super) fn memory_usage(&self) -> u64 { + self.stack.memory_usage() + self.heap.memory_usage() + self.code_usage + } +} +``` + +获取用户程序 ELF 文件映射的内存占用的最好方法是在加载 ELF 文件时记录内存占用,这需要对 `elf` 模块中的 `load_elf` 函数进行修改: + +```rust +pub fn load_elf( + elf: &ElfFile, + physical_offset: u64, + page_table: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, + user_access: bool, +) -> Result, MapToError> { + trace!("Loading ELF file...{:?}", elf.input.as_ptr()); + + // use iterator and functional programming to load segments + // and collect the loaded pages into a vector + elf.program_iter() + .filter(|segment| segment.get_type().unwrap() == program::Type::Load) + .map(|segment| { + load_segment( + elf, + physical_offset, + &segment, + page_table, + frame_allocator, + user_access, + ) + }) + .collect() +} + +// load segments to new allocated frames +fn load_segment( + elf: &ElfFile, + physical_offset: u64, + segment: &program::ProgramHeader, + page_table: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, + user_access: bool, +) -> Result> { + let virt_start_addr = VirtAddr::new(segment.virtual_addr()); + let start_page = Page::containing_address(virt_start_addr); + + // ... + + let end_page = Page::containing_address(virt_start_addr + mem_size - 1u64); + Ok(Page::range_inclusive(start_page, end_page)) +} +``` + +之后,在 `pkg/kernel/src/proc/vm` 中完善 `ProcessVm` 的 `load_elf_code` 函数,在加载 ELF 文件时记录内存占用。 + +为了便于测试和观察,在 `pkg/kernel/src/proc/manager.rs` 的 `print_process_list` 和 `Process` 的 `fmt` 实现中,添加打印内存占用的功能。 + +```rust +impl ProcessManager { + pub fn print_process_list(&self) { + let mut output = + String::from(" PID | PPID | Process Name | Ticks | Memory | Status\n"); + + // ... + + // NOTE: print memory page usage + // (you may implement following functions) + let alloc = get_frame_alloc_for_sure(); + let frames_used = alloc.frames_used(); + let frames_recycled = alloc.recycled_count(); + let frames_total = alloc.frames_total(); + + let used = (frames_used - frames_recycled) * PAGE_SIZE as usize; + let total = frames_total * PAGE_SIZE as usize; + + output += &format_usage("Memory", used, total); + drop(alloc); + + // ... + } +} + +// A helper function to format memory usage +fn format_usage(name: &str, used: usize, total: usize) -> String { + let (used_float, used_unit) = humanized_size(used as u64); + let (total_float, total_unit) = humanized_size(total as u64); + + format!( + "{:<6} : {:>6.*} {:>3} / {:>6.*} {:>3} ({:>5.2}%)\n", + name, + 2, + used_float, + used_unit, + 2, + total_float, + total_unit, + used as f32 / total as f32 * 100.0 + ) +} +``` + +```rust +impl core::fmt::Display for Process { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let inner = self.inner.read(); + let (size, unit) = humanized_size(inner.proc_vm.as_ref().map_or(0, |vm| vm.memory_usage())); + write!( + f, + " #{:-3} | #{:-3} | {:12} | {:7} | {:>5.1} {} | {:?}", + self.pid.0, + inner.parent().map(|p| p.pid.0).unwrap_or(0), + inner.name, + inner.ticks_passed, + size, + unit, + inner.status + )?; + Ok(()) + } +} +``` + +!!! success "阶段性成果" + + 使用你实现的 Shell 打印进程列表和他们的内存占用。 + + 使用 `fact` 阶乘递归程序进行测试,在其中使用 `sys_stat` 系统调用打印进程信息,尝试观察到内存占用的变化。 + +## 用户程序的内存释放 + +## 内核的内存统计 + +## 内核栈的自动增长 + +## 用户态堆 + +## 思考题 + +1. 当在 Linux 中运行程序的时候删除程序在文件系统中对应的文件,会发生什么?程序能否继续运行?遇到未被映射的内存会发生什么? + +## 加分项 diff --git a/src/0x07/pkg/kernel/src/proc/vm/heap.rs b/src/0x07/pkg/kernel/src/proc/vm/heap.rs new file mode 100644 index 0000000..7392584 --- /dev/null +++ b/src/0x07/pkg/kernel/src/proc/vm/heap.rs @@ -0,0 +1,101 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + +use alloc::sync::Arc; +use x86_64::{ + structures::paging::{mapper::UnmapError, Page}, + VirtAddr, +}; + +use super::{FrameAllocatorRef, MapperRef}; + +// user process runtime heap +// 0x100000000 bytes -> 4GiB +// from 0x0000_2000_0000_0000 to 0x0000_2000_ffff_fff8 +pub const HEAP_START: u64 = 0x2000_0000_0000; +pub const HEAP_PAGES: u64 = 0x100000; +pub const HEAP_SIZE: u64 = HEAP_PAGES * crate::memory::PAGE_SIZE; +pub const HEAP_END: u64 = HEAP_START + HEAP_SIZE - 8; + +/// User process runtime heap +/// +/// always page aligned, the range is [base, end) +pub struct Heap { + /// the base address of the heap + /// + /// immutable after initialization + base: VirtAddr, + + /// the current end address of the heap + /// + /// use atomic to allow multiple threads to access the heap + end: Arc, +} + +impl Heap { + pub fn empty() -> Self { + Self { + base: VirtAddr::new(HEAP_START), + end: Arc::new(AtomicU64::new(HEAP_START)), + } + } + + pub fn fork(&self) -> Self { + Self { + base: self.base, + end: self.end.clone(), + } + } + + pub fn brk( + &mut self, + new_end: Option, + mapper: MapperRef, + alloc: FrameAllocatorRef, + ) -> Option { + // FIXME: if new_end is None, return the current end address + + // FIXME: check if the new_end is valid (in range [base, base + HEAP_SIZE]) + + // FIXME: calculate the difference between the current end and the new end + + // NOTE: print the heap difference for debugging + + // FIXME: do the actual mapping or unmapping + + // FIXME: update the end address + + Some(new_end) + } + + pub(super) fn clean_up( + &self, + mapper: MapperRef, + dealloc: FrameAllocatorRef, + ) -> Result<(), UnmapError> { + if self.memory_usage() == 0 { + return Ok(()); + } + + // FIXME: load the current end address and **reset it to base** (use `swap`) + + // FIXME: unmap the heap pages + + Ok(()) + } + + pub fn memory_usage(&self) -> u64 { + self.end.load(Ordering::Relaxed) - self.base.as_u64() + } +} + +impl core::fmt::Debug for Heap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Heap") + .field("base", &format_args!("{:#x}", self.base.as_u64())) + .field( + "end", + &format_args!("{:#x}", self.end.load(Ordering::Relaxed)), + ) + .finish() + } +} diff --git a/src/0x07/pkg/kernel/src/proc/vm/mod.rs b/src/0x07/pkg/kernel/src/proc/vm/mod.rs new file mode 100644 index 0000000..fb24538 --- /dev/null +++ b/src/0x07/pkg/kernel/src/proc/vm/mod.rs @@ -0,0 +1,172 @@ +use alloc::{format, vec::Vec}; +use x86_64::{ + structures::paging::{ + mapper::{CleanUp, UnmapError}, + page::*, + *, + }, + VirtAddr, +}; +use xmas_elf::ElfFile; +use crate::{humanized_size, memory::*}; + +pub mod heap; +pub mod stack; + +use self::{heap::Heap, stack::Stack}; + +use super::PageTableContext; + +// See the documentation for the `KernelPages` type +// Ignore when you not reach this part +// +// use boot::KernelPages; + +type MapperRef<'a> = &'a mut OffsetPageTable<'static>; +type FrameAllocatorRef<'a> = &'a mut BootInfoFrameAllocator; + +pub struct ProcessVm { + // page table is shared by parent and child + pub(super) page_table: PageTableContext, + + // stack is pre-process allocated + pub(super) stack: Stack, + + // heap is allocated by brk syscall + pub(super) heap: Heap, + + // code is hold by the first process + // these fields will be empty for other processes + pub(super) code: Vec, + pub(super) code_usage: u64, +} + +impl ProcessVm { + pub fn new(page_table: PageTableContext) -> Self { + Self { + page_table, + stack: Stack::empty(), + heap: Heap::empty(), + code: Vec::new(), + code_usage: 0, + } + } + + + // See the documentation for the `KernelPages` type + // Ignore when you not reach this part + + /// Initialize kernel vm + /// + /// NOTE: this function should only be called by the first process + // pub fn init_kernel_vm(mut self, pages: &KernelPages) -> Self { + // // FIXME: record kernel code usage + // self.code = /* The kernel pages */; + // self.code_usage = /* The kernel code usage */; + + // self.stack = Stack::kstack(); + + // // ignore heap for kernel process as we don't manage it + + // self + // } + + pub fn brk(&mut self, addr: Option) -> Option { + self.heap.brk( + addr, + &mut self.page_table.mapper(), + &mut get_frame_alloc_for_sure(), + ) + } + + pub fn load_elf(&mut self, elf: &ElfFile) { + let mapper = &mut self.page_table.mapper(); + + let alloc = &mut *get_frame_alloc_for_sure(); + + self.load_elf_code(elf, mapper, alloc); + self.stack.init(mapper, alloc); + } + + fn load_elf_code(&mut self, elf: &ElfFile, mapper: MapperRef, alloc: FrameAllocatorRef) { + // FIXME: make the `load_elf` function return the code pages + self.code = + elf::load_elf(elf, *PHYSICAL_OFFSET.get().unwrap(), mapper, alloc, true).unwrap(); + + // FIXME: calculate code usage + self.code_usage = /* The code usage */; + } + + pub fn fork(&self, stack_offset_count: u64) -> Self { + let owned_page_table = self.page_table.fork(); + let mapper = &mut owned_page_table.mapper(); + + let alloc = &mut *get_frame_alloc_for_sure(); + + Self { + page_table: owned_page_table, + stack: self.stack.fork(mapper, alloc, stack_offset_count), + heap: self.heap.fork(), + + // do not share code info + code: Vec::new(), + code_usage: 0, + } + } + + pub fn handle_page_fault(&mut self, addr: VirtAddr) -> bool { + let mapper = &mut self.page_table.mapper(); + let alloc = &mut *get_frame_alloc_for_sure(); + + self.stack.handle_page_fault(addr, mapper, alloc) + } + + pub(super) fn memory_usage(&self) -> u64 { + self.stack.memory_usage() + self.heap.memory_usage() + self.code_usage + } + + pub(super) fn clean_up(&mut self) -> Result<(), UnmapError> { + let mapper = &mut self.page_table.mapper(); + let dealloc = &mut *get_frame_alloc_for_sure(); + + // FIXME: implement the `clean_up` function for `Stack` + self.stack.clean_up(mapper, dealloc)?; + + if self.page_table.using_count() == 1 { + // free heap + // FIXME: implement the `clean_up` function for `Heap` + self.heap.clean_up(mapper, dealloc)?; + + // free code + for page_range in self.code.iter() { + elf::unmap_range(*page_range, mapper, dealloc, true)?; + } + + unsafe { + // free P1-P3 + mapper.clean_up(dealloc); + + // free P4 + dealloc.deallocate_frame(self.page_table.reg.addr); + } + } + + // NOTE: maybe print how many frames are recycled + // **you may need to add some functions to `BootInfoFrameAllocator`** + + Ok(()) + } +} + +impl core::fmt::Debug for ProcessVm { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (size, unit) = humanized_size(self.memory_usage()); + + f.debug_struct("ProcessVm") + .field("stack", &self.stack) + .field("heap", &self.heap) + .field("memory_usage", &format!("{} {}", size, unit)) + .field("page_table", &self.page_table) + .finish() + } +}