复习一下 OS
1、每个程序需要独立的虚拟地址
2、虚拟地址不能一个字节一个字节的管理
一个虚拟内存字节,对应一个物理内存字节,你得建立多少个映射?
所以要按照“块”来管理,这个块就是“页”
虚拟页号 (VPN) 和 物理页号 (FPN) 之间,建立映射
虚拟地址 = 虚拟页号 (VPN) + 页内偏移 (offset)
Linux 一般是 4KB,所以页内偏移是 12 位
物理地址 = 物理页号 PFN + 页内偏移 offset
3、页表、页表项
页表,就是那个记录虚拟页号-->物理页号的 hashmap
一般的 hashmap,它是键值对。但是页表的每一个键值对,起了一个名字,叫做“页表项 (PTE)”
因为它的值,不只是记录物理页号,还会记录 状态和权限。
4、缺页异常 Page Fault
第一种:访问的虚拟地址没有映射。访问 NULL,因为 0 不是 hashmap 的键,所以直接死
第二种:页暂时不在内存里,被 swap 出去了。是因为查到了,但是值里的状态不对。
5、多级页表
这是实际 Linux 的设计架构
我们前面说,它是一个 hashmap,这其实是不对的
因为 hashmap/map 的速度太慢(计算 hash,找 bucket,处理冲突,开放寻址探测等等,不然就是红黑树,都慢)
它这里需要一个,访问次数固定的结构,好预测,所以是数组
如果是数组,那每个进程,都要有一个从 0 一直到 2^页的总个数 的大数组,这太大了
所以多级页表就是 把一个巨大数组切成树状的、按需分配的小数组(硬件友好的稀疏数组)
它是把,页面总个数,拆分成高位,中位,低位,然后 key 的每一段 bit 直接就是位置
所以多级页表确实更慢了,4 级需要读 4 次
稀疏数组确实更好,但是应用层用 hashmap/map,是因为允许比较即可,比如字符串。有时还需要遍历等等。而这个要求键必须固定长度 int。
6、页表缓存 TLB
每次都逐级在多级页表里查,太慢了
而是 CPU 里有一个硬件缓存,记录之前那个 map 的一部分内容
也是解决多级指针连跳的问题
7、每个进程都有自己的页表
不同进程的页表不同,所以相同虚拟地址可以映射到不同物理页
struct mm_struct
页表根指针 虚拟内存区域 VMA 代码段、堆、栈、mmap 区域等信息