超标量处理器笔记_虚拟存储器

1.概述

现代的高性能处理器都支持虚拟地址Virtual address但是为什么需要虚拟地址。

随着应用程序规模越来越大,以至于物理内存已经无法容纳下这样的程序了,通常的做法就是把程序分割成很多片段,片段0首先放到物理内存中执行,当他执行完时调用下一个片段。

虽然片段在物理内存中的交换是操作系统完成的,但是程序眼需要先对程序进行分割,费时费力。

为了解决这个问题,有了虚拟存储器Virtual Memory。

虚拟存储器的基本思想,是对于一个程序来说,它的程序code数据data和堆栈stack的总大小可以超过实际物理内存的大小,操作系统把当前使用的部分放到物理内存中,而把其他未使用的内容放到下一级存储器,如硬盘或闪存。这样不需要进行分割。

有了这一样的概念就可以说一个程序是运行在虚拟存储器空间的,空间大小由处理器的位数决定。

[!TIP]

对于一个32位的处理器来说,其地址范围就是0~0xFFFF_FFFF,也就是4GB,这个范围就是处理器能够产生的地址范围。

其中的某一个地址就称为虚拟地址,与虚拟存储器相对应的就是物理存储器,其中的某一个地址就是物理地址。物理存储器的大小不能超过处理器最大可以寻址的空间


在没有使用虚拟地址的系统中,处理器输出的地址会直接送到物理存储器中,如图所示。

image-20250902183833552

如果使用了虚拟地址,则虚地址不会被直接送到物理存储器中,而是需要先进行地址转换,负责地址转换的部件一般称为内存管理单元Memory Manage Unit(MMU)

image-20250902184117499

使用虚拟存储器之后,每个程序总认为它占有处理器的所有地址空间,因此程序可以任意使用处理器的地址资源,这样在编写程序的时候,不需要考虑地址的限制,每个程序都认为处理器中只有自己在运行。当这些程序真正放到处理器中运行时,由操作系统负责调度,讲物理存储器动态分配给各个程序。将每个程序的虚拟地址转化为物理地址。

通过操作系统转化可以实现程序保护,即使两个程序都使用了同一个虚拟地址,他们也会对应到不同物理地址,因此可以保证每个程序的内容不会被其他程序随便改写。

这种方式还可以实现程序间的共享。

[!TIP]

例如程序A,B都使用了printf函数,操作系统在进行地址转换的时候,会将地址A,B转化为同样的物理地址,这个地址就是printf函数的物理地址

因此虚拟存储器不仅可以降低物理存储器的容量需求,还可以带来其他好处,如保护和共享。


地址转换

虚拟存储器目前最通用的方式是基于分页(page)的虚拟存储器,虚拟地址空间的划分以页为单位,典型页大小为4KB,相应的物理地址空间也进行同样大小的划分。

由于历史原因,在物理地址空间中不叫页,而称为frame,它和页的大小必须相等

当程序开始运行时,会将当前需要的部分内容从硬盘中搬移到物理内存中,每次搬移一页。

只有在需要的时候才将一个页的内容放到物理内存中,这种方式称为demand page,它使处理器可以运行比物理内存更大的程序。

[!NOTE]

对于一个虚拟地址VA来说,VA[11:0]用来表示页内位置,称为page offset,VA剩余部分用来表示哪个页,称为VPN(Virtual Page Number)。相应的,PA是物理地址,PA[11:0]用来表示frame内的位置,称为frame offset,PA剩余部分表示frame内的位置,称为PFN(Physical Frame Number)

因为页和frame的大小是一样的,所以从VA到PA的转化实际上就是从VPN到PFN的转化,offset部分是不需要变化的。

image-20250903112339554

以一条指令为例

Load R2, 5[R1]	//假设R2的值为0

这条load指令执行时,得到的取数据的虚拟地址是R1+5=5,也就是地址5会被送到MMU中,如上图所示,地址5落在了page0(04096),而page0映射到了物理地址中的frame4(12,28816,384),因此MMU将虚拟地址转换为物理地址12228+5=12,293,并把这个地址送到物理内存中读取数据。

再看一个例子

Load R1, 0[R1]	//假设R1的置为20500

还是看上面的图,20500落在了page5(20480~24,576),但是距离这个page的起始地址20480,有20字节的距离,由于这个便宜是不会变化的,因此被映射的物理地址是frame3中的12288+20=12308

Load R1, 0[R1]	//假设R1的置为32780

虚拟地址32780落在了page8范围内,但page8中没有有效的映射,即此时page8没有存在于物理内存中,而是存在于硬盘中,MMU发现这个页没有被映射之后,就产生一个Page Faule的异常送给处理器。

处理器转到Page Faule对应的异常处理程序中处理,必须从内存的八个frame中找到一个档期那很少被使用的,然后解除page的映射关系,然后把需要的内容从硬盘搬移到物理内存中的frame空间,并把page8标记为映射到这个frame。

如果这个被替换掉的frame还是脏状态的,还需要先把它的内容搬移到硬盘中。

2.1单级页表

对处理器来说,如果它需要的page不在物理内存中,就产生了Page Fault类型的异常,需要访问下一级的存储器,如硬盘,而硬盘的访问时间一般是以ms位单位的,需要很长时间,严重降低了处理器性能。因此要尽量减少Page Fault发生的频率。

