需要实现什么
虚拟内存机制
拥有什么
使用固定内核栈和用户栈的分时多任务系统
思路
将 loader.rs 中的固定内核栈和用户栈转为根据虚拟内存机制创建的内核空间和用户空间。
用户:
将用户进程初始化从TASK_MANAGER移到TaskControlBlock::new()中,实现根据elf自动创建用户地址空间。
当访问一段虚拟地址时,向satp寄存器中写入页表根地址并启动 MMU 三级页表机制。根据虚拟地址中的三个9字节长度的页表项地址逐级从页表中找到三个页表项,最后从最后一个页表项中获取物理页号。
因此,我们需要实现的内容有:
- 物理地址pa、虚拟地址va、物理页号ppn、虚拟页号vpn
- 地址空间 MemorySet、页表 PageTable、页表项 PageTableEntry
- 两个用于自动回收内存的 FrameTracker 和 逻辑段 MapArea
内核:
初始化内存管理单元,类似用户进程创建内核的内存空间与页表并写入 satp 寄存器。将 MapArea 中的 MapType 设置为 Identical 使用恒等映射操纵内存。
TASK_MANAGER 从 elf 文件中统一加载进程并创建用户空间。
改进 Trap 处理和 sys_write 的实现。
本章代码树
├── os
│ ├── ...
│ └── src
│ ├── ...
│ ├── config.rs(修改:新增一些内存管理的相关配置)
│ ├── linker.ld(修改:将跳板页引入内存布局)
│ ├── loader.rs(修改:仅保留获取应用数量和数据的功能)
│ ├── main.rs(修改)
│ ├── mm(新增:内存管理的 mm 子模块)
│ │ ├── address.rs(物理/虚拟 地址/页号的 Rust 抽象)
│ │ ├── frame_allocator.rs(物理页帧分配器)
│ │ ├── heap_allocator.rs(内核动态内存分配器)
│ │ ├── memory_set.rs(引入地址空间 MemorySet 及逻辑段 MemoryArea 等)
│ │ ├── mod.rs(定义了 mm 模块初始化方法 init)
│ │ └── page_table.rs(多级页表抽象 PageTable 以及其他内容)
│ ├── syscall
│ │ ├── fs.rs(修改:基于地址空间的 sys_write 实现)
│ │ ├── mod.rs
│ │ └── process.rs
│ ├── task
│ │ ├── context.rs(修改:构造一个跳转到不同位置的初始任务上下文)
│ │ ├── mod.rs(修改,详见文档)
│ │ ├── switch.rs
│ │ ├── switch.S
│ │ └── task.rs(修改,详见文档)
│ └── trap
│ ├── context.rs(修改:在 Trap 上下文中加入了更多内容)
│ ├── mod.rs(修改:基于地址空间修改了 Trap 机制,详见文档)
│ └── trap.S(修改:基于地址空间修改了 Trap 上下文保存与恢复汇编代码)
└── user
├── build.py(编译时不再使用)
├── ...
└── src
├── linker.ld(修改:将所有应用放在各自地址空间中固定的位置)
└── ...
实现过程
物理地址PhysAddr,虚拟地址VirtAddr,物理页号PhysPageNum,虚拟页号VirtPageNum
实现了地址页号间相互转换、从物理地址中读取数据(页表项、页、任意类型指针)、从虚拟地址中读取各级页表项地址。
用户进程
user用户程序中 linker.ld(修改:将所有应用放在各自地址空间中固定的位置)
任务控制块TaskControlBlock
每个任务都有自己的地址空间 MemorySet 。
地址空间 MemorySet
地址空间是一系列有关联的逻辑段, 用来表明正在运行的应用所在执行环境中的可访问内存空间,在这个内存空间中,包含了页表和一系列的不一定连续的逻辑段。 这样我们就有任务的地址空间、内核的地址空间等说法了。
// os/src/mm/memory_set.rs
pub struct MemorySet {
page_table: PageTable,
areas: Vec<MapArea>,
}
PageTable 下挂着所有多级页表的节点所在的物理页帧,而每个 MapArea 下则挂着对应逻辑段中的数据所在的物理页帧,这两部分 合在一起构成了一个地址空间所需的所有物理页帧。这同样是一种 RAII 风格,当一个地址空间 MemorySet 生命周期结束后, 这些物理页帧都会被回收。
提供from_elf方法用于创建用户程序地址空间。
其中.text即为代码段,为只读;.data为已初始化读写数据段;.rodata为只读数据段;.bss段包含程序中未初始化的全局变量和static变量。
from_elf:将跳板插入到应用地址空间(Trampoline)->解析elf并写入逻辑段MapArea中(Framed)->在目前涉及到的最大的虚拟页号 max_end_vpn 上面加入保护页和用户栈(Guard Page和User Stack)->映射次高页面来存放 Trap 上下文(TrapContext)。
/// Include sections in elf and trampoline and TrapContext and user stack,
/// also returns user_sp_base and entry point.
pub fn from_elf(elf_data: &[u8]) -> (Self, usize, usize) {
let mut memory_set = Self::new_bare();
// map trampoline
memory_set.map_trampoline();
// map program headers of elf, with U flag
let elf = xmas_elf::ElfFile::new(elf_data).unwrap();
let elf_header = elf.header;
let magic = elf_header.pt1.magic;
assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!");
let ph_count = elf_header.pt2.ph_count();
let mut max_end_vpn = VirtPageNum(0);
for i in 0..ph_count {
let ph = elf.program_header(i).unwrap();
if ph.get_type().unwrap() == xmas_elf::program::Type::Load {
let start_va: VirtAddr = (ph.virtual_addr() as usize).into();
let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into();
let mut map_perm = MapPermission::U;
let ph_flags = ph.flags();
if ph_flags.is_read() {
map_perm |= MapPermission::R;
}
if ph_flags.is_write() {
map_perm |= MapPermission::W;
}
if ph_flags.is_execute() {
map_perm |= MapPermission