[rCore学习笔记 032] 管理SV39多级页表
上一节:硬件本节:软件实现
物理页帧管理
内核->空闲内存->物理页帧->分配->存放
可用物理页的分配和回收
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 {
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 {
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当作一个软件来看,基本的内存分配目前是如下图所示的:
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]