文章目录
前言
我们都知道,CPU执行程序、处理数据都需要和内存打交道,这个打交道的方式那就是内存地址。本章则是探讨虚拟地址和物理地址之间的关系和转换机制,内存资源正是通过这些机制来管理。
一、虚拟地址与物理地址
1.1虚拟地址
虚拟地址,正如其名,这个地址是虚拟的,它只是逻辑上存在的一个数据值。
那么这个地址是谁产生的呢?那就是链接器。当开发软件经过编译步骤后,就需要链接成可执行文件才可以运行。而链接器的主要工作就是把多个代码模块组装在一起,并解决模块之间的引用,即处理程序代码间的地址引用,形成程序运行的静态内存空间视图。只不过这个地址是虚拟而统一的,而根据操作系统的不同,这个虚拟地址空间的定义也许不同,应用软件开发人员无需关心,由开发工具链给自动处理了。
虚拟内存实际上是存储器地址空间抽象的一种实现。从进程的角度看起来就好像自己独占了整个内存,链接器链接编译产物的时候只需要从0地址开始做变量和函数地址的替换,不需要关心自己是否会把其他进程的内存单元给污染。
1.2物理地址
物理地址,字面意思,这个地址是真实存在的,是内存中真正的地址。它在逻辑上只是一个数据,但这个数据可以被地址译码器等电子器件变成电子信号,这可以让地址总线查找到真正的存储单元。
1.3虚拟地址到物理地址的转换
明白了虚拟地址和物理地址后,我们发现如果想要程序正常执行,那就是必须要将虚拟地址转换成物理地址。
要实现转换,用软件方式太低效,用硬件方式没有灵活性,最终采用软硬件结合的方式实现,那就是MMU(内存管理单元)。MMU可以接受软件给出的地址对应关系数据,进行地址转换。
逻辑上的MMU工作原理框架图如下:
上图中展示了 MMU 通过地址关系转换表,将 0x80000~0x84000 的虚拟地址空间转换成 0x10000~0x14000 的物理地址空间,而地址关系转换表本身则是放物理内存中的。
如果是虚拟地址一一对应物理地址,那么物理地址就会很快用完。但如果采用虚拟段基址一一对应物理段基址,因为段长度各不相同,所以依然不可取。最终采取一个折中方案。即把虚拟地址空间和物理地址空间都分成同等大小的块,也称为页,按照虚拟页和物理页进行转换。根据软件配置不同,这个页的大小可以设置为 4KB、2MB、4MB、1GB,这样就进入了现代内存管理模式——分页模型。
分页模型框架如下图:
上图可看出,一个虚拟页可以对应到一个物理页,由于页大小一经配置就是固定的,所以在地址关系转换表中,只要存放虚拟页地址对应的物理页地址就行了。
二、MMU
2.1什么是MMU?
MMU即内存管理单元,是用硬件电路逻辑实现的一个地址转换器件,它负责接受虚拟地址和地址关系转换表,以及输出物理地址。
根据实现方式的不同,MMU 可以是独立的芯片,也可以是集成在其它芯片内部的,比如集成在 CPU 内部,x86、ARM 系列的 CPU 就是将 MMU 集成在 CPU 核心中的。
x86 CPU 要想开启 MMU,就必须先开启保护模式或者长模式,实模式下是不能开启 MMU 的。
由于保护模式的内存模型是分段模型,它并不适合于 MMU 的分页模型,所以我们要使用保护模式的平坦模式,这样就绕过了分段模型。
上图中,程序代码中的虚拟地址,经过 CPU 的分段机制产生了线性地址,平坦模式和长模式下线性地址和虚拟地址是相等的。
如果不开启MMU,在保护模式下可以关闭 MMU,这个线性地址就是物理地址。因为长模式下的分段弱化了地址空间的隔离,所以开启 MMU 是必须要做的,开启 MMU 才能访问内存地址空间。
2.2MMU工作流程
地址关系转换表,其实它有个更加专业的名字——页表。它描述了虚拟地址到物理地址的转换关系,也可以说是虚拟页到物理页的映射关系,所以称为页表。
为了增加灵活性和节约物理内存空间(因为页表是放在物理内存中的),所以页表中并不存放虚拟地址和物理地址的对应关系,只存放物理页面的地址,MMU 以虚拟地址为索引去查表返回物理页面地址,而且页表是分级的,总体分为三个部分:一个顶级页目录,多个中级页目录,最后才是页表,逻辑结构图如下:
MMU工作流程:第一个位段索引顶级页目录中一个项,该项指向一个中级页目录,然后用第二个位段去索引中级页目录中的一个项,该项指向一个页目录,再用第三个位段去索引页目录中的项,该项指向一个物理页地址,最后用第四个位段作该物理页内的偏移去访问物理内存。
简而言之,层层递进。虚拟地址有四块构成,第一块保存了下一个中间页目录地址,第二块保存了下一级的页表地址,第三块保存了物理页地址,第四块是偏移。
为了避免一次性加载大量页表到内存中,采用多级页表策略。一次只加载一级页表到内存中,并且可以选择淘汰上一级页表,通过多次映射的方法来避免大页表,这是时间换空间的策略。
另外注意:虚拟地址中的页内偏移才是决定页大小和多级目录的核心点,页内偏移必须与页大小保持一致,这样才能保证寻址可以找到页内的每一个地址。最后的页内偏移也就对应了我们所说的4KB/2MB/4MB页表。
三、保护模式下的分页
分页模式的灵活性、通用性、安全性,是现代操作系统内存管理的基石,更是事实上的标准内存管理模型,现代商用操作系统都必须以此为基础实现虚拟内存功能模块。
保护模式下的分页,保护模式下只有 32 位地址空间,最多 4GB-1 大小的空间。
32 位虚拟地址经过分段机制之后得到线性地址,又因为通常使用平坦模式,所以线性地址和虚拟地址是相同的。
保护模式下的分页大小通常有两种,一种是 4KB 大小的页,一种是 4MB 大小的页。分页大小的不同,会导致虚拟地址位段的分隔和页目录的层级不同,但虚拟页和物理页的大小始终是等同的。
3.1保护模式的4KB分页
32 位虚拟地址被分为三个位段:页目录索引、页表索引、页内偏移。只有一级页目录,其中包含 1024 个条目 ,每个条目指向一个页表,每个页表中有 1024 个条目。其中一个条目就指向一个物理页,每个物理页 4KB。这正好是 4GB 地址空间,如下图所示。
上图的CR3是CPU的一个 32 位的寄存器,MMU 就是根据这个寄存器找到页目录的。
当前分页模式下的 CR3、页目录项、页表项的格式:
前面说页目录1024个,页表1024个,然后1024个4KB物理页在,这样正好是4GB,页目录和页表是由一个4字节32位表示,那么1024个4字节又是4KB,所以这就是4KB分页。
地址始终是 4KB 对齐的,所以低 12 位才可以另作它用,形成了页面的相关属性,如是否存在、是否可读可写、是用户页还是内核页、是否已写入、是否已访问等。
3.2保护模式的4MB分页
32 位虚拟地址被分为两个位段:页表索引、页内偏移。只有一级页目录,其中包含 1024 个条目。其中一个条目指向一个物理页,每个物理页 4MB,正好为 4GB 地址空间,如下图所示。
CR3还是32位的寄存器,指向一个 4KB 大小的页表,这个页表依然要 4KB 地址对齐,其中包含 1024 个页表项,格式如下图:
4MB大小的页面下,页表项还是 4 字节 32 位,但只需要用高 10 位来保存物理页面的基地址就可以。因为每个物理页面都是 4MB,所以低 22 位始终为 0,为了兼容 4MB 页表项低 8 位和 4KB 页表项一样,只不过第 7 位变成了 PS 位,且必须为 1,而 PAT 位移到了 12 位。
四、长模式下的分页
如果开启了长模式,则必须同时开启分页模式,因为长模式弱化了分段模型,而分段模型也确实有很多不足,不适应现在操作系统和应用软件的发展。同时,长模式也扩展了 CPU 的位宽,使得 CPU 能使用 64 位的超大内存地址空间。所以,长模式下的虚拟地址必须等于线性地址且为 64 位。
长模式下的分页大小通常也有两种,4KB 大小的页和 2MB 大小的页。
4.1长模式的4KB分页
64 位虚拟地址被分为 6 个位段,分别是:保留位段,顶级页目录索引、页目录指针索引、页目录索引、页表索引、页内偏移。
顶级页目录、页目录指针、页目录、页表各占有 4KB 大小,其中各有 512 个条目,每个条目 8 字节 64 位大小,如下图所示:
上图的CR3已变成 64 位的 CPU 的寄存器,它指向一个顶级页目录,里面的顶级页目项指向页目录指针,依次类推。
注意:虚拟地址 48 到 63 这 16 位是根据第 47 位来决定的,47 位为 1,它们就为 1,反之为 0,这是因为 x86 CPU 并没有实现全 64 位的地址总线,而是只实现了 48 位,但是 CPU 的寄存器却是 64 位的。
长模式下的 4KB 分页下,由一个顶层目录、二级中间层目录和一层页表组成了 64 位地址翻译过程。
4.2长模式的2MB分页
64 位虚拟地址被分为 5 个位段 :保留位段、顶级页目录索引、页目录指针索引、页目录索引。
页内偏移,顶级页目录、页目录指针、页目录各占有 4KB 大小,其中各有 512 个条目,每个条目 8 字节 64 位大小。
长模式下 2MB 和 4KB 分页的区别是,2MB 分页下是页目录项直接指向了 2MB 大小的物理页面,放弃了页表项,然后把虚拟地址的低 21 位作为页内偏移,21 位正好索引 2MB 大小的地址空间。
2MB 分页模式下的 CR3、顶级页目录项、页目录指针项、页目录项的格式,格式如下图:
此模式下没有了页表项,取而代之的是,页目录项中直接存放了 2MB 物理页基地址。由于物理页始终 2MB 对齐,所以其地址的低 21 位为 0,用于存放页面属性位。
五、开启MMU
5.1开启MMU流程
要使用分页模式就必先开启 MMU,但是开启 MMU 的前提是 CPU 进入保护模式或者长模式。开启MMU的步骤如下:
1.使 CPU 进入保护模式或者长模式。
2.准备好页表数据,这包含顶级页目录,中间层页目录,页表,假定我们已经编写了代码,在物理内存中生成了这些数据。
3.把顶级页目录的物理内存地址赋值给 CR3 寄存器。
4.设置 CPU 的 CR0 的 PE 位为 1,这样就开启了 MMU。
5.2MMU地址转换失败后的操作流程
MMU 的主要功能是根据页表数据把虚拟地址转换成物理地址,但有没有可能转换失败?当然有可能,例如,页表项中的数据为空,用户程序访问了超级管理者的页面,向只读页面中写入数据。这些都会导致 MMU 地址转换失败。
MMU 地址转换失败了怎么办呢?失败了既不能放行,也不是 reset,MMU 执行的操作如下:
1.MMU 停止转换地址。
2.MMU 把转换失败的虚拟地址写入 CPU 的 CR2 寄存器。
3.MMU 触发 CPU 的 14 号中断,使 CPU 停止执行当前指令。
4.CPU 开始执行 14 号中断的处理代码,代码会检查原因,处理好页表数据返回。
5.CPU 中断返回继续执行 MMU 地址转换失败时的指令。
5.3操作系统对应用程序的地址空间进行隔离
在分页模式下,操作系统是如何对应用程序的地址空间进行隔离的?
之前提到,实模式下多个任务共享所有地址空间太危险,因此才有了保护模式,保护模式下的分页模式是一个巨大的创新。
对于每个进程而言,它会误认为(被操作系统欺骗)自己独有所有地址空间,因此它访问地址是不会考虑任何问题的,可是这个地址是虚拟地址,每个程序对应的CR3内容不同,进而映射也不相同,同样的虚拟地址被映射到不同的物理地址,待被MMU翻译后会得到对应的页表,而这个页表由操作系统管理,不同的进程拥有不同的页表,也因此产生了进程地址空间隔离,但是多个进程也是可以共享某个页表,这也是进程通信(IPC)的根本手段。
参考资料
以上内容是我学习彭东老师的《操作系统实战45讲》后所进行的一个笔记记录,如有错误,还请各位大佬多多指教。
我主要参考了以下资料,十分感谢:
操作系统实战45讲——彭东老师