找回密码
 立即注册
首页 业界区 安全 [rCore学习笔记 032] 管理SV39多级页表

[rCore学习笔记 032] 管理SV39多级页表

忌才砟 2025-6-11 15:29:53
上一节:硬件
本节:软件实现
物理页帧管理

内核->空闲内存->物理页帧->分配->存放
可用物理页的分配和回收

Link文件中关于操作系统bin文件的内存设计

内容os\src\linker-qemu.ld:
  1. OUTPUT_ARCH(riscv)
  2. ENTRY(_start)
  3. BASE_ADDRESS = 0x80200000;
  4. SECTIONS
  5. {
  6.     . = BASE_ADDRESS;
  7.     skernel = .;
  8.     stext = .;
  9.     .text : {
  10.         *(.text.entry)
  11.         . = ALIGN(4K);
  12.         strampoline = .;
  13.         *(.text.trampoline);
  14.         . = ALIGN(4K);
  15.         *(.text .text.*)
  16.     }
  17.     . = ALIGN(4K);
  18.     etext = .;
  19.     srodata = .;
  20.     .rodata : {
  21.         *(.rodata .rodata.*)
  22.         *(.srodata .srodata.*)
  23.     }
  24.     . = ALIGN(4K);
  25.     erodata = .;
  26.     sdata = .;
  27.     .data : {
  28.         *(.data .data.*)
  29.         *(.sdata .sdata.*)
  30.     }
  31.     . = ALIGN(4K);
  32.     edata = .;
  33.     sbss_with_stack = .;
  34.     .bss : {
  35.         *(.bss.stack)
  36.         sbss = .;
  37.         *(.bss .bss.*)
  38.         *(.sbss .sbss.*)
  39.     }
  40.     . = ALIGN(4K);
  41.     ebss = .;
  42.     ekernel = .;
  43.     /DISCARD/ : {
  44.         *(.eh_frame)
  45.     }
  46. }
复制代码
1.png

可以看到我们可以分配的内从从ekernel开始,而从哪里结束取决于我们的物理设备,这里因为学习的是K210,那么选择的实际上是8MiB的内存,也就是从0x80000000到0x80800000.
这里在子模块os\src\boards\qemu.rs里有:
  1. // os/src/config.rs
  2. pub const MEMORY_END: usize = 0x80800000;
复制代码
那么我们在这里就是要实现一个页帧内存分配器来管理这段内存,首先我们先将初始地址结束地址转换为页号:
2.png

实现代码:
  1. // os\src\mm\frame_allocator.rs
  2. pub struct StackFrameAllocator {
  3.     current: usize,
  4.     end: usize,
  5.     recycled: Vec<usize>,
  6. }
  7. impl StackFrameAllocator {
  8.     pub fn init(&mut self, l: PhysPageNum, r: PhysPageNum) {
  9.         self.current = l.0;
  10.         self.end = r.0;
  11.     }
  12. }
  13. impl FrameAllocator for StackFrameAllocator {
  14.     fn new() -> Self {
  15.         Self {
  16.             current: 0,
  17.             end: 0,
  18.             recycled: Vec::new(),
  19.         }
  20.     }
  21.     fn alloc(&mut self) -> Option<PhysPageNum> {
  22.         if let Some(ppn) = self.recycled.pop() {
  23.             Some(ppn.into())
  24.         } else if self.current == self.end {
  25.             None
  26.         } else {
  27.             self.current += 1;
  28.             Some((self.current - 1).into())
  29.         }
  30.     }
  31.     fn dealloc(&mut self, ppn: PhysPageNum) {
  32.         let ppn = ppn.0;
  33.         // validity check
  34.         if ppn >= self.current || self.recycled.iter().any(|&v| v == ppn) {
  35.             panic!("Frame ppn={:#x} has not been allocated!", ppn);
  36.         }
  37.         // recycle
  38.         self.recycled.push(ppn);
  39.     }
  40. }
复制代码
这里注意Vec的成功使用少不了前一部分的堆内存分配器.
3.png

