1.内存的痛点
内存不足【Not Enough Memory】
32位处理器架构为每个程序【Program】提供了专属自己的 32 位地址,当通过load和store 指令操作字【word】时,我们可以指定一个 32 位地址,因此,这意味着程序【Program】可以访问 32 位地址空间中的任意字节。而 32 位地址仅仅能访问 4GB【 byte】 内存。如果我们的内存不足 4GB ,会出现内存访问失败。
内存碎片
现在操作系统都是支持多道程序【multiprogramming】的,多个程序共享物理内存。
内存安全
每个程序都可以访问任意 32 位的内存地址,如果多个程序访问同一块内存地址,他们会互相影响,从而导致安全隐患。
如果所有的程序都可以访问相同的 32 位内存空间,内存存在如下问题
- 内存不足
- 内存碎片
- 内存安全
那如何解决这个问题???
任何计算机科学中的问题,都可以通过添加中间层来解决!
问题的关键点在于所有程序共享的是“相同的 32 位内存空间” ,我们可以通过为每个程序提供专属于自己的内存虚拟内存空间【virtual memory space】,然后单独的将该虚拟内存空间【virtual memory space】映射到物理内存空间【RAM memory space】上(如果内存耗尽,我们可以将其移动到磁盘中)。
2.什么是虚拟内存
在没有虚拟内存之前,程序地址【Program Address】 = 物理地址【RAM Address】。
虚拟内存作为中间层,将程序地址【Program Address】映射到物理地址【RAM Address】,来解决痛点。
下面我们来看虚拟内存是如何解决内存的三个痛点的。
内存不足【Not Enough Memory】
- 将部分程序地址空间【Program Address Space】映射到磁盘【Disk】
- 需要的话,我们可以将其加载进内存
当程序访问的内存地址超过物理内存空间时,由于虚拟内存的存在,我们可以首先通过驱逐策略,将内存中最旧的数据通过页换出【Page Out】移动到磁盘中,并更新虚拟内存页表中的映射关系,将对应的虚拟内存地址映射到磁盘。然后通过页换入【Page In】,将本次访问的数据由磁盘【Disk】移动到内存中,并更新对应的虚拟内存映射关系。
需要注意的是,磁盘的访问速度,大概是RAM速度的1000x,任何时候,当你不能把你的数据放入内存,而必须访问磁盘,你必将为此付出了巨大的性能损失。
内存碎片
多道程序设计的操作系统上,当某个程序退出时,虚拟内存是可以通过灵活的映射,解决由内存碎片导致的内存空间不连续的问题。
内存安全
虚拟内存通过将每个程序的程序地址【Program Address】分别映射到不同的物理地址,解决了多个程序同时访问同一内存地址带来的内存安全问题。
虚拟内存让我们可以对多道程序实现完全隔离,使它们无法相互共享/损坏数据。但是这也带来一个弊端,即程序之间无法共享数据了?其实不然,我们可以通过使用相同的映射,就可以实现程序间共享,即将程序地址【Program Address】均映射到相同的物理地址【RAM Address】上。
3.虚拟内存是如何工作的
基本思想:分离地址空间
- 虚拟内存【VA】:程序所看到的,它是由处理器架构决定的,在 32 位处理架构上,虚拟内存空间就是 0 ~
- 物理内存【PA】:计算机上的物理RAM,如果电脑上安装的是 2GB 内存,那么物理内存空间就是 0 ~
程序实如何访问内存的?
- 程序执行一个load命令,并指定一个虚拟地址
- 计算机将虚拟地址转换为物理地址
- 如过物理地址不存在于内存上,操作系统会将其从磁盘中加载到内存中
- 计算机使用物理地址读取RAM中的数据,并将数据返回给程序
4.页表
页表是用于追踪虚拟内存到物理内存之间映射的。对于每一个虚拟地址【Virtual Address】,页表中都存在其对应的一条页表项【Page Table Entry,PTE】。
那么,在 32 位架构上,一个页表需要有多少个页表项【Page Table Entry,PTE】呢?
答案是: ,大约为10亿条,我们假设每个页表项【PTE】只存储物理地址,用索引标识对应的虚拟地址,则大概至少占用 4GB(
) 内存。因为内存是字对齐的,在 32 位架构上,CPU 一个字的大小是 32 位,即 4 bytes。而虚拟内存总大小为
,因此
。
陷入了困境~
解决方案:将内存按照块【Block】/页【Page】来划分,而不是字【Word】。
下面以 4KB 的页来举例:
页表通过管理以块【Block】/页【Page】为单位组织的数据,解决了以字【Word】为单位组织数据带来的问题,在以 4KB 为页大小的情况下,即一页包括 1024() 个字,页表项【PTE】的总数锐减到
条,大约为 100 万条,大概占用
,大约为 4MB。
- 使用更少的页表项【PTE】就可以覆盖整个内存空间
- 但是在RAM的使用上降低了灵活性(页换入/页换出)
5.地址转换
前提:
- 32 位机器
- 256MB RAM
- 4KB 页大小
因此:
- 32 位虚拟内存地址
- 28 位物理内存地址
我们来看一下Page、Offset之间的转换示意图:
6.地址转换走查
当虚拟地址在页表欧中有映射时:
当虚拟地址在页表中无映射时:
需要注意的,Offset Size 完全由页大小控制:
- 在页大小 = 4KB 时,Offset Size 占用 12 位
- 当页大小 = 64KB 时,Offset Size 占用 16 位
7.Page Fault
当虚拟地址请求的数据不在RAM中时,发生了什么?
- 【1 cycle】页表项【PTE】说该页不在RAM中,而时再磁盘上
- 【100 cycle】硬件(即CPU)生成一个缺页异常【Page Fault Exception】
- 【10000 cycle】操作系统捕获该异常,并跳转到缺页处理程序【Page Fault Handler】清理该异常,操作系统会负责分析是哪个页导致的缺页以及它在磁盘上的位置,并将其从磁盘加载到 RAM 上
- 操作系统首先选择一个页,将其从 RAM 中驱逐出去(这里设置到内存驱逐策略,比如FIFO,LIFO)
- 【40000000 cycle】如果驱逐的页是脏的【Drity】,它需要首先写回到磁盘上
- 【40000000 cycle】操作系统将新页从磁盘中读取出来,并加载到 RAM 中
- 【1000 cycle】操作系统变更页表项【PTE】,标识其映射关系
- 【10000 cycle】操作系统跳转回导致缺页异常的指令处,此时不会再触发缺页异常,因为响应的页已经加载到了 RAM 中
因此,一个缺页,需要花费 8000 万个机器周期
缺点:触发缺页时非常慢
优点:不再会因为内存不足而崩溃
8.内存安全
如果每个应用程序都拥有专属于自己的页表,那么它可以将每个程序的虚拟地址【VA】,映射到唯一的物理地址【PA】,但是这也导致程序间无法共享数据。
在32位Linunx上,程序的地址空间如图所示:
通过虚拟内存映射,我们可以实现隔离与共享:
那么虚拟内存到底是如何提供分别映射【Separate Mapping】的呢?
- 每个进程都有其专属的页表
- 操作系统确保只有在我们希望共享时,才映射到同一物理地址
9.更快地虚拟内存
虚拟内存的开销(每个指令平均访问内存1.33次):
- 访问 RAM 中的页表
- 地址转换:VA -> PA
- 访问由物理地址指定的 RAM 内存
解决方案:页表缓存【Page Table Cache】,该技术被称为转译后备缓冲器【Translation Lookaside Buffer】
为了保证速度,转译后备缓冲器【Translation Lookaside Buffer,TLB】通常都很小:
- 将 TLB 按照指令【iTLB】和数据用【dTLB】户分开
- 通常有 64 项,4-way( 4KB 页大小)
- 或者有 32 项,4-way( 2MB 页大小)
TLB 太小了,我们如何才能使其变大,而不影响性能呢?
- 设置更大的页大小:4KB 页大小,TLB 支持 64 项 ,只能覆盖到 256KB 大小的内存,而如果使用 2MB 的页大小,TLB 支持 32 项,则能覆盖到 64MB 大小的内存
- 增加二级 TLB,许多处理器确实也是这么做的,L2 TLB 大约数 L1 TLB 的8倍,时间开销则大概是是两倍
- 硬件自动填充 TLB:这也被称为“Hardware Page Table Walk”,基本上,硬件假设页表以一种特殊的形式驻留在内存中,它可以在 TLB miss 时,从页表获取数据,而不需要进入操作系统处理流程中
10.TLB是如何工作的
前提:
- 32 位虚拟地址空间
- 4KB 页大小
TLB empty
当 TLB 为空的时候:
- 虚拟地址中的低 12 位(Virtual Page Offset)无需转换
- 虚拟地址中的高 24 位(Virtual Page Number)进入 TLB
- 因为 TLB 为空,因此触发 TLB miss
- 从 RAM 中加载页表项【PTE】到 TLB
- 完成地址转换
- 访问由物理地址所指定的 RAM
TLB miss
TLB miss + eviction
TLB miss + eviction + Disk
11.多级页表
对于一个4KB 页大小的 32 位机器而言:
- 1M 个页表项【PTE】(32 位中,12 位用于 Page Offset,剩余 20 位,
)
- 每一个页表项【PTE】大约需要占用 4 字节(20 字节用于物理页码,其他的用于授权位)
- 大概需要占用 4MB
但是因为每个程序都有其专属的页表,如果我们有100个程序,那么大概需要 ,而最艰难的是,我们不能将页表页换出【Page Out】到磁盘上!因为如果页表不在 RAM 中,我们没有办法访问它。
那如何解决这个问题???
任何计算机科学中的问题,都可以通过添加中间层来解决!
多级页表【Multi-Level Page Table】
首先是一级页表【L1 Page Table】,该页表大小为 4KB ,共计 1024 个页表项【PTE】,它用于指向其他页。
然后是二级页表【L2 Page Table】,该页表大小为 4KB ,共计 1024 个页表项【PTE】。
通过一二级页表,我们便可以将页表换出到磁盘上,不再需要始终保持在 RAM 中。
需要注意的是,一级页表必须始终驻留在 RAM 中,这样我们才能定位到其他页表!!!
多级页表是如何工作的?
12.TLB AND Cache
TLB 和 CPU Cache 可以以两种方式协作!!
物理缓存
顾名思义,即 CPU 缓存是基于物理地址缓存的:
- CPU 生成虚拟地址
- TLB 将虚拟地址映射为物理地址
- 物理地址命中 CPU Cache,则返回数据,否则继续向下走
- 根据物理地址访问 RAM
缺点:
- 慢,在访问 CPU Cache 前必须做一次 TLB 映射
虚拟缓存
顾名思义,即 CPU 缓存是基于虚拟地址缓存的
- CPU 生成虚拟地址
- 虚拟地址命中 CPU Cache,则返回数据,否则继续向下走
- TLB 将虚拟地址映射为物理地址
- 根据物理地址访问 RAMA
优点:
- 快,只有在 Cache miss 时才需要做 TLB 映射
缺点:
- 虚拟 CPU Cache 是基于虚拟内存实现的缓存,所以基于虚拟地址的保护不能让应用程序区分开来
合二为一 VIPT【Virtually Indexed Physically Tagged】
在 VIPT 中同时操作 TLB 转换【TLB Translation】和缓存查找【Cache Lookup】。
- 使用虚拟页码【virtual page number】进行 TLB 转换【TLB Translation】
- 使用页偏移【Page Offset】进行缓存查找【Cache Lookup】
- TLB 转换得到物理地址【Physically Address】
- 缓存查找获得物理标签【Physical Tag】
- 如果物理地址【Physically Address】 == 物理标签【Physical Tag】,则缓存命中
优点:
- 快, TLB 转换【TLB Translation】和缓存查找【Cache Lookup】是同时做的
- 安全,只有物理地址与物理标签匹配时才命中
缺点:
- 只能使用页偏移【Page Offset】来索引缓存,这限制了缓存大小的上限
- 目前很多处理器使用 VIPT 来实现 L1 缓存