这就需要优化页在物理内存中的摆放,使用比较灵活的替换算法能够减少Page Fault发生的频率。

在使用虚拟存储器的系统中,都是使用一张表格来存储从虚拟地址到物理地址的的对应关系,这个表格称为页表Page Table,PT ,一般都是将这个表格放到物理内存中,使用虚拟地址来寻址,表格中寻址到的内容就是这个虚拟地址对用的物理地址,每个程序都有自己的页表。

为了指示一个程旭的页表在物理内存中的起始位置,在处理器中一般都会包含一个寄存器,用来存放当前运行程序的页表在物理内存中的起始地址,这个寄存器称为页表寄存器Page Table Register(PTR).

每次操作系统将一个程序调入物理内存中执行的时候,就会将寄存器PTR设置好。

时使用PTR和虚拟地址共同来寻址页表,就相当于用他们两个共同组成一个地址,使用这个地址来寻址物理内存。

如果页表中这个被寻址的表项对应的有效位是0,就表示这个虚拟地址对应的4KB空间还没有被操作系统映射到物理内存中,此时就产生了Page Fault类型的异常。

image-20250903165135643

真正寻址页表的其实不是虚拟地址的所有位数,而只是VPN就可以了,从页表中找到的内容也不是整个物理地址,而是PFN。

根据上图,页表中的每个表项似乎只需要18位的PFN和1位的有效位,但实际上因为页表是放到物理内存中,而物理内存中的数据位宽都是32位的,所以有剩余的位数,用来表示一些其他的信息,如每个页的属性信息(是否可读或可写等)。一个程序在运行的时候需要在物理内存中划分出4MB的连续空间来存储他的页表,然后才可以正常的运行这个程序。

[!IMPORTANT]

在处理器中,一个程序对应的页表,连同PC和通用寄存器一起,组成了这个程序的状态

如果在当前程序执行的时候,想要另外一个程序使用这个处理器,就需要将当前程序的状态进行保存,这样就可以一段时间后对这个程序进行恢复,从而使整个程序可以继续执行。在操作系统中通常将这样的程序称为进程。

当一个进程被处理器执行的时候,这个进程是活跃的,否则称之为不活跃。

操作系统通过将一个进程的状态加载到处理器中,就可以使这个进程进入活跃状态。

一个进程的页表指定了它能够在物理内存中访问的地址空间,在一个进程进行状态保存的时候,其实并不需要保存整个页表,只需将整个页表对应的PTR进行保存即可。因为每个进程都拥有全部的虚拟地址空间,因此不同进程都存在相同的虚拟地址,操作系统需要负责将这些不同的进程分配到物理内存中的不同地方,并将这些映射信息更新到页表中。

image-20250904092849308

如图所示,处理器中存在三个进程的情况,每个进程都有自己的页表。

三个进程中相同的虚拟地址VA1但PT分别将他们引射到了不同的PA,但不同的VA2,VA3,VA4映射到了相同物理地址,访问了同一个函数。这种方式实现了不同进程之间的保护和共享。

[!IMPORTANT]

为了节省硅片面积,都会把页表放到物理内存中,这样要得到一个虚拟地址对应的数据,需要访问两次物理内存。第一次访问页表,得到物理地址,第二次用整个物理地址来访问物理内存,得到需要的数据。

按照这种访问过程,需要的时间肯定会很长,因为物理内存的访问速度很慢,所以这种方式没有错误,但是效率不高,现实中会使用TLB和Cache来加快这个过程。

image-20250904093401510

事实上一个程序很难用完整个4GB的虚拟存储器空间,大部分程序只是用了很少一部分,导致页表中大部分内容都是空的,这样页表的利用效率很低。

通过多级页表,可以减少页表对于物理存储空间的占用。


多级页表

将单级页表中4MB的线性页表划分为若干个更小的页表,称他们为子页表。

处理器在执行进程的时候,不会一下把整个线性页表都放入物理内存中,而是根据需求逐步放入子页表,而且页表也不需要占用连续的物理内存空间了,提高了物理内存的利用效率。

但是由于所有的子页表是不连续放在物理内存中,所以需要一个表格来记录每个子页表在物理内存中存储的位置,称其为第一级页表和第二级页表。

image-20250904144744962

一个页表中的表项简称为PTE,当操作系统创建一个进程时,就在物理内存中为这个进程找到一块连续的4KB空间,存放这个进程的第一级页表。,并将第一级页表在物理内存中的起始地址放到PTR寄存器中。

就随着整个进程的执行,操作系统会逐步在物理内存中创建第二级页表,每次创建一个第二级页表,操作系统就要将他的起始地址放到第一级页表对应的表项中

[!IMPORTANT]

虚拟地址与页表的关系

对于32位虚拟地址,页大小为4KB(2^12,12位)的系统,页表中的表项是个数是2^20,将其分为2^10等份

image-20250904151506200

VA中的p1部分和p2部分的宽度都是10位,因此他们的变化范围都是0x000~0x3FF

每次当p1部分变化时,操作系统需要在物理内存中创建一个新的第二级页表。

当p1不发生变化,只是p2部分变化范围在0x000~0x3FF之内时,此时不需要创建新的第二季页表。

每当虚拟地址中的p2部分发生变化,就需要使用一个新的页,系统将整个新的页从下级存储器中取出来放到内存中,然后将这个页在物理内存中的起始地址填充到第二季页表对用的PTE中。