锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

程序中的地址如何转换?

时间:2023-06-08 07:07:00 1720fa集成电路

1.写在前面

本博客参考操作系统实战 45 讲》

我们介绍了上一个博客CPU本博客介绍了下一个程序中如何转换地址的三种模式?

CPU 执行程序和处理数据应与内存打交道。这种处理方式是内存地址。

内存芯片需要先告知读取指令和读写数据:hi,请把内存老哥拿走 0x10000 地址处的数据交给我……hi,内存兄弟,我已经计算好了,请让我把结果写回来 0x200000 地址空间。这些地址存在于代码指令字段后的常数或寄存器中。

在这里插入图片描述

今天,我们将专门研究程序中的地址。说到程序中的地址,我不知道你是否好奇,为什么系统设计师要引入虚拟地址?

我会带你从一个多程序并发的场景中热身,思考会导致什么问题,为什么这些问题可以通过虚拟地址来解决。

搞懂原理之后,我还会带你一起探索虚拟地址与物理地址的关系及转换机制。在下一节课中,你会发现我们最宝贵的内存资源是通过这些机制管理的。

2.从多程序并发场景开始

想象一下,如果计算机行一个程序 A,这种方法正好用在前面 CPU 的实模式因为程序 A 链接时可以确定地址,如内存地址 0x8000 开始,每个操作程序 A 都装入内存 0x8000 没有其他程序干扰,地址开始运行。

现在改变一下,在内存中放另一个程序 B,程序 A 和程序 B 每个人运行一秒钟,这样一个循环,直到其中一个结束。这个新场景会出现一些问题。当然,我们只关心这些与内存相关的核心问题。

  1. 谁来保证程序? A 跟程序 B 没有内存地址的冲突?换句话说,程序 A、B 内存地址是什么,这个问题是由的 A、B 操作系统仍然决定程序协商。
  2. 如何保证程序 A 跟程序 B 不会互相读写自己的内存空间?这个问题比较简单,可以通过保护模式来解决。
  3. 如何解决内存容量问题? A 和程序 B,程序代码在不断开发迭代中占用的空间会越来越大,导致内存无法安装。
  4. 如果不仅仅是程序,还要考虑扩展后的复杂情况 A、B,也可能有程序 C、D、E、F、G……它们由不同的公司开发,每台计算机的内存容量不同。此时,它对我们的内存计划有什么影响?

要完美解决上述核心问题 4 一个问题,一个更好的解决方案是让所有程序都享受自己 0 开始到最大地址空间,这个地址空间是独立的,是该程序私有的,其他程序既看不或访问地址空间与其它程序无关,和具体的计算机也无关

事实上,计算机科学家早就这么做了,这个方案就是虚拟地址,下面我们来看看。

3.虚拟地址

就像它的名字一样,这个地址是虚拟的,自然地与特定的环境解耦,包括系统软件环境和硬件环境。

虚拟地址是一个逻辑数据值,例如 0~100 就有 101 整数值,这个 0~100 区间可以说是虚拟地址空间,虚拟地址空间有 101 个地址。

让我们看看开始 Hello World 我们用的例子 objdump 工具反汇编 Hello World 二进制文件将获得以下代码片段:

00000000000004e8 <_init>: 4e8: 48 83 ec 08                                         sub $0x8,%rsp 4ec: 48 8b 05 f5 0a 20 00                                mov 0x200af5(%rip),%rax # 200fe8 <__gm 4f3: 48 85 c0                                            test %rax,%rax 4f6: 74 02                                               je 4fa <_init 0x12> 4f8: ff d0                                               callq *%rax 4fa: 48 83 c4 08                                         add $0x8,%rsp 4fe: c3                                                  retq 

左边的第一列数据是虚拟地址,第三列是程序指令,如:mov 0x200af5(%rip),%rax,je 4fa,callq *%rax指令中的数据都是虚拟地址。

事实上,所有的应用程序都是这样开始的。这是因为每个应用程序的虚拟地址空间是相同的和独立的。

那么这个地址是谁产生的呢?

答案是链接器,其实我们开发软件编译步骤后,需要链接到可执行文件,链接器的主要工作是组装多个代码模块,解决模块之间的处理程序代码间的地址引用,形成程序运行的静态内存空间视图。

