需要实现

  1. 初始化堆
  2. malloc/calloc/free
    ..好像也没什么

rCoreBook里直接用了现成的堆管理器buddy_system_allocator, 不能自己跟着写一个了.
等有空(基本没空)的时候看看怎么实现一个堆管理器罢
看了Elegy的博客 大概是说malloc/calloc什么的也是要经过堆管理器的.

地址空间

  • MMU
  • PTE
  • TLB

MMU: 内存管理单元
CPU取指令取地址之前, 地址经过MMU从虚拟地址转为物理地址

PTE: 页表项
标志位 + RSW(?) + ppn + 保留位

TLB: 快表.
缓存一部分映射,从而加快(跳过)三级映射的过程

分段内存管理

将程序所占的内存空间分配到物理内存中
或者更细粒度地,将程序分成多个段,不定顺序地分配到物理内存中.(复杂度很高)
这种做法没有内碎片,有外碎片

分页内存管理

上图更直观
分页内存管理
尽量简单地来说:
程序和物理内存都有n个大小相同的段,并且有各自的id
mmu建立起两种id之间的对应关系.
cpu通过虚拟地址访问时, 虚拟地址的前半段为虚拟页号,会被转为物理页号.
其后半段是偏移量,这个不用转换.
页表内容也是存到内存中的一个页表里的!

riscv下如何管理内存

这里使用sv39, 即ppn有39位

satp: 一个64位csr寄存器^[状态和控制寄存器,比如mstatus表示在哪个态, mcause表示异常或中断的原因等.].
satp字段分布
PPN:根页表所在的物理页号.
设置satp来启用分页模式.

vpn: 27, ppn: 44

多级页表

线性表: 一对一映射,有很大一部分空间被浪费(因为未被使用)

引入多级页表(比如3级):查找的工作细分到最后一级页表.
一级页表的ppn占9位,2^9=512,可表示512个次级页表项.
一个页表项8B,则一个页表4KiB.


如何实现多级页表?

先实现一个页帧分配器.
实现:new, alloc, dealloc(free).

1
2
3
4
5
pub struct StackFrameAllocator {
current: usize, // [current, end)内的物理页号从未被分配过, cur end表示物理页号
end: usize,
recycled: Vec<usize>, // lifo, 保存被回收的物理页号
}

分配时,若recycle中没有已经被回收的页,则尝试分配新的页.
回收时,若页号合法,将回收的页号push到recycle中.

封装一个页帧类型FrameTracker,并为其实现new和Drop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub struct FrameTracker {
pub ppn: PhysPageNum,
}

impl FrameTracker {
pub fn new(ppn: PhysPageNum) -> Self {
let bytes_array = ppn.get_bytes_array();
// 重置页帧
for i in bytes_array {
*i = 0;
}
Self { ppn }
}
}

impl Drop for FrameTracker {
fn drop(&mut self) {
frame_dealloc(self.ppn);
}
}

然后考虑页表的实现:

1
2
3
4
5
6
pub struct PageTable {
// 根节点的物理页号
root_ppn: PhysPageNum,
// 页表的所有节点,同时也绑定了页帧的生命周期,页帧会随PageTable回收而回收.
frames: vec::Vec<FrameTracker>,
}

每个页表都存储了其对应的页帧(包括它本身. 前面提到了页表本身的内容也是存到它自身指代的某个页帧里的,这里是放到frames[0]了.)

map/unmap VPN/PPN

也就是绑定和解绑vpn到ppn的映射.
建立时,查找每级页表中有没有对应下一级的vpn页表,没有就创建. 然后将物理地址放到第三级页表中.
解绑就是清空第三级页表的对应物理地址.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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以将一个pte放到页帧中
*pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
}

// 解绑
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();
}