上一节:硬件
本节:软件实现
物理页帧管理
内核->空闲内存->物理页帧->分配->存放
可用物理页的分配和回收
Link文件中关于操作系统bin文件的内存设计
内容os\src\linker-qemu.ld:- OUTPUT_ARCH(riscv)
- ENTRY(_start)
- BASE_ADDRESS = 0x80200000;
- SECTIONS
- {
- . = BASE_ADDRESS;
- skernel = .;
- stext = .;
- .text : {
- *(.text.entry)
- . = ALIGN(4K);
- strampoline = .;
- *(.text.trampoline);
- . = ALIGN(4K);
- *(.text .text.*)
- }
- . = ALIGN(4K);
- etext = .;
- srodata = .;
- .rodata : {
- *(.rodata .rodata.*)
- *(.srodata .srodata.*)
- }
- . = ALIGN(4K);
- erodata = .;
- sdata = .;
- .data : {
- *(.data .data.*)
- *(.sdata .sdata.*)
- }
- . = ALIGN(4K);
- edata = .;
- sbss_with_stack = .;
- .bss : {
- *(.bss.stack)
- sbss = .;
- *(.bss .bss.*)
- *(.sbss .sbss.*)
- }
- . = ALIGN(4K);
- ebss = .;
- ekernel = .;
- /DISCARD/ : {
- *(.eh_frame)
- }
- }
复制代码
可以看到我们可以分配的内从从ekernel开始,而从哪里结束取决于我们的物理设备,这里因为学习的是K210,那么选择的实际上是8MiB的内存,也就是从0x80000000到0x80800000.
这里在子模块os\src\boards\qemu.rs里有:- // os/src/config.rs
- pub const MEMORY_END: usize = 0x80800000;
复制代码 那么我们在这里就是要实现一个页帧内存分配器来管理这段内存,首先我们先将初始地址和结束地址转换为页号:
实现代码:- // os\src\mm\frame_allocator.rs
- pub struct StackFrameAllocator {
- current: usize,
- end: usize,
- recycled: Vec<usize>,
- }
- impl StackFrameAllocator {
- pub fn init(&mut self, l: PhysPageNum, r: PhysPageNum) {
- self.current = l.0;
- self.end = r.0;
- }
- }
- impl FrameAllocator for StackFrameAllocator {
- fn new() -> Self {
- Self {
- current: 0,
- end: 0,
- recycled: Vec::new(),
- }
- }
- fn alloc(&mut self) -> Option<PhysPageNum> {
- if let Some(ppn) = self.recycled.pop() {
- Some(ppn.into())
- } else if self.current == self.end {
- None
- } else {
- self.current += 1;
- Some((self.current - 1).into())
- }
- }
- fn dealloc(&mut self, ppn: PhysPageNum) {
- let ppn = ppn.0;
- // validity check
- if ppn >= self.current || self.recycled.iter().any(|&v| v == ppn) {
- panic!("Frame ppn={:#x} has not been allocated!", ppn);
- }
- // recycle
- self.recycled.push(ppn);
- }
- }
复制代码这里注意Vec的成功使用少不了前一部分的堆内存分配器.
为了保证每个物理页帧在被创建的时候清空页帧内容,在被销毁的时候能够自动回收.这里借用了RAII的思想.
RAII(Resource Acquisition Is Initialization) 资源和对象的声明周期一致的一种思想.
创建的时候初始化,Drop的时候进行回调
使用DropTrait来进行管理
为PhysPageNum创建一个包裹Tracker意为追踪器:- /// manage a frame which has the same lifecycle as the tracker
- pub struct FrameTracker {
- pub ppn: PhysPageNum,
- }
- impl FrameTracker {
- pub fn new(ppn: PhysPageNum) -> Self {
- // page cleaning
- let bytes_array = ppn.get_bytes_array();
- for i in bytes_array {
- *i = 0;
- }
- Self { ppn }
- }
- }
- impl Debug for FrameTracker {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- f.write_fmt(format_args!("FrameTracker:PPN={:#x}", self.ppn.0))
- }
- }
- impl Drop for FrameTracker {
- fn drop(&mut self) {
- frame_dealloc(self.ppn);
- }
- }
复制代码这里要注意,要想直接访问物理地址,必须是使用M特权级.
但是我们使用的是S和U特权级,因此,需要一个恒等映射来解决这个问题.
这里恒等映射,其实是存在一个小问题的因为我们物理地址正常是56位,但是虚拟地址是39位,理论上会出问题.
但是我们的内存从[0x8000_0000,0x8080_0000)恰好弥补了这一点,一共就涉及32位嘛.
如下图所示,恒等映射就是把PPN和VPN用下一节提到的map映射起来,可以看到8MiB的映射需要约16KiB的页表项(忽略一级和二级页表损耗), 这里总结下来就是原本大小除以512 就是页表项的损耗.
注意这个L1 3Bit,只能有000,001,010,011四种状态,要注意是左闭右开的区间,4*4KiB=16KiB.
建立和拆除虚实地址映射关系
实际上就是建立: 虚拟地址 -> 物理地址的接口和映射.
实现方法:- type FrameAllocatorImpl = StackFrameAllocator;
- lazy_static! {
- /// frame allocator instance through lazy_static!
- pub static ref FRAME_ALLOCATOR: UPSafeCell<FrameAllocatorImpl> =
- unsafe { UPSafeCell::new(FrameAllocatorImpl::new()) };
- }
- /// initiate the frame allocator using `ekernel` and `MEMORY_END`
- pub fn init_frame_allocator() {
- unsafe extern "C" {
- safe fn ekernel();
- }
- FRAME_ALLOCATOR.exclusive_access().init(
- PhysAddr::from(ekernel as usize).ceil(),
- PhysAddr::from(MEMORY_END).floor(),
- );
- }
- /// allocate a frame
- pub fn frame_alloc() -> Option<FrameTracker> {
- FRAME_ALLOCATOR
- .exclusive_access()
- .alloc()
- .map(FrameTracker::new)
- }
- /// deallocate a frame
- fn frame_dealloc(ppn: PhysPageNum) {
- FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
- }
复制代码 对应的VPN转PPN的方法:- // os\src\mm\address.rs
- impl PhysPageNum {
- 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) }
- }
- 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) }
- }
- pub fn get_mut<T>(&self) -> &'static mut T {
- let pa: PhysAddr = (*self).into();
- unsafe { (pa.0 as *mut T).as_mut().unwrap() }
- }
- }
复制代码 同样地,如果只进行查找不进行创建,可以得到如下两个方法:- // os\src\mm\page_table.rs
- 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
- }
复制代码 总结
如果把kernel当作一个软件来看,基本的内存分配目前是如下图所示的:
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |