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()
+ }
+}