页表
本节介绍NUDT-OS中的页表抽象。
页表项
/// 页表项
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct PageTableEntry(usize);
impl PageTableEntry {
/// 1111..._0000_0000_0000
const PHYS_ADDR_MASK: usize = !(PAGE_SIZE - 1);
/// 将一个物理地址和flags填入一个页表项中
pub const fn new_page(pa: PhysAddr, flags: PageTableFlags) -> Self {
Self((pa.0 & Self::PHYS_ADDR_MASK) | flags.bits() as usize)
}
...
}
每个页表项中存放一个物理地址(下一级页表的起始物理地址),这个地址是关于4096字节对齐的,故低十二位用来存放页表项可能的标记。new_page
方法将一个物理地址和一组flags写入一个页表项中。
页表
/// 页表
pub struct PageTable {
/// 4级页表的起始物理地址
pub root_pa: PhysAddr,
/// 页表包含的一组物理页帧
tables: Vec<PhysFrame>,
}
页表结构体PageTable
中包含了四级页表首地址和一个物理页帧的队列。每个物理页帧存放一张大小为4096字节的某级页表。
内核运行时,cr3寄存器中存放四级页表的起始物理地址,硬件的MMU模块寻址虚拟地址时会从cr3寄存器中取出页表地址并查询,最终转换得虚拟地址的物理地址。进程切换时,将cr3寄存器载入自己的页表的起始物理地址即实现了地址空间切换。
x64架构中虚拟地址的各字段含义和地址翻译过程见下图:
/// 从虚拟地址获取四级页表索引,cr3中存四级页表首地址
///
/// 按这个索引取出的是三级页表首地址
const fn p4_index(va: VirtAddr) -> usize {
(va.0 >> (12 + 27)) & (ENTRY_COUNT - 1)
}
/// 从虚拟地址获取三级页表索引
const fn p3_index(va: VirtAddr) -> usize {
(va.0 >> (12 + 18)) & (ENTRY_COUNT - 1)
}
/// 从虚拟地址获取二级页表索引
const fn p2_index(va: VirtAddr) -> usize {
(va.0 >> (12 + 9)) & (ENTRY_COUNT - 1)
}
/// 从虚拟地址获取一级页表索引
const fn p1_index(va: VirtAddr) -> usize {
(va.0 >> 12) & (ENTRY_COUNT - 1)
}
/// 获取一个虚拟地址的物理地址,可能创建低级页表
fn get_entry_or_create(&mut self, va: VirtAddr) -> Option<&mut PageTableEntry> {
let p4 = table_of(self.root_pa);
let p4e = &mut p4[p4_index(va)];
let p3 = next_table_or_create(p4e, || self.alloc_table())?;
let p3e = &mut p3[p3_index(va)];
let p2 = next_table_or_create(p3e, || self.alloc_table())?;
let p2e = &mut p2[p2_index(va)];
let p1 = next_table_or_create(p2e, || self.alloc_table())?;
let p1e = &mut p1[p1_index(va)];
Some(p1e)
}
将上图中的地址翻译过程用Rust表示如上。
/// 映射一个虚拟地址到一个物理地址,写入页表
impl PageTable {
pub fn map(&mut self, va: VirtAddr, pa: PhysAddr, flags: PageTableFlags) {
let entry = self.get_entry_or_create(va).unwrap();
if !entry.is_unused() {
panic!("{:#x?} is mapped before mapping", va);
}
*entry = PageTableEntry::new_page(pa.align_down(), flags);
}
/// 取消映射一个虚拟地址,清除页表
pub fn unmap(&mut self, va: VirtAddr) {
let entry = get_entry(self.root_pa, va).unwrap();
if entry.is_unused() {
panic!("{:#x?} is invalid before unmapping", va);
}
entry.0 = 0;
}
}
最终实现页表的map
和unmap
方法