只是这个地址是虚拟而统一的,而根据不同的操作系统,虚拟地址空间的定义可能不同,应用软件开发人员不需要关心,开发工具链自动处理。因为这个虚拟地址是独立统一的,所以不用担心占用和重写各个公司开发的应用。

4.物理地址

虚拟地址虽然解决了很多问题,但是虚拟地址只是逻辑上存在的地址,如果不能作用于硬件电路,程序需要处理内存,以获得内存中的指令和数据。内存只识别一个地址,即物理地址。

物理地址是什么?物理地址在逻辑上也是一个数据,但该数据将被地址译码器和其他电子设备转换为电子信号,并放置在地址总线上。地址总线电子信号的各种组合可以选择存储单元。

但是地址总线上的信号(即物理地址)也可以选择其他设备中的存储单元,如显卡中的显示器I/O 设备中的寄存器,网卡上的网络帧缓存器。但是,如果没有特别说明,我们说的物理地址就是指选择内存单元的地址。

5.将虚拟地址转换为物理地址

在了解了虚拟地址和物理地址后,我们发现虚拟地址必须转换为物理地址,以便程序能够正常执行。要转换,机构必须转换,这相当于一个函数:p=f(v),输入虚拟地址 v,输出物理地址 p。

那么如何实现这个函数呢?

用软件实现效率太低,用硬件实现不灵活,最后用软硬件结合实现。 MMU(内存管理单元)。MMU 地址转换可以接受软件给出的地址对应关系数据。

先来看看逻辑。 MMU 工作原理框架图。如下图所示:

上图显示 MMU 通过地址关系转换表, 0x80000~0x84000 将虚拟地址空间转换为0x10000~0x14000 的物理地址空间,而地址关系转换表本身就放在物理内存中。

让我们考虑一下地址关系转换表实现. 若存储在地址关系转换表中:虚拟地址对应物理地址。

然后问题来了,32 在地址空间下,4GB 虚拟地址的地址关系转换表将转换整个虚拟地址 32 物理地址空间用完了,显然不行。

如果将以前的保护模式下的分段模式结合起来,则存储在地址关系转换表中:虚拟段基址对应物理段基址,看似可以,但由于段长不同,仍不可取。

根据刚才的分析,系统设计师最终使用了一个折中的方案,即把虚拟地址空间和物理地址空间分为相同大小的块,也称为页面,按照虚拟页和物理页进行转换。根据软件配置不同,这个页的大小可以设置为 4KB、2MB、4MB、1GB,这样就进入了现代内存管理模式——分页模型。

如下图所示:

从图片中可以看出,虚拟页面可以对应物理页面,因为页面大小一旦配置就固定了,因此,在地址关系转换表中,只需存储虚拟页面地址对应的物理页面地址即可。

我知道,说到这里,也许你还没有弄清楚 MMU 不要担心地址关系转换表的细节。我们现在有了研究它们的基础。让我们探索它们。

6.MMU

MMU 也就是说,内存管理单元是一种地址转换器,它负责接收虚拟地址和地址关系转换表,并输出物理地址。

根据实现方法的不同,MMU 它可以是独立的芯片,也可以集成在其他芯片中,如集成在其他芯片中 CPU 内部,x86、ARM 系列的 CPU 就是将 MU 集成在 CPU 核心中的。

SUN 公司的 CPU 是将独立的 MMU 芯片卡在总线上的,有一夫当关的架势。下面我们只研究 x86 CPU 中的 MMU。x86 CPU 要想开启 MMU,就必须先开启保护模式或者长模式,实模式下是不能开启 MMU 的。

由于保护模式的内存模型是分段模型,它并不适合于 MMU 的分页模型,所以我们要使用保护模式的平坦模式,这样就绕过了分段模型。这个平坦模型和长模式下忽略段基址和段长度是异曲同工的。地址产生的过程如下所示。

上图中,程序代码中的虚拟地址,经过 CPU 的分段机制产生了线性地址,平坦模式和长模式下线性地址和虚拟地址是相等的。

如果不开启 MMU,在保护模式下可以关闭 MMU,这个线性地址就是物理地址。因为长模式下的分段弱化了地址空间的隔离,所以开启 MMU 是必须要做的,开启 MMU 才能内存地址空间保存。

7.MMU 页表

