清华训练营悟道篇之操作系统的内存管理

文章目录

  • SV39多级页表的硬件机制
  • 物理页帧的管理
  • 多级页表管理
  • 内核与应用的地址空间

SV39多级页表的硬件机制

清华训练营悟道篇之操作系统的内存管理_第1张图片

  • 三级页表
    • 共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 就是虚拟地址对应的物理地址

物理页帧的管理

  • [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  = self.0;
        let mut idx = [0usize; 3];
        for i in (0..3).rev() {
            idx[i] =  & 511;
             >>= 9;
        }
        idx
    }
}
    • 逐级遍历页表,如果发现对应的页表没有被建立,则申请一个物理页框并填上一级页表项
/// Find PageTableEntry by VirtPageNum, create a frame for a 4KB page table if not exist
fn find_pte_create(&mut self, : VirtPageNum) -> Option<&mut PageTableEntry> {
    let idxs = .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, : VirtPageNum) -> Option<&mut PageTableEntry> {
    let idxs = .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, : VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
     let pte = self.find_pte_create().unwrap();
     assert!(!pte.is_valid(), " {:?} is mapped before mapping", );
     *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, : VirtPageNum) {
     let pte = self.find_pte().unwrap();
     assert!(pte.is_valid(), " {:?} is invalid before unmapping", );
     *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 的入口地址。
    • 内核
      • 跳板页与内核栈

清华训练营悟道篇之操作系统的内存管理_第2张图片

      • 内核代码的内存布局 (os/src/linker.ld里面定义的东西)清华训练营悟道篇之操作系统的内存管理_第3张图片
    • 用户
      • 用户的 linker.ld 定义的内存布局
        清华训练营悟道篇之操作系统的内存管理_第4张图片
/// 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)
        );
    }
}

你可能感兴趣的:(操作系统,系统架构)