地址空间

本节介绍虚拟地址空间和虚拟内存区域,虚拟地址空间由一张页表和若干虚拟内存区域组成。虚拟内存区域指的是在虚拟地址上连续的一段区域。

虚拟内存区域

/// 虚存区域
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方法清除页表项,释放分配的物理页帧对应的位。克隆地址空间时,首先克隆原地址空间的所有内存区域,再插入到新的地址空间中。