地址空间
本节介绍虚拟地址空间和虚拟内存区域,虚拟地址空间由一张页表和若干虚拟内存区域组成。虚拟内存区域指的是在虚拟地址上连续的一段区域。
虚拟内存区域
/// 一段连续的内存区域
pub struct MapArea {
/// 起始虚地址
pub start: VirtAddr,
/// 区域大小
pub size: usize,
/// 映射标志
pub flags: PageTableFlags,
/// 虚地址到物理地址的映射
pub mapper: BTreeMap<VirtAddr, PhysFrame>,
}
一段区域共用同一组映射标记。mapper存放了这段区域中所有虚拟页面到物理页帧的映射关系。
impl MapArea {
/// 获取一个虚拟地址的物理地址,若没有映射则为其分配一个物理页帧
///
/// 返回虚地址的物理地址
pub fn map(&mut self, va: VirtAddr) -> PhysAddr {
assert!(va.is_aligned());
match self.mapper.entry(va) {
Entry::Occupied(e) => e.get().start_pa(),
Entry::Vacant(e) => e.insert(PhysFrame::alloc_zero().unwrap()).start_pa(),
}
}
/// 取消映射一个虚拟地址,物理页帧会由编译器drop释放位
pub fn unmap(&mut self, va: VirtAddr) {
self.mapper.remove(&va);
}
/// 克隆一段内存区域
pub fn clone(&self) -> Self {
let mut mapper = BTreeMap::new();
for (&va, old_pa) in &self.mapper {
// 分配一个新的物理页帧
let new = PhysFrame::alloc().unwrap();
new.as_slice().copy_from_slice(old_pa.as_slice());
mapper.insert(va, new);
}
Self {
start: self.start,
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
方法时,将映射关系写入页表。
虚拟地址空间
/// 一个地址空间
pub struct MemorySet {
/// 页表
pub pt: PageTable,
/// 内存区域起地址到内存区域的映射
areas: BTreeMap<VirtAddr, MapArea>,
}
地址空间包含一张页表和若干内存区域。
impl MemorySet {
/// 创建一个新的虚拟地址空间
pub fn new() -> Self {
Self {
pt: PageTable::new(),
areas: BTreeMap::new(),
}
}
/// 地址空间中插入一段内存区域
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
方法清除地址空间中所有内存区域的映射关系,并清除页表中所有对应的页表项。
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
方法清除页表项,释放分配的物理页帧对应的位。克隆地址空间时,首先克隆原地址空间的所有内存区域,再插入到新的地址空间中。