清华训练营悟道篇之操作系统的内存管理
文章目录
- SV39多级页表的硬件机制
- 物理页帧的管理
- 多级页表管理
- 内核与应用的地址空间
SV39多级页表的硬件机制
- 三级页表
- 共3*9 = 27位虚拟页号
- 第 26-18 位为一级页索引 VPN0
- 第 17-9 位为二级页索引 VPN1
- 第 8-0 位为三级页索引 VPN2
- 每个页表都用 9 位索引
- 2^9 =512 个页表项
- 每个页表项 64位 = 8 字节
- 每个页表大小都为 512×8=4KiB
- 每个页表刚好被放到一个物理页框中
- 虚拟地址 (VPN0,VPN1,VPN2,offset) :
- 首先会记录装载「当前所用的一级页表的物理页」的页号到 satp 寄存器中;
- 把 VPN0 作为偏移在一级页表的物理页中找到二级页表的物理页号;
- 把 VPN1 作为偏移在二级页表的物理页中找到三级页表的物理页号;
- 把 VPN2 作为偏移在三级页表的物理页中找到要访问位置的物理页号;
- 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址
- 共3*9 = 27位虚拟页号
物理页帧的管理
- [ekernel, MEMORY_END)
- 总共 8M - rCore内核所占内存的大小
/// Allocate a physical page frame in FrameTracker style
pub fn frame_alloc() -> Option<FrameTracker> {
FRAME_ALLOCATOR
.exclusive_access()
.alloc()
.map(FrameTracker::new)
}
/// Deallocate a physical page frame with a given ppn
pub fn frame_dealloc(ppn: PhysPageNum) {
FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
}
- RAII
impl Drop for FrameTracker {
fn drop(&mut self) {
frame_dealloc(self.ppn);
}
}
多级页表管理
/// page table structure
pub struct PageTable {
root_ppn: PhysPageNum,
frames: Vec<FrameTracker>,
}
/// Assume that it won't oom when creating/mapping.
impl PageTable {
/// Create a new page table
pub fn new() -> Self {
let frame = frame_alloc().unwrap();
PageTable {
root_ppn: frame.ppn,
frames: vec![frame],
}
}
}
-
PageTable
- PageTable 要保存它根节点的物理页号 root_ppn 作为页表唯一的区分标志
- frames: Vec: 以 FrameTracker 的形式保存了页表所有的节点(包括根节点)所在的物理页帧
-
内核中直接访问物理地址(恒等映射)
- get_pte_array 得到某级页表的所有页表项
impl PhysPageNum {
/// Get the reference of page table(array of ptes)
pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
let pa: PhysAddr = (*self).into();
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
}
/// Get the reference of page(array of bytes)
pub fn get_bytes_array(&self) -> &'static mut [u8] {
let pa: PhysAddr = (*self).into();
unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
}
/// Get the mutable reference of physical address
pub fn get_mut<T>(&self) -> &'static mut T {
let pa: PhysAddr = (*self).into();
pa.get_mut()
}
}
- map and unmap
- 从一个虚拟地址中得到一级、二级、三级页表索引
impl VirtPageNum {
/// Get the indexes of the page table entry
pub fn indexes(&self) -> [usize; 3] {
let mut vpn = self.0;
let mut idx = [0usize; 3];
for i in (0..3).rev() {
idx[i] = vpn & 511;
vpn >>= 9;
}
idx
}
}
-
- 逐级遍历页表,如果发现对应的页表没有被建立,则申请一个物理页框并填上一级页表项
/// Find PageTableEntry by VirtPageNum, create a frame for a 4KB page table if not exist
fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
let idxs = vpn.indexes();
let mut ppn = self.root_ppn;
let mut result: Option<&mut PageTableEntry> = None;
for (i, idx) in idxs.iter().enumerate() {
let pte = &mut ppn.get_pte_array()[*idx];
if i == 2 {
result = Some(pte);
break;
}
if !pte.is_valid() {
let frame = frame_alloc().unwrap();
*pte = PageTableEntry::new(frame.ppn, PTEFlags::V);
self.frames.push(frame);
}
ppn = pte.ppn();
}
result
}
-
- 逐级遍历页表,如果发现对应的页表没有被建立,则申请一个物理页框并填上一级页表项
/// Find PageTableEntry by VirtPageNum, create a frame for a 4KB page table if not exist
fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
let idxs = vpn.indexes();
let mut ppn = self.root_ppn;
let mut result: Option<&mut PageTableEntry> = None;
for (i, idx) in idxs.iter().enumerate() {
let pte = &mut ppn.get_pte_array()[*idx];
if i == 2 {
result = Some(pte);
break;
}
if !pte.is_valid() {
let frame = frame_alloc().unwrap();
*pte = PageTableEntry::new(frame.ppn, PTEFlags::V);
self.frames.push(frame);
}
ppn = pte.ppn();
}
result
}
-
- map与unmap的具体实现
/// set the map between virtual page number and physical page number
#[allow(unused)]
pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
let pte = self.find_pte_create(vpn).unwrap();
assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn);
*pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
}
/// remove the map between virtual page number and physical page number
#[allow(unused)]
pub fn unmap(&mut self, vpn: VirtPageNum) {
let pte = self.find_pte(vpn).unwrap();
assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn);
*pte = PageTableEntry::empty();
}
内核与应用的地址空间
- 逻辑段 MapArea
- 描述一段建立了映射的连续的虚拟地址区间
- 管理虚拟页面到物理页框的映射关系
- 管理页表映射方式(MapType)与映射的标志位(MapPermission)
- 地址空间管理
- 页表 + 一个 vec 维护的 MapArea
- 内核地址空间
- 应用地址空间
- 在 os/src/build.rs 中,不再将丢弃了所有符号的应用二进制镜像链接进内核,
- 直接使用 ELF 格式的可执行文件
- 这个loader子模块在后续chapter中会被文件系统替代
- 需要学习解析 ELF格式 文件的过程
- 联想一下编译原理学到的相关知识
- 基于地址空间修改分时多任务
- trampoline 这个东西需要多读几遍,很有意思
- 在 __alltraps 中需要借助寄存器 jr 而不能直接 call trap_handler 了。因为在 内存布局中,这条 .text.trampoline 段中的跳转指令和 trap_handler 都在代码段之内,汇编器(Assembler) 和链接器(Linker)会根据 linker.ld 的地址布局描述,设定电子指令的地址,并计算二者地址偏移量 并让跳转指令的实际效果为当前 pc 自增这个偏移量。但实际上我们知道由于我们设计的缘故,这条跳转指令在被执行的时候, 它的虚拟地址被操作系统内核设置在地址空间中的最高页面之内,加上这个偏移量并不能正确的得到 trap_handler 的入口地址。
- 内核
- 跳板页与内核栈
- 内核
-
-
- 内核代码的内存布局 (os/src/linker.ld里面定义的东西)
- 用户
- 用户的 linker.ld 定义的内存布局
- 用户的 linker.ld 定义的内存布局
-
/// Mention that trampoline is not collected by areas.
fn map_trampoline(&mut self) {
self.page_table.map(
VirtAddr::from(TRAMPOLINE).into(),
PhysAddr::from(strampoline as usize).into(),
PTEFlags::R | PTEFlags::X,
);
}
.text : {
*(.text.entry)
. = ALIGN(4K);
strampoline = .;
*(.text.trampoline);
. = ALIGN(4K);
*(.text .text.*)
}
.section .text.trampoline
.globl __alltraps
.globl __restore
.align 2
__alltraps:
# ...
# load kernel_satp into t0
ld t0, 34*8(sp)
# load trap_handler into t1
ld t1, 36*8(sp)
# move to kernel_sp
ld sp, 35*8(sp)
# switch to kernel space
csrw satp, t0
sfence.vma
# jump to trap_handler
jr t1
__restore:
# a0: *TrapContext in user space(Constant); a1: user space token
# switch to user space
csrw satp, a1
sfence.vma
csrw sscratch, a0
-
- trap_return
/// finally, jump to new addr of __restore asm function
pub fn trap_return() -> ! {
set_user_trap_entry();
let trap_cx_ptr = TRAP_CONTEXT_BASE;
let user_satp = current_user_token();
extern "C" {
fn __alltraps();
fn __restore();
}
let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE;
// trace!("[kernel] trap_return: ..before return");
unsafe {
asm!(
"fence.i",
"jr {restore_va}", // jump to new addr of __restore asm function
restore_va = in(reg) restore_va,
in("a0") trap_cx_ptr, // a0 = virt addr of Trap Context
in("a1") user_satp, // a1 = phy addr of usr page table
options(noreturn)
);
}
}