现在我们开始研究地址关系转换表,其实它有个更加专业的名字——页表。它描述了虚拟地址到物理地址的转换关系,也可以说是虚拟页到物理页的映射关系,所以称为页表。

为了增加灵活性和节约物理内存空间(因为页表是放在物理内存中的),所以页表中并不存放虚拟地址和物理地址的对应关系只存放物理页面的地址MMU 以虚拟地址为索引去查表返回物理页面地址,而且页表是分级的,总体分为三个部分:一个顶级页目录,多个中级页目录,最后才是页表,逻辑结构图如下.

从上面可以看出,一个虚拟地址被分成从左至右四个位段。

第一个位段索引顶级页目录中一个项,该项指向一个中级页目录,然后用第二个位段去索引中级页目录中的一个项,该项指向一个页目录,再用第三个位段去索引页目录中的项,该项指向一个物理页地址,最后用第四个位段作该物理页内的偏移去访问物理内存。这就是 MMU 的工作流程

8.保护模式下的分页

前面的内容都是理论上帮助我们了解分页模式原理的,分页模式的灵活性、通用性、安全性,是现代操作系统内存管理的基石,更是事实上的标准内存管理模型,现代商用操作系统都必须以此为基础实现虚拟内存功能模块。

因为我们的主要任务是开发操作系统,而开发操作系统就落实到真实的硬件平台上去的,下面我们就来研究 x86 CPU 上的分页模式。

首先来看看保护模式下的分页,保护模式下只有 32 位地址空间,最多 4GB-1 大小的空间。

根据前面得知 32 位虚拟地址经过分段机制之后得到线性地址,又因为通常使用平坦模式,所以线性地址和虚拟地址是相同的。

保护模式下的分页大小通常有两种,一种是 4KB 大小的页,一种是 4MB 大小的页。分页大小的不同,会导致虚拟地址位段的分隔和页目录的层级不同,但虚拟页和物理页的大小始终是等同的

9.保护模式下的分页——4KB 页

该分页方式下,32 位虚拟地址被分为三个位段:页目录索引、页表索引、页内偏移,只有一级页目录,其中包含 1024 个条目 ,每个条目指向一个页表,每个页表中有 1024 个条目。其中一个条目就指向一个物理页,每个物理页 4KB。这正好是 4GB 地址空间。如下图
所示。

上图中 CR3 就是 CPU 的一个 32 位的寄存器,MMU 就是根据这个寄存器找到页目录的。下面,我们看看当前分页模式下的 CR3、页目录项、页表项的格式。

可以看到,页目录项、页表项都是 4 字节 32 位,1024 个项正好是 4KB(一个页),因此它们的地址始终是 4KB 对齐的,所以低 12 位才可以另作它用,形成了页面的相关属性,如是否存在、是否可读可写、是用户页还是内核页、是否已写入、是否已访问等。

10.保护模式下的分页——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 位。

11.长模式下的分页

如果开启了长模式,则必须同时开启分页模式,因为长模式弱化了分段模型,而分段模型也确实有很多不足,不适应现在操作系统和应用软件的发展。

同时,长模式也扩展了 CPU 的位宽,使得 CPU 能使用 64 位的超大内存地址空间。所以,长模式下的虚拟地址必须等于线性地址且为 64 位。

长模式下的分页大小通常也有两种,4KB 大小的页和 2MB 大小的页。

12.长模式下的分页——4KB 页

该分页方式下,64 位虚拟地址被分为 6 个位段,分别是:保留位段,顶级页目录索引、页目录指针索引、页目录索引、页表索引、页内偏移,顶级页目录、页目录指针、页目录、页表各占有 4KB 大小,其中各有 512 个条目,每个条目 8 字节 64 位大小,如下图所示。

上面图中 CR3 已经变成 64 位的 CPU 的寄存器,它指向一个顶级页目录,里面的顶级页目项指向页目录指针,依次类推。

需要注意的是,虚拟地址 48 到 63 这 6 位是根据第 47 位来决定的,47 位为 1,它们就为 1,反之为 0,这是因为 x86 CPU 并没有实现全 64 位的地址总线,而是只实现了 48位,但是 CPU 的寄存器却是 64 位的。

这种最高有效位填充的方式,即使后面扩展 CPU 的地址总线也不会有任何影响,下面我们去看看当前分页模式下的 CR3、顶级页目录项、页目录指针项、页目录项、页表项的格式,我画了一张图帮你理解。

