地址空间
本节介绍虚拟地址空间和虚拟内存区域,虚拟地址空间由一张页表和若干虚拟内存区域组成。虚拟内存区域指的是在虚拟地址上连续的一段区域。
虚拟内存区域
/// 虚存区域
pub struct MemoryArea {
/// 起始虚地址
start_vaddr: usize,
/// 虚拟内存区域长度
size: usize,
/// 映射标识
flags: PageTableFlags,
/// 映射关系
mapper: Cell<HashMap<usize, PhysFrame>>,
}
MemoryArea
指的是一段连续的虚拟内存区域(比如一个栈空间),而物理内存并不一定连续,共用同一组映射标记。mapper
存放了这段区域中所有虚拟页面到物理页帧的映射关系。
impl MapArea {
/// 获取一个虚地址映射的物理页帧,若没有则分配一个页帧
pub fn map(&self, vaddr: usize) -> usize {
assert!(is_aligned(vaddr));
match self.mapper.get_mut().entry(vaddr) {
Entry::Occupied(e) => e.get().0,
Entry::Vacant(e) => e.insert(PhysFrame::alloc_zero().unwrap()).start_paddr(),
}
}
/// 取消映射一个虚地址
pub fn unmap(&self, vaddr: usize) {
self.mapper.get_mut().remove(&vaddr);
}
}
impl Clone for MemoryArea {
/// 克隆一块虚存区域,重新分配所有页帧
fn clone(&self) -> Self {
let mut mapper = Cell::new(HashMap::new());
for (&vaddr, frame) in self.mapper.get() {
let new_frame = PhysFrame::alloc().unwrap();
new_frame.as_slice().copy_from_slice(frame.as_slice());
mapper.insert(vaddr, new_frame);
}
Self {
start_vaddr: self.start_vaddr,
size: self.size,
flags: self.flags,
mapper,
}
}
}
映射虚拟内存时,可能会分配物理页帧。取消映射时,编译器会自动帮我们调用物理页帧的drop
方法,释放之前分配的位。
在使用fork
时,子进程复制父进程地址空间,这会复制地址空间中的所有内存区域,clone
方法简单的复制所有已经映射的虚拟地址,但是为他们分配新的物理页帧,并且将原物理页帧中的数据复制到新的物理页帧中去。
/// 映射一段虚拟内存区域,映射关系写入页表
impl PageTable{
pub fn map_area(&mut self, area: &mut MapArea) {
assert!(area.start.0 + area.size < PHYS_OFFSET);
let mut va = area.start.0;
let end = va + area.size;
while va < end {
let pa = area.map(VirtAddr(va));
self.map(VirtAddr(va), pa, area.flags);
va += PAGE_SIZE;
}
}
}
当使用页表的map_area
方法时,将映射关系写入页表。
虚拟地址空间
/// 进程地址空间
#[derive(Default)]
pub struct MemorySet {
/// 地址空间包含的虚存区域
areas: Cell<Vec<Arc<MemoryArea>>>,
/// 页表
page_table: Arc<Cell<PageTable>>,
}
地址空间包含一张页表和若干内存区域。
impl MemorySet {
/// 地址空间中插入一段内存区域
pub fn insert(&mut self, area: MapArea) {
if area.size > 0 {
if let Entry::Vacant(e) = self.areas.entry(area.start) {
// 映射关系写入页表
self.pt.map_area(e.insert(area));
} else {
panic!(
"MemorySet::insert: MapArea starts from {:#x?} is existed!",
area.start
);
}
}
}
/// 从页表中清除地址空间中所有内存区域的映射关系
pub fn clear(&mut self) {
for area in self.areas.values_mut() {
self.pt.unmap_area(area);
}
self.areas.clear();
}
/// 将自己的页表首地址写入cr3寄存器
pub fn activate(&self) {
my_x86_64::set_cr3(self.pt.root_pa.0);
}
}
向内存区域插入insert
一个内存区域时,将映射关系写入这个地址空间对应的页表。clear
方法清除地址空间中所有内存区域的映射关系,并清除页表中所有对应的页表项。‘
activate
方法将自己的四级页表起始物理地址写入Cr3寄存器,这就完成了地址空间的切换,在切换到一个新进程时,首先需要切换其地址空间。
impl Drop for MemorySet {
/// 释放时清除所有内存区域
///
/// 从页表中清除映射关系
fn drop(&mut self) {
self.clear();
}
}
impl Clone for MemorySet {
/// 克隆地址空间,即克隆其包含的所有连续内存区域
fn clone(&self) -> Self {
let mut ms = Self::new();
for area in self.areas.values() {
ms.insert(area.clone());
}
ms
}
}
MemorySet
释放时,不仅要回收其内存,还需要调用clear
方法清除页表项,释放分配的物理页帧对应的位。克隆地址空间时,首先克隆原地址空间的所有内存区域,再插入到新的地址空间中。