页与分页
复习
- 地址空间:每个进程都以为自己独占一片连续内存
- 地址重定位与内存保护:程序不知道实际装在哪里时怎样访问数据
- 分段:按代码、数据和栈划分内存,以及外部碎片从何而来
TL;DR
- 分页:把虚拟内存和物理内存都切成同样大小的块
- 虚拟内存切成的块叫页,物理内存切成的块叫页帧
- 页映射到页帧,任意一页可以放进任意一个页帧
- 分页消除了外部碎片,但可能留下少量内部碎片
正文
分段挺好,就是会留下外部碎片。问题的根源在于:段的长度不固定,大小不一,拼来拼去总会留下塞不下的空隙。
有没有办法让块的大小都一样,从而不挑地方?这就是分页(paging)的思路。
把两边都切成等大的块
分页的做法很干脆:把两边的内存都切成固定大小的块。
- 把虚拟内存切成等大的块,每块叫一个页(page)
- 把物理内存也切成同样大的块,每块叫一个页帧(frame)
页和页帧大小相同(比如都是 4 KB)。因为大小一致,任何一页都可以放进任何空闲的页帧——不再挑剔位置,也就没有了“空隙塞不下”的问题。
虚拟内存 物理内存
┌──────┐ ┌──────┐
│ 页 0 │──────────>│页帧 5│
├──────┤ ├──────┤
│ 页 1 │──────────>│页帧 2│
├──────┤ ├──────┤
│ 页 2 │──────────>│页帧 9│
└──────┘ └──────┘
地址也被切成两半
既然内存按页划分,地址自然也跟着切。一个虚拟地址,可以看成两部分:
- 高位是页号:说明这是第几页
- 低位是页内偏移:说明在这页里的第几个字节
虚拟地址 = [ 页号 ][ 页内偏移 ]
页号用来找“这一页被放到了哪个页帧”,页内偏移则原封不动地保留——因为一页之内的相对位置不会变。
好处与代价
分页的好处很直接:
- 没有外部碎片:块都一样大,随便哪块空着都能用
- 管理简单:物理内存被切成整齐的格子,分配和回收都方便
- 是虚拟内存的基础:正因为“页可以不连续地放”,后面才能把暂时不用的页挪到磁盘上
代价是内部碎片:进程最后一页往往填不满,那一页里没用到的部分就浪费了。不过每页顶多浪费不到一页的空间,比外部分段那些零散的大空隙划算得多。
那么,页号到底怎么变成页帧号?这就要靠一张“对照表”了。下一章,我们认识页表。
思考题
页大小选大一点或小一点,各有什么利弊?如果页太小,页的数量会变多,这会带来什么额外负担?
小结
知识点
- 分页:虚拟内存和物理内存都切成等大的块
- 页、页帧,以及“任意页可放任意页帧”
- 虚拟地址 = 页号 + 页内偏移
- 分页消除外部碎片,但可能有内部碎片
参考资料
- Wikipedia(zh):分页:paging
- Wikipedia(zh):页:页与页帧
思考题答案(仅供参考)
页越大,页的数量越少,页表越小、查表开销越低,但最后一页浪费的内部碎片也越多;页越小,内部碎片越少、更省内存,但页的数量猛增,页表会变得很大,地址转换和管理的负担都随之上升。所以页大小要在“省内存”和“少开销”之间取折中。现实中常见的 4 KB 就是这么权衡出来的。这也说明,内存管理里几乎每一步都是在做取舍。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