为了保证每个物理页帧在被创建的时候清空页帧内容,在被销毁的时候能够自动回收.这里借用了RAII的思想.
RAII(Resource Acquisition Is Initialization) 资源和对象的声明周期一致的一种思想.
创建的时候初始化,Drop的时候进行回调
使用DropTrait来进行管理
为PhysPageNum创建一个包裹Tracker意为追踪器:
  1. /// manage a frame which has the same lifecycle as the tracker
  2. pub struct FrameTracker {
  3.     pub ppn: PhysPageNum,
  4. }
  5. impl FrameTracker {
  6.     pub fn new(ppn: PhysPageNum) -> Self {
  7.         // page cleaning
  8.         let bytes_array = ppn.get_bytes_array();
  9.         for i in bytes_array {
  10.             *i = 0;
  11.         }
  12.         Self { ppn }
  13.     }
  14. }
  15. impl Debug for FrameTracker {
  16.     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
  17.         f.write_fmt(format_args!("FrameTracker:PPN={:#x}", self.ppn.0))
  18.     }
  19. }
  20. impl Drop for FrameTracker {
  21.     fn drop(&mut self) {
  22.         frame_dealloc(self.ppn);
  23.     }
  24. }
复制代码
这里要注意,要想直接访问物理地址,必须是使用M特权级.
但是我们使用的是S和U特权级,因此,需要一个恒等映射来解决这个问题.
这里恒等映射,其实是存在一个小问题的因为我们物理地址正常是56位,但是虚拟地址是39位,理论上会出问题.
但是我们的内存从[0x8000_0000,0x8080_0000)恰好弥补了这一点,一共就涉及32位嘛.
如下图所示,恒等映射就是把PPN和VPN用下一节提到的map映射起来,可以看到8MiB的映射需要约16KiB的页表项(忽略一级和二级页表损耗), 这里总结下来就是原本大小除以512 就是页表项的损耗.
4.png

注意这个L1 3Bit,只能有000,001,010,011四种状态,要注意是左闭右开的区间,4*4KiB=16KiB.
建立和拆除虚实地址映射关系

实际上就是建立: 虚拟地址 -> 物理地址的接口和映射.
5.png

实现方法:
  1. type FrameAllocatorImpl = StackFrameAllocator;
  2. lazy_static! {
  3.     /// frame allocator instance through lazy_static!
  4.     pub static ref FRAME_ALLOCATOR: UPSafeCell<FrameAllocatorImpl> =
  5.         unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) };
  6. }
  7. /// initiate the frame allocator using `ekernel` and `MEMORY_END`
  8. pub fn init_frame_allocator() {
  9.     unsafe extern "C" {
  10.         safe fn ekernel();
  11.     }
  12.     FRAME_ALLOCATOR.exclusive_access().init(
  13.         PhysAddr::from(ekernel as usize).ceil(),
  14.         PhysAddr::from(MEMORY_END).floor(),
  15.     );
  16. }
  17. /// allocate a frame
  18. pub fn frame_alloc() -> Option<FrameTracker> {
  19.     FRAME_ALLOCATOR
  20.         .exclusive_access()
  21.         .alloc()
  22.         .map(FrameTracker::new)
  23. }
  24. /// deallocate a frame
  25. fn frame_dealloc(ppn: PhysPageNum) {
  26.     FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
  27. }
复制代码
对应的VPN转PPN的方法:
  1. // os\src\mm\address.rs
  2. impl PhysPageNum {
  3.     pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
  4.         let pa: PhysAddr = (*self).into();
  5.         unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
  6.     }
  7.     pub fn get_bytes_array(&self) -> &'static mut [u8] {
  8.         let pa: PhysAddr = (*self).into();
  9.         unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
  10.     }
  11.     pub fn get_mut<T>(&self) -> &'static mut T {
  12.         let pa: PhysAddr = (*self).into();
  13.         unsafe { (pa.0 as *mut T).as_mut().unwrap() }
  14.     }
  15. }
复制代码
同样地,如果只进行查找不进行创建,可以得到如下两个方法:
  1. // os\src\mm\page_table.rs
  2. fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
  3.         let idxs = vpn.indexes();
  4.         let mut ppn = self.root_ppn;
  5.         let mut result: Option<&mut PageTableEntry> = None;
  6.         for (i, idx) in idxs.iter().enumerate() {
  7.             let pte = &mut ppn.get_pte_array()[*idx];
  8.             if i == 2 {
  9.                 result = Some(pte);
  10.                 break;
  11.             }
  12.             if !pte.is_valid() {
  13.                 let frame = frame_alloc().unwrap();
  14.                 *pte = PageTableEntry::new(frame.ppn, PTEFlags::V);
  15.                 self.frames.push(frame);
  16.             }
  17.             ppn = pte.ppn();
  18.         }
  19.         result
  20.     }
复制代码
总结

如果把kernel当作一个软件来看,基本的内存分配目前是如下图所示的:
6.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册