#### 物理内存管理

在`memlayout.h`可以看到物理内存的布局

> 0~0x80000000：mmio
>
> 0x80000000~end：内核代码区
>
> end~PHYSTOP：空闲内存区

`kmem.freelist`是一个栈，采用链表存储，用于记录空闲页的地址。

`kfree()`是入栈操作，用头插法把空闲的物理页插入到`kmem.freelist`。

`kalloc()`是出栈操作，删除`kmem.freelist`的头部的元素。

`freerange()`是把从`end`到`PHYSTOP`之间的物理内存以页的形式插入到`kmem.freelist`。其中，`end`定义在`kernel.ld`，是内核的尾部。

#### 物理内存映射到虚拟内存

Sv39的三级映射是一棵三层的树，树根为satp寄存器的值，每个结点都是4k大小的一个页。

树根结点是一个页，这个页的地址放在`satp`寄存器里，树根结点最多可以包含512个子结点(PTE)，通过虚拟地址的38~30位索引。第二级结点，通过虚拟地址的29~21索引。第三级为叶子结点，通过虚拟地址的20~12位索引。

每个PTE占用64位，其中53~10位是PPN，是下一级的物理页号。其中9~0是flag。虚拟地址所对应的物理地址即为第三级的PPN加上虚拟地址的11~0位。

`walk()`查找虚拟地址对应的PTE的地址。就是对树进行按位查找。

`freewalk()`就是对树进行按值查找，其遍历方式为先根遍历。

`mappages()`就是修改叶子结点的值。

`copyout()`把内核空间的数据复制到用户空间。

`copyin()`把用户空间的数据复制到内核空间。

#### 虚拟内存之内核空间

在`memlayout.h`可以看到内核空间的布局。内核的虚拟内存范围是0~MAXVA，其中0~PHYSTOP部分和物理内存是直接相等的。但是有两处被映射到了高地址：一是trampoline页，还被映射在了顶部，是内核空间和用户空间的跳板；二是内核栈，在trampoline页的下面，内核栈是有间隔的，目的是为了防止溢出。

`kvminit()`用于建立内核空间到物理地址之间的映射。

`kvminithart()`使`kvminit()`建立的映射关系生效。

#### 虚拟内存之用户空间

在`memlayout.h`可以看到用户空间的布局。用户的虚拟内存范围是0~MAXVA，从0开始依次是代码区、数据区、栈区、堆区。高地址也有两个东西：一是trampoline页，也在顶部，起跳板的作用；二是trapframe页，保存的是`p->tf`的内容，trampoline页会用到它。