虚拟内存是现代操作系统中
最伟大的发明之一
。它为每个进程提供了一个一致的、私有的地址空间,让每个进程产生了一种自己在
独享主存
的错觉。
为了讲清楚MMU是如何一步一步完成地址翻译,取出数据的,本篇文章在前4节中讲解了虚拟内存中一些重要的概念,比如,
虚拟内存的作用,页命中,缺页异常处理,为什么需要TLB
等等。最后,通过两个地址翻译的例子,详细解释了MMU地址翻译的过程。
1. 什么是虚拟内存?
虚拟内存能够创建一个连续的更大的空间给进程使用,出现的原因是由于
主存的空间是有限
。
当运行多个进程或者一个进程需要更大的空间进行存储运行,主存显然是不够的,这个时候就需要更大更便宜的磁盘进行保存一部分数据。
对于进程来说,
虚拟内存就是一张连续的内存空间
,这个空间有些在主存中,有些在磁盘中。
2. 虚拟内存的作用
虚拟内存将主存看成是一个存储在
磁盘上的地址空间的高速缓存
,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,可以
高效地使用主存
。
虚拟内存为
每个进程提供了一致的地址空间
,简化了内存管理。
虚拟内存保护了每个进程的地址空间不被其他进程破坏。
3. 虚拟内存与物理内存
3.1 CPU存取数据
我们先来看下,CPU是如何根据地址取得数据的。
CPU 在这里生成的物理地址为 4,把地址发送给内存,然后内存从该地址获取其中保存的字,最后将其发送回 CPU。
MMU(Memory Management Unit)叫做内存管理单元,主要用来管理
虚拟内存与物理内存的映射
,由硬件自动完成。
3.2 物理地址常用术语
这里需要比较烧脑地介绍几个名词,后面
理解MMU地址翻译的时候会用到
。
物理内存(physical memory)
,主存RAM,实际能使用的物理空间。
物理页(physical page)
,把物理内存按照页表的大小进行划分。
物理地址(physical
address,PA)
, 物理内存划分了根据物理页划分为很多块,通过物理地址进行定位。
物理页号
(
physical
page number,PPN
) ,定位缓存中的数据字。
物理页号偏移
(
physical
page offset, PPO
),定位缓存中的数据块。
缓存标记(cache tag,CT)
,在高速缓存中作为行匹配。
缓存索引(cache index,CI)
,在高速缓存中作为组索引。
缓存偏移(cache offset,CO)
,在高速缓存中用作行内偏移来选择目的数据块。
物理页号偏移PPO
= 组索引CI + 行内偏移CO。
物理页号PPN
= 行匹配CT。
物理地址 PA
= 物理页号 + 物理页号偏移 = PPN * page size + ppo。
3.3 虚拟地址常用术语
虚拟内存(virtual memory)
,每个程序独有,存放在磁盘上,由多个虚拟页(VP, virtual page)组成。
虚拟内存的地址编码称
虚拟地址空间(virtual address space VAS)
,跟物理内存一样,但虚拟内存是每个进程独有的,其大小是根据操作系统的指令集位有关,如32位,64位,32位,每个进程就有4G,64位有个百亿的GB。
虚拟页(virtual page,VP )
,把虚拟内存按照页表的大小进行划分。
虚拟地址(virtual address)
,通俗说是计算机进程加载地址的指令,进程给的虚拟地址通过MMU进行获取地址计算物理地址空间,然后获取物理地址对应的数据传送到CPU上。
虚拟页号(virtual page number ,VPN)
,用于定位页表的PTE。
虚拟页号偏移(virtual page offset VPO)
,跟PPO值一样,定位物理内存的地址。
TLB索引(TLB index,TLBI)
,在页表中作为组索引。
TLBT标记(TLB tag,TLBT)
,在页表中作为行匹配。
虚拟页号VPN
= TLBT + TLBI。
虚拟地址 VA
= 虚拟页号 + 虚拟页号偏移 。
3.4 页表常用术语
页表(page tables)
,虚拟地址与物理地址的
对应表
集合。进程虚拟地址转换成物理地址,程序需要用到数据放在物理主存或磁盘某个位置,页表是存储在主存中。
页表条目(page table entry PTE)
,虚拟地址与物理地址具体
对应记录
。页表是由多个页表条目PTE组成的数组,PTE 由一个有效位 和 n位地址字段组成,如果设置了有效位,那么地址字段就标识DRAM中相应的物理页的起始位置。
3.5 页命中/缺页
处理器产生一个虚拟地址。
MMU生成PTE地址,并从高速缓存/主存请求得到它。
高速缓存/主存向MMU返回PTE。
MMU构造物理地址,并把它传送给高速缓存/主存。
高速缓存/主存返回所请求的数据字给处理器。
处理器产生一个虚拟地址。
MMU生成PTE地址,并从高速缓存/主存请求得到它。
高速缓存/主存向MMU返回PTE。
PTE中的有效位是零,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘。
缺页处理程序页面调入新的页面,并更新内存中的PTE。
缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面现在缓存在物理内存中,所以就会命中,主存就会将所请求字返回给处理器。
4. 为什么有了高速缓存,还需要TLB呢?
局部性原则保证了在任意时刻, 程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集或者常驻集。
换句话说, 局部性原则揭示了一个现象:在一段时间内,我们会反复调入或调出同一个或几个虚拟页页面。而且,每次CPU产生一个VA时, MMU就必须查阅PTE,以便将VA翻译为PA, 注意是每次,所以开销很大。
解决方法: 为了消除这样的开销,在MMU中包括了一个关于PTE的小缓存,称为翻译后备缓冲器,TLB(Translation Lookaside Buffer)。
关键点: 所有的地址翻译步骤都是在芯片上的MMU中执行的, 因此执行速度非常快。
说了这么多,下面就是本文的重点,我们看两个例子,虚拟地址是如何转换为物理地址的。
5. MMU是如何完成地址翻译的?
5.1 准备工作
5.1.1 内存系统的基本条件
假设我们有一个简单内存系统,我们做出如下规定:
虚拟地址(VA):14 位
物理地址(PA):12 位
页面大小:64 字节
虚拟页号(VPN):8位
虚拟页面偏移量(VPO):6 位(64 = 2^6)
物理页号(PPN):6位
物理页偏移量(PPO):6位
5.1.2 TLB
假设TLB 有 16 个条目,并且是 4 路组相连的。TLB 缓存的是页表条目,
页表条目是虚拟页号的唯一标识
。所以,我们只需要用虚拟页号去访问 TLB。
我们使用 VPN 的
低两位(2^2=4)作为组索引
。剩下的
6位作为标记位
。然后用不同的值来初始化 TLB。
左边的红色区域(第一个列)并不是 TLB 的条目,
仅仅是为了方便区分是哪一组
。
我们只根据
索引来查找组
,每一个条目都有一个标记位。一个 TLB 条目如果有效,它就含有一个物理地址。
5.1.3 页表
现在,我们还需要页表。假设,图中是我们页表的前 16 个条目。每一个页表有一个
物理页号
和一个
有效位
。
如果有效位有效,则表示那个虚拟页面对应的物理页面在内存中,并且 PPN 项给出了对应的物理页号。
5.2 产生虚拟地址
假设 CPU 执行了一条指令,它产生了一个有效地址 0x3d4。它把这个地址传递给了 MMU。
我们需要找出对应的
物理地址
,然后从缓存或内存中
取出数据
。
在这个例子中,
虚拟页面偏移
(VPO)是0x24,
虚拟页号
(VPN)是 0xf,
TLB 索引
(TLBI)是虚拟页号的低两位是 0b11,也就是 0x3。
TLB 标记位
(TLBT)是 3。
MMU 做的第一件是就是
查询 TLB
,所以,我们先取出
索引位
,值为 3。
我们找到
第 3 组
,我们在第 3 组中找
标记位为 3 的表项
。
遍历这 4 个条目,有一个
标记位为 7
的项,但它不是我们想要的,它的
有效位为 0
。再往后找,找到一个
标记位为 3 并且有效位为 1
。
所以,我们在 TLB 中找到了页表条目。页表条目返回这个值。MMU 返回的
物理页号是 0x0D
。
5.3 构造物理地址
现在我们可以构造物理地址,PPO的值总是等于VPO的值,可以直接拷贝过来,为0x24。
PPN的值从 TLB 缓存的 PTE 中得到,为0x0d。合在一起构成了
物理地址 0x354
。
下一步是使用这个物理地址去看高速缓存中有没有这个物理地址的缓存。
5.4 遍历高速缓存
把 0x354送入高速缓存,请求高速缓存返回对应物理地址上的值,在这个例子中,我们只需要返回一个字节。
高速缓存收到请求后,首先去检查高速缓存中
是否有块缓存了该字节
。
高速缓存先取出物理地址的
索引位
是 0b00101,也就是 0x5。
接着去第 5 组找。找标记位为 0xd 的项,有一个匹配的标记位且
有效位为 1
。这就是我们要在高速缓存中找的项。
偏移量是 0,所以我们去请求第五组偏移量为 0 的字节,值为 0x36。
缓存命中,高速缓存把这个字节返回给 MMU, MMU 把它传递给处理器。最后处理器可能把这个字节存储在一个寄存器里。
以上就是一个完整的地址翻译的例子,在这个例子中,并没有出现缺页的情况。
下面我们看一个在缺页异常处理中,是如何完成地址翻译的。
5.5 缺页处理
好了,我们来看下一个例子。这次 CPU 发送给 MMU 的虚拟地址是 0x0020。
和之前的例子一样,我们可以得到
VPN
为0x00,
VPO
为0x01,
TLBI
为0,
TLBT
为0x00。
第一步是检查
TLB 看是否有页表条目的缓存
。
在 TLB 中,如果缓存存在,它应该在第一组,并且它的
标记位应该为 0
。所以,我们在第 0 组内找标记位为 0 的项。
第一项是 0x03,不匹配,第二项是 0x09,不匹配,第三项是 0x00,匹配,但是
有效位为 0
。所以,这次
TLB 缓存不命中
。
查找缓存失败了,我们只能去内存中去
读取页表中对应的页表条目
。
查看页表,寻找虚拟页号为 0 的项。检查对应的页表条目,
看虚拟页是否在内存中
。
虚拟页号为 0 的项的
有效位为1
,我们就可以得到一个
物理页号为0x28
。根据物理页号和物理页面偏移量就可以构造出物理地址。
现在 MMU 拥有了物理地址,就可以将其发送到高速缓存。并请求高速缓存返回对应的物理地址上的一个字节。
高速缓存得到了这个物理地址。它取出对应的索引位,在这个例子中是 0x8。
所以我们去高速缓存的第八组,然后寻找对应的标记位,在这个例子中是0x28。
第八组有一个条目,它的标记位是 24,这里是一次
缓存不命中
。
6. 总结
虚拟存储器的工作原理是有一些复杂,本文描述的也并不全是最真实的计算机中的工作方式,比如,PTE由一个有效位和一个地址字段组成其实是为了便于理解而假设出来的。
但是这种方式成功的
解决了直接使用物理内存会出现的问题
。比如,虚拟内存中连续存储
解决了物理内存碎片化,资源利用率过低的问题
;每个进程只能访问自己独立的用户空间而内核空间是共用的
解决了进程间的安全问题
;缺页异常和选择牺牲页的算法
提高了内存读写的效率
等等。