ELF文件
对于每个程序,其在经历预处理、编译、汇编之后,都要经过链接器将其链接成一个单一的可执行文件。在现在Unix和x86-64 Linux系统上,其使用的可执行格式为ELF,如下:
可以看到ELF涵盖了程序中的各种信息,加载器就是通过读取ELF文件中的数据和代码,将其从磁盘复制到内存中,生成相应的进程并跳转到第一条指令或入口点来运行该程序。
进程
进程地址空间如下图所示:
可以看到,只读代码和数据对应于elf文件的(.init,.text,.rodata),读写段对应于elf文件的(.data,.bss)。
问题来了,如果我们有很多个程序要运行,所需内存已经超过了我们物理内存的容量,此时该怎么处理呢?
其实,当程序运行时,如果内存空间足够大,操作系统会按分页机制,将程序调入内存中。否则,操作系统会分批将程序的部分内容调入内存,再通过磁盘上的虚拟内存来实现内存置换,达到按需加载的目的。
到这里对虚拟内存有一定的概念了,似乎其作用就是“虚拟地扩充我们的内存”。
虚拟内存
为了更加有效地管理内存,操作系统对主存提出了一种抽象的概念:虚拟内存。其通过硬件+软件的支持,为进程提供了更大的、一致的和私有的地址空间。虚拟内存主要提供一下三个能力:
1)它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
2)它为每个进程提供了一致的地址空间,从而简化了存储器管理;
3)保护了每个进程的地址空间不被其他进程破坏;
虚拟存储器是计算机系统最重要的概念之一。它的成功在于它是沉默地、自动地工作着,不需要应用程序员的任何干涉。
既然虚拟存储器在幕后工作得如此之好,为什么程序员还要理解它,原因有以下几个:
1)虚拟存储器是中心的。
虚拟存储器遍及计算机系统的所有层面,理解虚拟存储器将帮助更好地理解系统通常是如何工作的。
2)虚拟存储器是强大的。
虚拟存储器给予应用程序强大的能力。可以创建和销毁存储器的片(chunk),将存储器映射到磁盘文件的某个部分,以及与其他进程共享存储器。
3)虚拟存储器是危险的。
每次应用程序引用一个变量、间接引用一个指针,或者调用一个诸如malloc这样的动态分配程序时,它就会和虚拟存储器发生交互。如果虚拟存储器使用不当,应用将遇到复杂危险的与存储器有关的错误。例如,一个带有错误指针的程序可以立即崩溃于“段错误”或者“保护错误”,它可能在崩溃之前还默默地运行了几个小时,或者是最令人惊慌地,运行完成却产生不正确的结果。
这篇文章主要讲述两方面:
1)虚拟存储器是如何工作的;
2)应用程序如何使用和管理虚拟存储器;
9.1 物理和虚拟寻址计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。
每字节都有唯一的物理地址(Physical Address)PA。
第一个字节的地址为0,接下来的字节地址为1,以此类推。
CPU访问存储器最自然的方式就是使用物理地址。这种寻址方式被称为物理寻址。
当CPU执行一条加载指令时,它会生成一个有效的物理地址,通过存储器总线,把它传递给主存。
主存取出从物理地址4处开始的4字节的字,并 将它返回给CPU,CPU会将它存放在一个寄存器中。
现代处理器使用的是一种称为虚拟寻址的寻址方式。
使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址。
将一个虚拟地址转换成物理地址的任务叫做地址翻译。CPU芯片上叫做MMU(Memory Management Unit)存储器管理单元的专用硬件会进行这个任务。
地址翻译的任务需要硬件和操作系统紧密配合,MMU会利用放在主存上的查询表来动态翻译虚拟地址,该表的内容由操作系统来管理。
9.2 地址空间地址空间是一个非负整数地址的有序集合:{0,1,2,...}
如果地址空间中的整数是连续的,那么我们说它是线性地址空间。
为了简化讨论,我们总是假设地址空间是线性地址空间。
在带虚拟存储器的系统中,CPU从一个有N=2^n 个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间。
一个地址空间的大小是由表示最大地址所需要的位数来描述的。
例如,一个包含N=2^n个地址的虚拟地址空间叫做一个n位地址空间。
现代系统典型地支持32位或者64位虚拟地址空间。
一个系统还有物理地址空间,它与系统中物理存储器的M个字节相对应:{0,1,2,...,M-1}
M不要求是2的幂,但为了简化讨论,一般假设M=2^m。物理地址空间对应于系统中实际拥有DRAM容量。
地址空间的概念非常重要。
它清楚地区分了数据对象(字节)和它们的属性(地址)。
主存中的每个字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。
允许每个数据对象有多个独立的地址,其中每一个地址都选自一个不同的地址空间。这就是虚拟存储器的基本思想
9.3 虚拟内存作为缓存的工具概念上而言,虚拟内存(VM)被组织为一个由存放在磁盘上的 N 个连续的字节大小的单元组成的数组。
每字节都有一个唯一的虚拟地址,这个唯一的虚拟地址是作为到数组的索引的。
VM 系统通过将虚拟存储器分割为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。每个虚拟页的大小为 P=2^p 字节。
类似地,物理存储器被分割为物理页(Physical Page, PP),大小也为 P 字节(物理页也称为页帧(page frame))。
在任意时刻,虚拟页面的集合部分都分为三个不相交的子集:
- 未分配的:VM 系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
- 缓存的:当前缓存在物理存储器中的已分配页。
- 未缓存的:没有缓存在物理存储器中的已分配页。
在存储层次结构中,DRAM缓存的位置对于他的组织结构有很大的影响。DRAM 缓存的组织结构完全是由巨大的不命中开销驱动的。
因为大的不命中处罚和访问第一字节的开销,虚拟页往往很大,典型地是4KB-2MB。由于大的不命中处罚,DRAM 缓存是全相连的,也就是说,任何虚拟页都可以放置在任何的物理页中。
不命中时的替换策略也很重要,因为替换错了虚拟页的处罚也非常高。因此,与硬件对 SRAM 缓存相比,操作系统对 DRAM 缓存使用了更复杂精密的替换算法。最后,因为对磁盘的访问时间很长,DRAM 缓存总是使用写回(write back),而不是直写。
术语SRAM缓存来表示位于CPU和主存之间的L1、L2、L3高速缓存;高速缓存
术语DRAM缓存来表示虚拟存储器系统的缓存,它在主存中缓存虚拟页;主存缓存
SRAM比DRAM快大约10倍,DRAM比磁盘快大约100000倍;
DRAM不命中,要由本地磁盘服务;
SRAM不命中,要由DRAM服务;
9.3.2 页表页表将虚拟页映射到物理页;
每次地址翻译硬件将虚拟地址转换为物理地址时都会读取页表。
操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。
通过页表,虚拟存储器系统可以判定一个虚拟页是否存放在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页放在哪个物理页中。
如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置。
在物理存储中选择一个牺牲页,并将虚拟页从磁盘拷贝到DRAM中,替换这个牺牲页。
这些功能是由许多软硬件联合提供的,包括操作系统软件、MMU(存储器管理单元)中的地址翻译硬件和一个存放在物理存储器中的叫做页表的数据结构。
页表是页表条目(Page Table Entry, PTE)的一个数组;
每个PTE是由一个有效位和一个n位地址字段构成的。
有效位表明当前虚拟页是否缓存在DRAM中。
地址字段表明DRAM中相应的物理页起始位置。
如果没有设置有效位,空地址表示虚拟页还没有被分配;否则这个地址指向虚拟页在磁盘上的起始位置。(虚拟页映射到磁盘中不叫缓存)。
图 9-4 页表
9.3.3 页命中
图 9-5 VM 页命中。对 VP 2 中一个字的引用就会命中
在虚拟内存的习惯说法中,DRAM 缓存不命中称为缺页(page fault)。图 9-6 展示了在缺页之前我们的示例页表的状态。CPU 引用了 VP 3 中的一个字,VP 3 并未缓存在 DRAM 中。地址翻译硬件从内存中读取 PTE 3,从有效位推断出 VP 3 未被缓存,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在 PP 3 中的 VP 4。如果 VP 4 已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改 VP 4 的页表条目,反映出 VP 4 不再缓存在主存中这一事实。
图 9-6 VM 缺页(之前)。对 VP3 中的字的引用会不命中,从而触发了缺页
接下来,内核从磁盘复制 VP 3 到内存中的 PP 3,更新 PTE 3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP 3 已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。图 9-7 展示了在缺页之后我们的示例页表的状态。
图 9-7 VM 缺页(之后)。缺页处理程序选择 VP 4 作为牺牲页,并从磁盘上用 VP 3 的副本取代它。在缺页处理程序重新启动导致缺页的指令之后,该指令将从内存中正常地读取字,而不会再产生异常
虚拟内存是在 20 世纪 60 年代早期发明的,远在 CPU - 内存之间差距的加大引发产生 SRAM 缓存之前。因此,虚拟内存系统使用了和 SRAM 缓存不同的术语,即使它们的许多概念是相似的。在虚拟内存的习惯说法中,块被称为页。在磁盘和内存之间传送页的活动叫做交换(swapping)或者页面调度(paging)。页从磁盘换入(或者页面调入)DRAM 和从 DRAM 换出(或者页面调出)磁盘。一直等待,直到最后时刻,也就是当有不命中发生时,才换入页面的这种策略称为按需页面调度(demand paging)。也可以采用其他的方法,例如尝试着预测不命中,在页面实际被引用之前就换入页面。然而,所有现代系统都使用的是按需页面调度的方式。
9.3.5 分配页面图 9-8 展示了当操作系统分配一个新的虚拟内存页时对我们示例页表的影响,例如,调用 malloc 的结果。在这个示例中,VP5 的分配过程是在磁盘上创建空间并更新 PTE 5,使它指向磁盘上这个新创建的页面。
9.3.6 局部性再次搭救图 9-8 分配一个新的虚拟页面。内核在磁盘上分配 VP 5,并且将 PTE 5 指向这个新的位置
尽管在整个运行过程中程序引用的不同页面的总数可能超出物理存储器总的大小,但是局部性原则保证了在任意时刻,程序往往在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set)或者常驻集(resident set)。
如果工作集的大小超出了物理存储器的大小,那么程序将产生一种不幸的状态,叫做颠簸(thrashing),这时页面将不断的换进换出。
9.4 虚拟内存作为存储管理的工具OS为每个进程提供一个独立的页表,就是一个独立的虚拟地址空间。多个虚拟页面可以映射到同一个共享物理页面上。
图 9-9 VM 如何为进程提供独立的地址空间。操作系统为系统中的每个进程都维护一个独立的页表
VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配。
简化链接:
独立的地址空间允许每个进程为它的存储器映像使用相同的格式,而不需要管 代码和数据 存放在物理存储器的何处。
这样大大简化了链接器的设计和实现,允许链接器生成全链接的可执行文件,这些可执行文件是独立于物理存储器中代码和数据的最终位置。
链接器现在可以假设每个程序都加载到完全相同的位置,所以链接器能提前知道程序要加载到哪里。
简化加载:虚拟内存使得容易向内存中加载可执行文件和共享对象文件。
简化共享:
独立地址空间为OS提供了一个管理用户进程和操作系统自身之间共享的机制。一般而言,每个进程都有自己私有的代码、数据、堆以及栈区域,是不与其他进程共享的。当要共享时,操作系统将不同进程中的虚拟页映射到相同的物理页面,从而多个进程共享这部分的代码。
简化内存分配:虚拟内存向用户进程提供一个简单的分配额外内存的机制。当一个用户程序要求额外的堆空间时候,操作系统分配 k 个适当的连续的虚拟内存页面,并且将他们映射到物理内存的中的 k 个任意页面,操作系统没有必要分配 k 个连续的物理内存页面。
9.5 虚拟内存作为存储器保护的工具虚拟地址空间的有些部分是只读的,比如代码段;有些部分只能由内核执行。在64位系统上,指针和地址也是64位的,但实际上,真正的虚拟地址空间是48位的,48位之后的高比特位全部为0或者全部为1,这是英特尔的规则,高位都是1的地址是为内核(内核代码,内核数据)保留的,高位都是0的地址是为用户代码保留的。
因此,可以在PTE中设置一些位,表明用户代码是否可以访问某些虚拟页面,或者他们是否必须由内核访问,这就是所谓的管理员模式。
你还可以设置一些位,表明该页面是否可以读、写、执行。通过向PTE添加位的简单技术,我们提供了这种自动的方式来保护虚拟地址空间的不同部分面授未经授权的访问。MMU在每次访问时检查这些位,如果进行非法操作,那么它就会抛出一个异常,由内核来处理。
9.6 地址翻译地址翻译的基础知识:
基本参数
虚拟地址(VA)的组成部分
物理地址(PA)的组成部分
形式上来说,地址翻译是一个 N 元素的虚拟地址空间(VAS)中的元素和一个 M 元素的物理地址空间(PAS)中元素之间的映射,
$$
\rm MAP:VAS\rightarrow PAS \cup ∅
$$
这里:
$$
\rm MAP(A)= \begin{cases}A'&\text{如果虚拟地址}A\text{处的数据在PAS的物理地址}A'\text{处}\∅ &\text{如果虚拟地址}A\text{处的数据不在物理内存中}\end{cases}
$$
下图展示了 MMU 如何利用页表来实现这种映射。
CPU 中的一个控制寄存器,页表基址寄存器(Page Table Base Register,PTBR)指向当前页表。
n 位的虚拟地址包含两个部分:
一个 p 位的虚拟页面偏移(Virtual Page Offset,VPO)
一个(n−p)位的虚拟页号(Virtual Page Number,VPN)
MMU 利用 VPN 来选择适当的 PTE。例如,VPN 0 选择 PTE 0,VPN 1 选择 PTE 1,以此类推。
将页表条目中物理页号(Physical Page Number,PPN)和虚拟地址中的 VP。串联起来,就得到相应的物理地址。
注意,因为物理和虚拟页面都是 P 字节的,所以物理页面偏移(Physical Page Offset,PPO)和 VPO 是相同的。
![img](https://1087580735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHt_spaxGgCbp2POnfq%2F-MIYPLpzE9Ks_UYOpt28%2F-MIYQgjBI9_qvCIM8oAW%2F09-12 使用页表的地址翻译.png?alt=media&token=d7031a9b-e28f-4a61-885e-48d7a04f6f8a)
图 a 展示了当页面命中时,CPU 硬件执行的步骤:
第 1 步:处理器生成一个虚拟地址,并把它传送给 MMU。
第 2 步:MMU 生成 PTE 地址,并从高速缓存/主存请求得到它。
第 3 步:高速缓存/主存向 MMU 返回 PTE。
第 4 步:MMU 构造物理地址,并把它传送给高速缓存/主存。
第 5 步:高速缓存/主存返回所请求的数据字给处理器。
图 9-13 页面命中(a)和缺页(b)的操作图
(VA:虚拟地址。PTEA:页表条目地址。PTE:页表条目。PA:物理地址)
页面命中完全是由硬件来处理的,与之不同的是,处理缺页要求硬件和操作系统内核协作完成,如图 9-13 b 所示。
第 1 步到第 3 步:和图 9-13a 中的第 1 步到第 3 步相同。
第 4 步:PTE 中的有效位是零,所以 MMU 触发了一次异常,传递 CPU 中的控制到操作系统内核中的缺页异常处理程序。
第 5 步:缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘。
第 6 步:缺页处理程序页面调入新的页面,并更新内存中的 PTE。
第 7 步:缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU 将引起缺页的虚拟地址重新发送给 MMU。因为虚拟页面 现在缓存在物理内存中,所以就会命中,在 MMU 执行了图 9-13 b 中的步骤之后,主存就会将所请求字返回给处理器。
练习9.3
给定一个 32 位的虚拟地址空间和一个 24 位的物理地址,对于下面的页面大小 P,确定 VPN、VPO、PPN 和 PPO 中的位数:
为了完全掌握地址翻译,你需要很好地理解这类问题。下面是如何解决第一个子问题:我们有 n=32 个虚拟地址位和 m=24 个物理地址位。页面大小是P=1 KB,这意味着对于 VPO 和 PPO,我们都需要 log2(1K)=10 位。(回想一下,VPO 和 PPO 是相同的。)剩下的地址位分别是 VPN 和 PPN。
9.6.1 结合高速缓存和虚拟内存在任何既使用虚拟内存又使用 SRAM 高速缓存的系统中,都有应该使用虚拟地址还是使用物理地址来访问 SRAM 高速缓存的问题。尽管关于这个折中的详细讨论已经超出了我们的讨论范围,但是大多数系统是选择物理寻址的。使用物理寻址,多个进程同时在高速缓存中有存储块和共享来自相同虚拟页面的块成为很简单的事情。而且,高速缓存无需处理保护问题,因为访问权限的检査是地址翻译过程的一部分。
图 9-14 展示了一个物理寻址的高速缓存如何和虚拟内存结合起来。主要的思路是地址翻译发生在高速缓存查找之前。注意,页表条目可以缓存,就像其他的数据字一样。
![img](https://1087580735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHt_spaxGgCbp2POnfq%2F-MIYPLpzE9Ks_UYOpt28%2F-MIYRUfwKpIOP07bMfU2%2F09-14 将VM与物理寻址的高速缓存结合起来.png?alt=media&token=edc18dde-68b7-4b1e-bade-e4edaebbdc97)
9.6.2 利用TLB加速地址翻译图 9-14 将 VM 与物理寻址的高速缓存结合起来
(VA:虚拟地址。PTEA:页表条目地址。PTE:页表条目。PA:物理地址)
正如我们看到的,每次 CPU 产生一个虚拟地址,MMU 就必须查阅一个 PTE,以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会要求从内存多取一次数据,代价是几十到几百个周期。如果 PTE 碰巧缓存在 L1 中,那么开销就下降到 1 个或 2 个周期。然而,许多系统都试图消除即使是这样的开销,它们在 MMU 中包括了一个关于 PTE 的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer,TLB)。
TLB 是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个 PTE 组成的块。TLB 通常有高度的相联度。如图 9-15 所示,用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。如果 TLB 有T=2t\small T = 2^tT=2t个组,那么 TLB 索引(TLBI)是由 VPN 的 t 个最低位组成的,而 TLB 标记(TLBT)是由 VPN 中剩余的位组成的。
图 9-15 虚拟地址中用以访问 TLB 的组成部分
图 9-16 a 展示了当 TLB 命中时(通常情况)所包括的步骤。这里的关键点是,所有的地址翻译步骤都是在芯片上的 MMU 中执行的,因此非常快。
第 1 步:CPU 产生一个虚拟地址。
第 2 步和第 3 步:MMU 从 TLB 中取出相应的 PTE。
第 4 步:MMU 将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。
第 5 步:高速缓存/主存将所请求的数据字返回给 CPU。
图 9-16 TLB 命中和不命中的操作图
当 TLB 不命中时,MMU 必须从 L1 缓存中取出相应的 PTE,如图 9-16 b 所示。新取出的 PTE 存放在 TLB 中,可能会覆盖一个已经存在的条目。
9.6.3 多级页表到目前为止,我们一直假设系统只用一个单独的页表来进行地址翻译。但是如果我们有一个 32 位的地址空间、4KB 的页面和一个 4 字节的 PTE,那么即使应用所引用的只是虚拟地址空间中很小的一部分,也总是需要一个 4MB 的页表驻留在内存中。对于地址空间为 64 位的系统来说,问题将变得更复杂。
用来压缩页表的常用方法是使用层次结构的页表。用一个具体的示例是最容易理解这个思想的。假设 32 位虚拟地址空间被分为 4KB 的页,而每个页表条目都是 4 字节。还假设在这一时刻,虚拟地址空间有如下形式:内存的前 2K 个页面分配给了代码和数据,接下来的 6K 个页面还未分配,再接下来的 1023 个页面也未分配,接下来的 1 个页面分配给了用户栈。图 9-17 展示了我们如何为这个虚拟地址空间构造一个两级的页表层次结构。
图 9-17 一个两级页表层次结构。注意地址是从上往下增加的
一级页表中的每个 PTE 负责映射虚拟地址空间中一个 4MB 的片(chunk),这里每一片都是由 1024 个连续的页面组成的。比如,PTE 0 映射第一片,PTE 1 映射接下来的一片,以此类推。假设地址空间是 4GB,1024 个 PTE 已经足够覆盖整个空间了。
如果片 i 中的每个页面都未被分配,那么一级 PTE i 就为空。例如,图 9-17 中,片 2 ~ 7 是未被分配的。然而,如果在片 i 中至少有一个页是分配了的,那么一级 PTE i 就指向一个二级页表的基址。例如,在图 9-17 中,片 0、1 和 8 的所有或者部分已被分配,所以它们的一级 PTE 就指向二级页表。
二级页表中的每个 PTE 都负责映射一个 4KB 的虚拟内存页面,就像我们查看只有一级的页表一样。注意,使用 4 字节的 PTE,每个一级和二级页表都是 4KB 字节,这刚好和一个页面的大小是一样的。
这种方法从两个方面减少了内存要求。第一,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。这代表着一种巨大的潜在节约,因为对于一个典型的程序,4GB 的虚拟地址空间的大部分都会是未分配的。第二,只有一级页表才需要总是在主存中;虚拟内存系统可以在需要时创建、页面调入或调出二级页表,这就减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中。
图 9-18 描述了使用 k 级页表层次结构的地址翻译。虚拟地址被划分成为 k 个 VPN 和 1 个 VPO。每个 VPN i 都是一个到第 i 级页表的索引,其中 1⩽i⩽k 。第 j 级页表中的每个 PTE,1⩽j⩽k−1,都指向第 j+1 级的某个页表的基址。第 k 级页表中的每个 PTE 包含某个物理页面的 PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定 PPN 之前,MMU 必须访问为个 PTE。对于只有一级的页表结构,PPO 和 VPO 是相同的。
图 9-18 使用 k 级页表的地址翻译
访问 k 个 PTE,第一眼看上去昂贵而不切实际。然而,这里 TLB 能够起作用,正是通过将不同层次上页表的 PTE 缓存起来。实际上,带多级页表的地址翻译并不比单级页表慢很多。
9.6.4 综合:端到端的地址翻译在这一节里,我们通过一个具体的端到端的地址翻译示例,来综合一下我们刚学过的这些内容,这个示例运行在有一个 TLB 和 L1 d-cache 的小系统上。为了保证可管理性,我们做出如下假设:
- 内存是按字节寻址的。
- 内存访问是针对 1 字节的字的(不是 4 字节的字)。
- 虚拟地址是 14 位长的(n=14)。
- 物理地址是 12 位长的(m=12)。
- 页面大小是 64 字节(P=64)。
- TLB 是四路组相联的,总共有 16 个条目。
- L1 d-cache 是物理寻址、直接映射的,行大小为 4 字节,而总共有 16 个组。
图 9-19 展示了虚拟地址和物理地址的格式。因为每个页面是2^6=64字节,所以虚拟地址和物理地址的低 6 位分别作为 VPO 和 PPO。虚拟地址的高 8 位作为 VPN。物理地址的高 6 位作为 PPN。
图 9-19 小内存系统的寻址。假设 14 位的虚拟地址(n=14),12 位的物理地址(m=12)和 64 字节的页面(P=64)
图 9-20 展示了小内存系统的一个快照,包括 TLB(图 9-20a)、页表的一部分(图 9-20b)和 L1 高速缓存(图 9-20c)。在 TLB 和高速缓存的图上面,我们还展示了访问这些设备时硬件是如何划分虚拟地址和物理地址的位的。
- TLB。TLB 是利用 VPN 的位进行虚拟寻址的。因为 TLB 有 4 个组,所以 VPN 的低 2 位就作为组索引(TLBI)。VPN 中剩下的高 6 位作为标记(TLBT),用来区别可能映射到同一个 TLB 组的不同的 VPN。
- 页表。这个页表是一个单级设计,一共有2^8=256个页表条目(PTE)。然而,我们只对这些条目中的开头 16 个感兴趣。为了方便,我们用索引它的 VPN 来标识每个 PTE;但是要记住这些 VPN 并不是页表的一部分,也不储存在内存中。另外,注意每个无效 PTE 的 PPN 都用一个破折号来表示,以加强一个概念:无论刚好这里存储的是什么位值,都是没有任何意义的。
- 高速缓存。直接映射的缓存是通过物理地址中的字段来寻址的。因为每个块都是 4 字节,所以物理地址的低 2 位作为块偏移(CO)。因为有 16 组,所以接下来的 4 位就用来表示组索引(CI)。剩下的 6 位作为标记(CT)。
图 9-20 (a) 小内存系统的 TLB:四组,16个条目,四路组相联
图 9-20 (b) 小内存系统的页表:只展示了前 16 个 PTE
图 9-20 (c) 小内存系统的高速缓存:16 个组,4 字节的块,直接映射
图 9-20 小内存系统的 TLB、页表以及缓存。TLB、页表和缓存中所有的值都是十六进制表示的
给定了这种初始化设定,让我们来看看当 CPU 执行一条读地址 0x03d4 处字节的加载指令时会发生什么。(回想一下我们假定 CPU 读取 1 字节的字,而不是 4 字节的字。)为了开始这种手工的模拟,我们发现写下虚拟地址的各个位,标识出我们会需要的各种字段,并确定它们的十六进制值,是非常有帮助的。当硬件解码地址时,它也执行相似的任务。
开始时,MMU 从虚拟地址中抽取出 VPN(0x0F),并且检查 TLB,看它是否因为前面的某个内存引用缓存了 PTE 0x0F 的一个副本。TLB 从 VPN 中抽取出 TLB 索引(0x03)和 TLB 标记(0x3),组 0x3 的第二个条目中有效匹配,所以命中,然后将缓存的 PPN(0x0D)返回给 MMU。
如果 TLB 不命中,那么 MMU 就需要从主存中取出相应的 PTE。然而,在这种情况中,我们很幸运,TLB 会命中。现在,MMU 有了形成物理地址所需要的所有东西。它通过将来自 PTE 的 PPN(0x0D)和来自虚拟地址的 VPO(0x14)连接起来,这就形成了物理地址(0x354)。
接下来,MMU 发送物理地址给缓存,缓存从物理地址中抽取出缓存偏移 CO(0x0)、缓存组索引 CI(0x5)以及缓存标记 CT(0x0D)。
因为组 0x5 中的标记与 CT 相匹配,所以缓存检测到一个命中,读出在偏移量 CO 处的数据字节(0x36),并将它返回给 MMU,随后 MMU 将它传递回 CPU。
翻译过程的其他路径也是可能的。例如,如果 TLB 不命中,那么 MMU 必须从页表中的 PTE 中取出 PPN。如果得到的 PTE 是无效的,那么就产生一个缺页,内核必须调入合适的页面,重新运行这条加载指令。另一种可能性是 PTE 是有效的,但是所需要的内存块在缓存中不命中。