页表
复习
- 地址重定位与内存保护:程序不知道实际装在哪里时怎样访问数据
- 分段:按代码、数据和栈划分内存,以及外部碎片从何而来
- 页与分页:把虚拟内存和物理内存都切成固定大小的块
TL;DR
- 页表(page table)记录“虚拟页对应哪个物理页帧”
- 它由内核维护,每个进程一份
- 页表项里还有有效位、权限位等信息
- 页表本身可能很大,这是后面要解决的新问题
正文
分页把内存切成整齐的页,可页往往不连续地散落在物理内存各处。于是需要一个“对照表”,记录每一页到底被放到了哪个页帧。这张表就是页表(page table)。
一张对照表
页表可以想成一个数组:用虚拟页号当下标,表项里存的就是对应的物理页帧号。
虚拟页号 → 页表项
0 → 页帧 5
1 → 页帧 2
2 → 页帧 9
...
地址转换时,硬件拿虚拟地址里的页号去查这张表,得到页帧号,再拼上原来的页内偏移,就得到了物理地址。
每个进程都有自己的一份页表。进程 A 和进程 B 可以拥有相同的虚拟页号,却指向不同的页帧——这正是“虚拟地址相同、物理地址不同”的实现方式。
页表项不只有页帧号
页表项里存的,其实不止“页帧号”。常见的还有:
- 有效位:这一页当前是否在内存里
- 保护位:这一页可读、可写还是可执行
- 访问位、脏位等:供置换和缓存管理使用(后面会讲)
有效位尤其重要:如果某一页暂时不在内存里(被挪到了磁盘),有效位就是 0,访问它会触发一次“缺页”,由内核把它调回来。
一个绕不开的问题:页表太大
页表好用,但很快会撞上规模问题。
设想一个 32 位地址空间、页大小 4 KB:页内偏移占 12 位,剩下 20 位是页号,共有 (2^{20})(约一百万个)页。每个页表项哪怕只占几字节,整个页表也要几 MB。而且每个进程都要一份——几十上百个进程,光页表就吃掉大量内存。
更糟的是,页表还要求连续存放、随时可查,这对内存布局也很不友好。
显然,不能让页表这样膨胀下去。怎么在“查得动”和“存得下”之间找平衡?下一章先看单级页表下地址是怎么转换的,之后再引入“多级页表”来瘦身。
思考题
页表为什么必须“每个进程一份”,而不能所有进程共用一张?如果共用,会发生什么?
小结
知识点
- 页表记录虚拟页到物理页帧的映射
- 每个进程一份页表,用来实现进程间隔离
- 页表项含页帧号、有效位、权限位等
- 页表规模可能很大,是新的难题
参考资料
- Wikipedia(zh):页表:page table
- Wikipedia(zh):虚拟内存:虚拟内存
思考题答案(仅供参考)
因为页表承载的正是“这个进程的虚拟地址究竟映射到哪些物理页”这份私有信息。如果所有进程共用一张,那么同一个虚拟页号就只能对应同一个物理页帧,进程之间再也无法拥有各自独立的内存——A 改的数据 B 直接就能看到,隔离荡然无存。所以,页表必须每个进程一份,它既是地址转换的依据,也是进程隔离的基石。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