由上图可知,长模式下的 4KB 分页下,由一个顶层目录、二级中间层目录和一层页表组成了 64 位地址翻译过程。

顶级页目录项指向页目录指针页页目录指针项指向页目录页页目录项指向页表页页表项指向一个 4KB 大小的物理页,各级页目录项中和页表项中依然存在各种属性位,这在图中已经说明。其中的 XD 位,可以控制代码页面是否能够运行。

13.长模式下的分页——2MB 页

在这种分页方式下,64 位虚拟地址被分为 5 个位段 :保留位段、顶级页目录索引、页目录指针索引、页目录索引,页内偏移,顶级页目录、页目录指针、页目录各占有 4KB 大小,其中各有 512 个条目,每个条目 8 字节 64 位大小。

可以发现,长模式下 2MB 和 4KB 分页的区别是,2MB 分页下是页目录项直接指向了2MB 大小的物理页面,放弃了页表项,然后把虚拟地址的低 21 位作为页内偏移,21 位正好索引 2MB 大小的地址空间。

下面我们还是要去看看 2MB 分页模式下的 CR3、顶级页目录项、页目录指针项、页目录项的格式,格式如下图。

上图中没有了页表项,取而代之的是,页目录项中直接存放了 2MB 物理页基地址。由于物理页始终 2MB 对齐,所以其地址的低 21 位为 0,用于存放页面属性位。

14.开启 MMU

要使用分页模式就必先开启 MMU,但是开启 MMU 的前提是 CPU 进入保护模式或者长模式,开启 CPU 这两种模式的方法,我们在已经讲过了,下面我们就来开启 MMU,步骤如下:

  1. 使 CPU 进入保护模式或者长模式。

  2. 准备好页表数据,这包含顶级页目录,中间层页目录,页表,假定我们已经编写了代码,在物理内存中生成了这些数据。

  3. 把顶级页目录的物理内存地址赋值给 CR3 寄存器。

    mov eax, PAGE_TLB_BADR ;页表物理地址
    mov cr3, eax
    
  4. 设置 CPU 的 CR0 的 PE 位为 1,这样就开启了 MMU.

    ;开启 保护模式和分页模式
    mov eax, cr0
    bts eax, 0 ;CR0.PE =1
    bts eax, 31 ;CR0.P = 1
    mov cr0, eax
    

15.MMU 地址转换失败

MMU 的主要功能是根据页表数据把虚拟地址转换成物理地址,但有没有可能转换失败?

绝对有可能,例如,页表项中的数据为空,用户程序访问了超级管理者的页面,向只读页面中写入数据。这些都会导致 MMU 地址转换失败。

MMU 地址转换失败了怎么办呢?失败了既不能放行,也不是 reset,MMU 执行的操作如下。

  1. MMU 停止转换地址。
  2. MMU 把转换失败的虚拟地址写入 CPU 的 CR2 寄存器。
  3. MMU 触发 CPU 的 14 号中断,使 CPU 停止执行当前指令。
  4. CPU 开始执行 14 号中断的处理代码,代码会检查原因,处理好页表数据返回。
  5. CPU 中断返回继续执行 MMU 地址转换失败时的指令。

这里你只要先明白这个流程就好了,后面会讲到内存管理的时候我们继续探讨。

16.总结

首先,我们从一个场景开始热身,发现多道程序同时运行有很多问题,都是内存相关的问题,内存需要隔离和保护。从而提出了虚拟地址与物理地址分离,让应用程序从实际的物理内存中解耦出来。

虽然虚拟地址是个非常不错的方案,但是虚拟地址必须转换成物理地址,才能在硬件上执行。为了执行这个转换过程,才开发出了 MMU(内存管理单元),MMU增加了转换的灵活性,它的实现方式是硬件执行转换过程,但又依赖于软件提供的地址转换表。

最后,我们下落到具体的硬件平台,研究了 x86 CPU 上的 MMU。

x86 CPU 上的 MMU 在其保护模式和长模式下提供 4KB、2MB、4MB 等页面转换方案,我们详细分析了它们的页表格式。同时,也搞清楚了如何开启 MMU,以及 MMU 地址转换失败后执行的操作。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章