Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

页与分页

复习

  • 地址空间:每个进程都以为自己独占一片连续内存
  • 地址重定位与内存保护:程序不知道实际装在哪里时怎样访问数据
  • 分段:按代码、数据和栈划分内存,以及外部碎片从何而来

TL;DR

  • 分页:把虚拟内存和物理内存都切成同样大小的块
  • 虚拟内存切成的块叫,物理内存切成的块叫页帧
  • 页映射到页帧,任意一页可以放进任意一个页帧
  • 分页消除了外部碎片,但可能留下少量内部碎片

正文

  分段挺好,就是会留下外部碎片。问题的根源在于:段的长度不固定,大小不一,拼来拼去总会留下塞不下的空隙。

  有没有办法让块的大小都一样,从而不挑地方?这就是分页(paging)的思路。

把两边都切成等大的块

  分页的做法很干脆:把两边的内存都切成固定大小的块。

  • 虚拟内存切成等大的块,每块叫一个(page)
  • 物理内存也切成同样大的块,每块叫一个页帧(frame)

  页和页帧大小相同(比如都是 4 KB)。因为大小一致,任何一页都可以放进任何空闲的页帧——不再挑剔位置,也就没有了“空隙塞不下”的问题。

虚拟内存            物理内存
┌──────┐           ┌──────┐
│ 页 0 │──────────>│页帧 5│
├──────┤           ├──────┤
│ 页 1 │──────────>│页帧 2│
├──────┤           ├──────┤
│ 页 2 │──────────>│页帧 9│
└──────┘           └──────┘

地址也被切成两半

  既然内存按页划分,地址自然也跟着切。一个虚拟地址,可以看成两部分:

  • 高位是页号:说明这是第几页
  • 低位是页内偏移:说明在这页里的第几个字节
虚拟地址 = [ 页号 ][ 页内偏移 ]

  页号用来找“这一页被放到了哪个页帧”,页内偏移则原封不动地保留——因为一页之内的相对位置不会变。

好处与代价

  分页的好处很直接:

  • 没有外部碎片:块都一样大,随便哪块空着都能用
  • 管理简单:物理内存被切成整齐的格子,分配和回收都方便
  • 是虚拟内存的基础:正因为“页可以不连续地放”,后面才能把暂时不用的页挪到磁盘上

  代价是内部碎片:进程最后一页往往填不满,那一页里没用到的部分就浪费了。不过每页顶多浪费不到一页的空间,比外部分段那些零散的大空隙划算得多。

  那么,页号到底怎么变成页帧号?这就要靠一张“对照表”了。下一章,我们认识页表

思考题

  页大小选大一点或小一点,各有什么利弊?如果页太小,页的数量会变多,这会带来什么额外负担?

小结

知识点

  • 分页:虚拟内存和物理内存都切成等大的块
  • 页、页帧,以及“任意页可放任意页帧”
  • 虚拟地址 = 页号 + 页内偏移
  • 分页消除外部碎片,但可能有内部碎片

参考资料

  1. Wikipedia(zh):分页:paging
  2. Wikipedia(zh):页:页与页帧

思考题答案(仅供参考)

  页越大,页的数量越少,页表越小、查表开销越低,但最后一页浪费的内部碎片也越多;页越小,内部碎片越少、更省内存,但页的数量猛增,页表会变得很大,地址转换和管理的负担都随之上升。所以页大小要在“省内存”和“少开销”之间取折中。现实中常见的 4 KB 就是这么权衡出来的。这也说明,内存管理里几乎每一步都是在做取舍。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