地址空间管理
概述
地址空间管理是 MM 子系统的最高抽象层,负责管理整个虚拟地址空间的布局、映射区域和页表操作。每个进程拥有独立的虚拟地址空间,支持内核和用户态的内存隔离。
设计目标
- 地址空间隔离:每个进程拥有独立的虚拟地址空间
- 灵活的内存布局:支持代码段、数据段、堆、栈等多种区域
- 按需分配:延迟分配物理内存,节省资源
- 系统调用支持:实现 brk、mmap、munmap 等内存管理系统调用
核心结构
MemorySpace
#![allow(unused)] fn main() { pub struct MemorySpace { page_table: ActivePageTableInner, // 页表(管理虚拟→物理映射) areas: Vec<MappingArea>, // 映射区域列表 heap_top: Option<Vpn>, // 用户堆顶(brk) } }
MappingArea
#![allow(unused)] fn main() { pub struct MappingArea { vpn_range: VpnRange, // 虚拟页号范围 [start, end) area_type: AreaType, // 区域类型(代码/数据/堆/栈) map_type: MapType, // 映射策略(Direct/Framed) permission: UniversalPTEFlag, // 权限标志(R/W/X/U) frames: BTreeMap<Vpn, TrackedFrames>, // 物理帧映射(Framed 类型使用) } }
映射策略
Direct 直接映射
用于内核空间,虚拟地址直接对应物理地址:
虚拟地址 物理地址
0xFFFF_FFC0_8000_0000 ←→ 0x8000_0000
0xFFFF_FFC0_8000_1000 ←→ 0x8000_1000
特点:
✓ 无需分配物理帧
✓ 访问物理内存无需查页表
✓ 仅用于内核空间
#![allow(unused)] fn main() { // 实现 for vpn in self.vpn_range { let vaddr = vpn.start_addr(); let paddr = vaddr.to_paddr(); let ppn = Ppn::from_addr_floor(paddr); page_table.map(vpn, ppn, PageSize::Size4K, self.permission)?; } }
Framed 帧映射
用于用户空间,每个虚拟页分配独立的物理帧:
虚拟页号 物理页号
VPN 0x1000 ←→ PPN 0x8234_5 (分配)
VPN 0x1001 ←→ PPN 0x8456_7 (分配)
特点:
✓ 每个虚拟页分配独立物理帧
✓ 物理内存可能不连续
✓ 自动管理物理帧生命周期(RAII)
#![allow(unused)] fn main() { // 实现 for vpn in self.vpn_range { let frame = alloc_frame()?; let ppn = frame.ppn(); page_table.map(vpn, ppn, PageSize::Size4K, self.permission)?; self.frames.insert(vpn, TrackedFrames::Single(frame)); } }
地址空间创建
内核地址空间
#![allow(unused)] fn main() { pub fn new_kernel() -> Self { let mut space = Self { page_table: ActivePageTableInner::new(), areas: Vec::new(), heap_top: None, }; // 1. 映射跳板页 space.map_trampoline(); // 2. 映射内核各段(Direct 映射) space.map_kernel_text(); // .text R+X space.map_kernel_rodata(); // .rodata R space.map_kernel_data(); // .data R+W space.map_kernel_bss(); // .bss R+W space.map_kernel_heap(); // heap R+W // 3. 直接映射物理内存 space.map_physical_memory(); space } }
内核段映射示例:
#![allow(unused)] fn main() { // .text 段(只读可执行) let text_start = Vaddr::new(stext as usize); let text_end = Vaddr::new(etext as usize); space.push(MappingArea::new( VaddrRange::new(text_start, text_end), MapType::Direct, UniversalPTEFlag::kernel_r() | UniversalPTEFlag::X, AreaType::KernelText, )); }
用户地址空间
#![allow(unused)] fn main() { pub fn from_elf(elf_data: &[u8]) -> Self { let mut space = Self { page_table: ActivePageTableInner::new(), areas: Vec::new(), heap_top: None, }; // 1. 解析 ELF 文件,映射各段(Framed 映射) let elf = xmas_elf::ElfFile::new(elf_data).unwrap(); for program_header in elf.program_iter() { if program_header.get_type() == Ok(xmas_elf::program::Type::Load) { // 创建映射区域 let area = MappingArea::new( vaddr_range, MapType::Framed, permission, area_type, ); // 拷贝数据到物理页 area.copy_data(&space.page_table, program_header.get_data(&elf).unwrap()); space.areas.push(area); } } // 2. 映射用户栈 space.map_user_stack(); // 3. 映射 trap 上下文 space.map_trap_context(); // 4. 初始化堆 space.heap_top = Some(space.infer_heap_start()); space } }
系统调用支持
brk - 堆扩展
#![allow(unused)] fn main() { pub fn brk(&mut self, new_end: Vaddr) -> SyscallResult<Vaddr> { let new_end_vpn = Vpn::from_addr_ceil(new_end); let current_end_vpn = self.heap_top.unwrap(); if new_end_vpn > current_end_vpn { // 扩展堆 let heap_area = self.find_heap_area_mut()?; heap_area.extend(&mut self.page_table, new_end_vpn)?; self.heap_top = Some(new_end_vpn); } else if new_end_vpn < current_end_vpn { // 收缩堆 let heap_area = self.find_heap_area_mut()?; heap_area.shrink(&mut self.page_table, new_end_vpn)?; self.heap_top = Some(new_end_vpn); } Ok(new_end) } }
使用场景:
#![allow(unused)] fn main() { // C 标准库 malloc 底层调用 let old_brk = process.memory_space.brk(Vaddr::new(0))?; // 获取当前堆顶 let new_brk = old_brk + Vaddr::new(size); process.memory_space.brk(new_brk)?; // 扩展堆 }
mmap - 匿名内存映射
#![allow(unused)] fn main() { pub fn mmap(&mut self, start: Vaddr, len: usize, prot: usize) -> SyscallResult<Vaddr> { let start_vpn = Vpn::from_addr_floor(start); let end_vpn = Vpn::from_addr_ceil(start + Vaddr::new(len)); // 检查地址范围是否可用 self.check_range_available(VpnRange::new(start_vpn, end_vpn))?; // 创建新的映射区域 let permission = Self::prot_to_pte_flags(prot); let area = MappingArea::new( VaddrRange::new(start, start + Vaddr::new(len)), MapType::Framed, permission, AreaType::UserAnonymous, ); // 映射到页表 area.map(&mut self.page_table)?; self.areas.push(area); Ok(start) } }
使用场景:
#![allow(unused)] fn main() { // 用户程序请求匿名内存 let addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); }
munmap - 取消映射
#![allow(unused)] fn main() { pub fn munmap(&mut self, start: Vaddr, len: usize) -> SyscallResult<()> { let start_vpn = Vpn::from_addr_floor(start); let end_vpn = Vpn::from_addr_ceil(start + Vaddr::new(len)); let unmap_range = VpnRange::new(start_vpn, end_vpn); // 找到重叠的映射区域并取消映射 let mut areas_to_remove = Vec::new(); for (idx, area) in self.areas.iter().enumerate() { if area.vpn_range.intersects(&unmap_range) { areas_to_remove.push(idx); } } // 取消映射并释放资源 for idx in areas_to_remove.iter().rev() { let area = self.areas.remove(*idx); area.unmap(&mut self.page_table)?; } Ok(()) } }
进程管理
fork 时的地址空间复制
#![allow(unused)] fn main() { pub fn clone_for_fork(&self) -> Self { let mut new_space = Self { page_table: ActivePageTableInner::new(), areas: Vec::new(), heap_top: self.heap_top, }; // 深拷贝所有映射区域 for area in &self.areas { let mut new_area = area.clone_structure(); // 拷贝物理页内容 for vpn in area.vpn_range { if let Some(old_frame) = area.frames.get(&vpn) { let new_frame = old_frame.clone(); // 分配新帧并拷贝数据 new_area.frames.insert(vpn, new_frame); } } // 映射到新页表 new_area.map(&mut new_space.page_table)?; new_space.areas.push(new_area); } new_space } }
写时复制(COW)优化(未来改进):
fork 时共享物理页并标记为只读,写入时触发缺页异常再复制,可显著提升性能并减少内存占用。
激活地址空间
#![allow(unused)] fn main() { pub fn activate(&self) { let root_ppn = self.page_table.root_ppn(); ActivePageTableInner::activate(root_ppn); } }
使用场景:
#![allow(unused)] fn main() { // 进程切换 fn switch_to_process(process: &mut Process) { process.memory_space.activate(); // 切换页表 // 跳转到用户态 } }
区域类型
#![allow(unused)] fn main() { pub enum AreaType { KernelText, // 内核代码段 KernelData, // 内核数据段 KernelHeap, // 内核堆 UserText, // 用户代码段 UserData, // 用户数据段 UserHeap, // 用户堆 UserStack, // 用户栈 UserAnonymous, // 用户匿名映射(mmap) Trampoline, // 跳板页 TrapContext, // Trap 上下文 } }
使用场景
场景 1:创建新进程
#![allow(unused)] fn main() { // 从 ELF 文件加载程序 let elf_data = load_elf_from_disk("/bin/hello")?; let memory_space = MemorySpace::from_elf(&elf_data); let process = Process { memory_space, // ... 其他字段 }; }
场景 2:进程 fork
#![allow(unused)] fn main() { fn sys_fork() -> SyscallResult<Pid> { let parent = current_process(); let child_space = parent.memory_space.clone_for_fork(); let child = Process { memory_space: child_space, parent: Some(parent.pid()), // ... 其他字段 }; Ok(child.pid()) } }
场景 3:动态内存分配(brk)
#![allow(unused)] fn main() { fn sys_brk(new_brk: usize) -> SyscallResult<usize> { let process = current_process_mut(); let new_end = Vaddr::new(new_brk); process.memory_space.brk(new_end)?; Ok(new_brk) } }
场景 4:内存映射(mmap)
#![allow(unused)] fn main() { fn sys_mmap(start: usize, len: usize, prot: usize) -> SyscallResult<usize> { let process = current_process_mut(); let start_vaddr = Vaddr::new(start); let mapped_addr = process.memory_space.mmap(start_vaddr, len, prot)?; Ok(mapped_addr.as_usize()) } }
常见问题
Q1: 内核和用户地址空间如何隔离?
A: 通过虚拟地址范围和 U 标志位:
- 内核空间:高半核(0xffff_ffc0_0000_0000 以上),U=0
- 用户空间:低地址(0x0 开始),U=1
- CPU 在用户态无法访问 U=0 的页面
Q2: fork 时为什么要深拷贝?
A: 当前实现保证父子进程完全独立。未来可改用写时复制(COW)优化性能。
Q3: 如何防止用户程序访问内核内存?
A: 两层保护:
- 页表权限:内核页面设置 U=0
- 地址检查:系统调用参数验证用户指针合法性
Q4: mmap 分配的地址范围如何选择?
A: 当前实现要求用户指定地址。未来可实现地址分配器自动选择空闲区域。
性能优化
TLB 刷新优化
#![allow(unused)] fn main() { // ✅ 高效:批量映射后一次性刷新 for vpn in vpn_range { area.map_single_page(vpn, &mut page_table)?; } PageTableInner::tlb_flush_all(); }
内存占用优化
- 共享只读页面(代码段、只读数据)
- 写时复制(COW)
- 大页支持(减少页表级数)
- 延迟分配(按需分配物理帧)
相关文档
参考实现
- 源代码:
os/src/mm/memory_space/ - 初始化:
os/src/mm/mod.rs:47-51(内核地址空间)