2025/4/8:之前 2023 年考研做的笔记,一直没写完没发出来,在没有大模型的那些年我写博客真是有点晦涩,但每句话都是我亲自写的都舍不得改。现在时隔两年,我自己都快看不懂了,赶紧琢磨一下发出来。
问题背景
操作系统讲到分页机制的时候,实在是一场虚拟地址、逻辑地址、物理地址、虚拟存储器、内存、外存、一级页表、二级页表、页、页表、页表项、物理块号(又称页框号)等概念的混合大乱斗。
我自己学的时候从来没捋清楚哪个概念是哪个概念,直到碰到了别人问我这个问题:为什么页表项的大小由逻辑地址的位数决定?
我一时间懵了,这两个东西有什么绝对的关联吗?
经过我一番仔细查证,其实“页表项的大小由逻辑地址的位数决定”这句话,不完全对,只是考研408为了好做题强行植入的理念。
文章目录
1 什么是页表项
在此仅以最简单的单级页表的设计为例。
顾名思义,页表项就是页表这张表里的每一项。页表项的大小取决于页表项里要存储的内容。页表里存储的内容是物理块号+一些控制信息,页表项的索引代表了页号。所以页表项的结构可以看成下图这样:
上图中所说的“控制”、“未用”、“弃用”、“可用”只是个示意,不同的操作系统会有不同的设计,例如 Linux 的页表的页表项的具体内容如下,可以看到有 3 位未使用,实际需要的控制位只有 9 位:
图源:为什么各级页表的大小不超过一个页面大小啊?_bin 的技术小屋的回答。这个回答整体比较晦涩难懂。
2 页表项的内容用来干嘛
上面说了页表项的具体内容是物理块号和控制信息,并且其顺序代表页号。页表项里的内容,是用来计算物理地址的。物理地址的定义是物理内存中的地址。
在此特别强调是内存中的,是因为有一部分人(比如我)容易将物理地址误认为是外存(磁盘上的)的地址,实际上内存条也是物理的。
计算方式是,物理(内存)地址 = 物理块号*页的大小+偏移地址。
接下来分别解释一下“如何查找物理块号”、“什么是页的大小”、“什么是偏移地址”这三个问题。
2.1 如何查找物理块号
假设查找过程是个函数叫做 SearchBlock,这个函数必定有参数即被查找的对象,而该参数取决于该函数的使用场景。而这个函数在程序对某个地址进行访问的时候被调用,于是被查找的就是程序需要访问的地址。众所周知,每个程序进程为了不依赖硬件地址,它们会建立虚拟空间,访问的都是自己虚拟出来的地址,简称虚拟地址,对于程序本身来说又称逻辑地址【1】。所以,该函数应该写成SearchBlock(逻辑地址)。
【1】“虚拟地址到底是不是逻辑地址”:我认为虚拟地址是相对于进程而言的概念,逻辑地址是相对于程序编写逻辑而言的概念,而在程序本身只对应一个进程的时候,我们可以简单地将二者等同。如果还有疑问,可以参考虚拟地址就是逻辑地址吗?_CSDN 问答。
查找过程如下:
页号 = 逻辑地址 / 页的大小
页表项 = 页表[页号] # 意思是通过页号查找页表找到对应的页表项
物理块号 = 页表项.物理块号 # 意思是物理块号是页表项的一部分,从页表项中得到物理块号
2.2 什么是页的大小
为理解页的大小,首先得清楚,页≠页表。
更直观地,可以把页表和页看成下图:
可以从上图中看出来以下几点(与常见的误区一一对应):
- 每个进程都有自己的虚拟页,并且有一个自己的页表。(虽然页表是用来做虚拟地址到物理地址的转换的,但是它的个数其实等于进程数量而不是物理内存条数量呀)
- 页表并不在虚拟内存空间中。(页表是不可能在虚拟内存空间中的。可以反证得知,假设页表存在于逻辑地址从 0~页表大小这部分,这一块怎么被访问呢?若期望访问 0x0 地址上的值,则需要查找页表第 0 项的具体内容;而查找页表的内容,却又需要先能够得到 0x0 这一项的值。妥妥死锁)
拓展:
问:页表到底存在哪?
答:物理内存或磁盘里(不过最高级的页表一定在内存里,只有一二三级可能会被调到磁盘去)。
问:页表不在虚拟内存中那它怎么被进程访问?
答:往往会设计一个页表基址寄存器,比如 x86/x64 的这个寄存器叫做 CR3。该寄存器存储了当前进程的页表基址。进程调度的时候会把进程的页表的基址调入该寄存器,当前进程直接使用这个寄存器的值即可。
在 x86 系统中,页表基址是固定的,位于 0xC0000000;而在 x64 系统中,页表基址不再是固定的值,而是每次系统启动后随机生成的。
参考:
言归正传,页的大小是操作系统自己决定的,操作系统会把物理内存划分成一页一页的(比如 4K/页),方便使用。页的大小往往会大于一个地址单元的长度,比如 Linux 每一页是 4K,不过 Linux 也提供了页大小为 2M 和 1GB 的选项。
2.3 什么是偏移地址
偏移地址就是要访问的这个地址在页里面相对于页基址偏移的地址。
偏移地址 = 逻辑地址 % 页的大小
3 页表项大小是多少
终于到了本文的标题也是核心内容——页表项大小!
在许多 408 教程或计算题中,往往会莫名其妙地一句话带过,说,页表项大小在通用操作系统中往往由逻辑地址的位数决定,比如 32 位程序的页表项大小就是 32 比特即 4 个字节,64 位程序的就是 64 比特即 8 个字节。总之就是说,页表项大小和一个地址单元等大。
页表项大小 = 逻辑地址位宽
退退退!页表项大小并不是什么时候都与地址单元长度等大的!
只是在考研出题的时候,通常是考虑最简单最单纯的情况那就是 32 位系统里算一级页表的时候,再复杂点整出了二级页表用 32 位表示也够用,所以市面上有 408 教程高深莫测地说“页表项的大小就是等于逻辑地址的位数”,啊对对对,通用 32 位操作系统的页表项的大小往往确实就是 32 位,64 位操作系统的最高级页表的页表项往往是 64 位,恰巧确实就是与逻辑地址的长度吻合。
但是,既然有缘点进来这篇文章,那就一起来抛开考研那点小惯性思维,一起来看下页表项大小的设计思路。
页表,究其根本还是人为编程实现的,经过几十年的发展,它的大小理应是最优的。如果页表项的大小是有意义的,并且是最优的,那就表明,页表项的大小如果是别的值,那么就一定会更差。
那么只需要考虑如果页表是其他的值,到底坏在哪里,就足够了。
3.1 为什么不 “页表项大小 > 地址单元的长度”?
因为:
- 单页的大小 >> 单个地址单元的大小(比如页大小为 4K,地址单元 32 位宽即 4B)
物理块的总数 = 物理内存总大小 / 页的大小
(比如物理内存一共 4GB,页大小是 4K,则物理块号的数量就只有 4G/4K=1M)- 单个进程的虚拟空间 >= 物理空间总量
虚拟空间的大小 = 2^地址单元长度的位数
(比如地址单元长 32 位,能表示的虚拟空间大小就是 2^32 即 4GB)
所以:
- 物理块号的数量 << 物理内存总大小(比如物理内存总大小为 4G、页大小为 4K,则物理块号的数量就只有 4G/4K=1M,即 2 的 20 次方比特,只需用 20 位即可表示)
- 一个地址单元即可表示所有物理块号(比如 32 位程序,其虚拟空间是 4GB,则物理内存最多是 4GB、页大小为 4K 的时候,物理块号只需要 20 位表示,比 32 位少得多)
因此,页表项大小 没有必要大于 地址单元的长度。同理,64 位程序中页表程序就更没必要大于地址单元的长度了。
拓展:
看到这里,肯定有人会提出质疑:
为什么单个进程的虚拟空间 >= 物理空间总量?如果我的物理内存有 32G,阁下的 32 位程序将如何应对?
答:这个就是“先有鸡还是先有蛋”的问题了。如果是先拿到 32G 的物理内存,并要求用 32 位程序去运行它,那么为了充分利用物理内存,可以将每页的大小设置为 2M,这样物理块号就只需要14位即可表示。(2^(30+5)) / (2^21) = 2^14,30+5 意味着 32G=2^5*2^30B,同理 21 意味着 2M。
但这种页的大小的设置,需要在操作系统底层做定制化的修改呢,不然剩下的部分识别不到的。所以,windows 32 位和 linux 32 位系统的默认情况设定的就是能够识别的最大的物理内存是 4G,不建议用于大于 4G 物理内存的机器,浪费内存。
3.2 为什么不 “页表项大小 < 地址单元的长度”?
有几个方面的原因:
-
字节对齐。程序每次访问地址都是按地址单元的长度来访问的;如果页表项大小是 7,那访问一项的时候可能需要同时访问两个地址才能拼接得到结果。所以为了字节对齐,页表项就算是小,也最好是能够小成逻辑地址长度的公约数。
-
32 位系统中的逻辑地址长度的公约数不够用,64 位系统的逻辑地址长度的公约数对一级页表够用、但是设计成多级页表之后就不够用了。
- 现在通用计算机里最常用的是 32 位和 64 位。
- 32 位中,32 位的最大公约数(除了自己)是 16,如果控制位的位数不变(还是 12 位-3 位空白位=9 位),那么只剩 7 位地址,假设内存不变还是保持 4G,那么一页需要达到 4G/(2^7) = 32M,这 4 位物理块号才够用;如果取页表项长度为 8 更是直接无法包含所有的控制位。
- 64 位中,同样需要调整物理块号位数,这里的物理块号通常是 36 位或 52 位,内存空间的上限很大。可以明显看出,在 64 位系统中,如果电脑本来就只有 4G~32G 的内存,那么页表项完全可以设计得小一点,比如设计为 32 位那么大,控制位 9 位、物理块号 20 到 23 位。可以看出来,64 位操作系统里其实是非常够用的。那反正大部分人也没有内存超过 32G 的电脑(笔者注:由于本文成文时间在2023年,当时32G内存确实是大部分人都没有,勿喷),为啥 64 位系统的页表项不设计成 32 位呢,而是设计成64位呢?因为!其实!其实离开了考研 408 那些数字的计算,真实的计算机系统并不是只使用一级页表呀!!!在 32 位系统中,通常使用 2 级页表;在 64 位系统中,通常使用 4 级页表或 5 级页表来管理虚拟内存。
-
多级页表为什么需要更大的页表项?
2023年的我在这个问题里写了TODO,没写完,2025年的我已经不记得当年的我要写什么了,这一部分回答由AI生成主旨、我简单总结,大家凑合看。
多级页表需要更大的页表项(如64位而非32位),主要是因为:
- 存储更长的物理地址(支持超过32G的大内存,着眼未来);
- 多级页表需要记录多级结构的控制信息(如权限标志、层级跳转),所以如果设计成32位,并不是全部的32位都能用来存储程序的信息,所以连32G的内存都兼容不到;举个例子,页的大小可能区别很大,多级页表需同时支持不同粒度的页面(如4KB小页和1GB大页)。大页映射需要在较高层级的页表项(如PDPT或PD)中直接存储大页的物理地址,并添加特殊标志位(如PS位指示大页),这会增加页表项的复杂度,也会导致不是所有的位都用于存储程序信息,需要一些冗余位很正常。
- 满足对齐需求:现代CPU要求页表项按特定字节对齐(如8字节对齐),以优化内存访问效率。若物理地址和控制位总长度不足对齐要求,需填充冗余比特。
- 满足硬件优化需求:更大的页表项可容纳更多信息(如地址空间标识符ASID、缓存策略位),帮助硬件更高效地管理TLB(Translation Lookaside Buffer)。
“历史上,X86 曾经历过两次扩展,从 8 位到 16 位,从 16 位到 32 位。以技术来说,将 X86 扩展到 64 位并不困难,生产成本也不会出现大幅度的提高。实际上,作为 X86 技术的领导者 AMD 公司在 2000 年便公布了 X86-64 计划,而第一款 X86-64 处理器即将在 2003 年面市。二年内,X86-64 处理器将出现在从笔记本到台式机、服务器的全部产品中。几年之后,我们将只能在嵌入式的产品中找到 32 位处理器的身影。”——2003 年中国青年报《64 位计算时代滚滚而来》
“在 Intel 开发手册中,将这四级页表分别称为 PML4E、PDPTE、PDE、PTE;但微软的命名方式略有不同,将这四级页表分别称为 PXE、PPE、PDE、PTE。”
虚拟地址转换[三] - 多级页表
3.3 为什么不 “页表项大小 = f(x)” ?
f(x)指的是函数,意思是既然调整页表项大小能够有效利用所有的内存,为什么页表项大小不直接设置成因内存大小变化而变化的函数运算结果?
- 通用操作系统主打的是“通用”,如果你接触专用的嵌入式设备固件多了,就会发现好多设备别说页表了,它都没有文件系统,这种f(x)的现象比比皆是,所以并不是没有这样设计的人,只是这样设计不通用;
- 通用系统最好能做到硬件透明,意思是与硬件架构无关;
- 如果要这样,每个程序进程都有一个页表,每个程序进程在启动时都得加一个“获取物理机状态”的步骤,蛮冗余的。
4 拓展:什么情况下,页表项大小可以不由逻辑地址位数决定
页表项大小不一定要由逻辑地址位数决定,而是由系统设计和需求来确定。在操作系统和计算机体系结构中,页表项大小通常是根据以下因素来选择的:
-
物理内存大小:页表项的大小通常受到物理内存大小的限制。如果系统的物理内存较小,那么页表项可以较小,以节省内存空间。(所以嵌入式软件的设计中如果没钱上内存,可以把页表项整小点!)
-
页大小:页表项的大小通常与页的大小相关。页的大小是由系统决定的,通常是 2 的幂次方,如 4KB、8KB 或 16KB 等。页表项的大小需要足够大以存储每个页的相关信息,例如物理地址、权限位等。
-
性能需求:页表项的大小也可以根据系统的性能需求来确定。较大的页表项可能会减少页表的访问次数(主要是因为较大的页表项意味着不需要搞太多级页表,多级页表会增加访问次数),但会增加内存占用(所以有钱买内存而且特讨厌多级页表的话可以把页表项整大点!)。较小的页表项可能会减少内存占用,但可能需要更多的访问来查找页的信息(所以没钱买内存可以把页表项整小点!)。
-
地址转换方案:某些计算机体系结构可能采用不同的地址转换方案,例如分段分页或多级页表。这些方案可能需要不同大小的页表项来适应不同的需求(所以前面说整小一点,优化版实现方案就是多级页表,使用了多级页表之后页表项可以整小点,还省内存,还利用了程序的局部性,反正超多优点!)。
总之,页表项大小是一个根据系统设计和性能需求来确定的参数,不一定受到逻辑地址位数的限制。系统设计师需要权衡内存占用和性能来选择适当的页表项大小。
例如,常见的页表项大小不等于逻辑地址位数的案例就是二级页表,比如 64 位的系统的 64 位程序,其第一级页表是 8 字节(也有 4 字节的)与地址单元大小相等,而第二级往往是 4 字节的。
拓展:多级页表的设计思路(页表问题也就是著名的数组问题和数组里寻找数组问题)内核杂谈——关于页表项大小的问题以及坑比的页表问题。
末(吐槽):这真是我写过的最难写的一篇博客了。
本账号所有文章均为原创,欢迎转载,请注明文章出处:https://shandianchengzi.blog.csdn.net/article/details/132752251。百度和各类采集站皆不可信,搜索请谨慎鉴别。技术类文章一般都有时效性,本人习惯不定期对自己的博文进行修正和更新,因此请访问出处以查看本文的最新版本。