CSAPP 虚拟内存01

从物理内存到虚拟内存

一般我们把内存看做是一个由M个连续的字节大小的单元组成的数组,而这个数组的下标就是物理地址。
第一个字节的地址为0,接下来的字节地址为1,以此类推。CPU通过物理地址找到内存中的数据,这种查找数据的方式叫做物理寻址。

相对应的,通过虚拟地址找到内存中的数据,称为虚拟寻址。主要寻址流程是通过 MMU(Memory management unit)把虚拟地址(Virtual Address, VA)转换为物理地址(Physical Address, PA),再由此进行实际的数据传输。大致的过程如下图所示
在这里插入图片描述
使用虚拟内存主要是基于下面三个考虑:

  • 可以更有效率的使用内存:使用 DRAM 当做部分的虚拟地址空间的缓存
  • 简化内存管理:每个进程都有统一的线性地址空间
  • 隔离地址控件:进程之间不会相互影响;用户程序不能访问内核信息和代码

虚拟内存的三个角色

作为缓存工具

概念上来说,虚拟内存就是存储在磁盘上的 N 个连续字节的数组。这个数组的部分有可能被使用的内容,会缓存在 DRAM 中,在 DRAM 中的每个缓存块(cache block)就称为页(page),如下图所示:
在这里插入图片描述
大致的思路和之前的 cache memory 是类似的,就是利用 DRAM 比较快的特性,把最常用的数据换缓存起来。如果要访问磁盘的话,大约会比访问 DRAM 慢一万倍,所以我们的目标就是尽可能从 DRAM 中拿数据。为此,我们需要:

  • 更大的页尺寸(page size):通常是 4KB,有的时候可以达到 4MB
  • 全相联(Fully associative):每一个虚拟页(virual page)可以放在任意的物理页(physical page)中,没有限制。
  • 映射函数非常复杂,所以没有办法用硬件实现,通常使用 Write-back 而非 Write-through 机制
    • Write-through: 命中后更新缓存,同时写入到内存中
    • Write-back: 直到这个缓存需要被置换出去,才写入到内存中(需要额外的 dirty bit 来表示缓存中的数据是否和内存中相同,因为可能在其他的时候内存中对应地址的数据已经更新,那么重复写入就会导致原有数据丢失)

页表(page table)
同任何缓存一样,虚拟存储器系统必须有某种方法来判定一个虚拟页是否存放在DRAM中的某个地方。页表是一个数组,数组中的每个元素称为页表项(PTE, page table entry),页表项存储着把虚拟页映射到物理页上的数据。在 DRAM 中,每个进程都有自己的页表,具体如下:
在这里插入图片描述
因为有一个表可以查询,就会遇到两种情况,一种是命中(Page Hit),另一种则是未命中(Page Fault)。

命中的时候,代表在 DRAM 中已经缓存了对应数据,即可以访问到页表中蓝色条目的地址。可以直接访问。
不命中的时候,即访问到 page table 中灰色条目的时候,因为在 DRAM 中并没有对应的数据,所以需要执行一系列操作(从磁盘复制到 DRAM 中),具体为:

  • 触发 Page fault,也就是一个缺页异常
  • Page fault handler 会选择 DRAM 中需要被置换的 page,并把数据从磁盘复制到 DRAM 中
  • Page fault handler重新启动导致缺页的指令,这时候就会是 page hit
  • 更新页表项,使它指向DRAM新创建的页。

仔细留意上面的页表,会发现有一个条目是 null,也就是没有分配。具体的分配过程(比方说声明了一个大数组),就是让该条目指向虚拟内存(在磁盘上)的某个页,但并不复制到 DRAM,只有当出现 page fault 的时候才需要拷贝数据。

为什么不直接复制到DRAM?一个程序它所引用的页面是有可能超过DRAM的总大小的,出现这种情况时,无法将整个运行程序(进程)复制到DRAM中,所以采用这种延迟策略,也是利用了局部性原理,保证了在任意时刻,这些页面将趋向于在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set)。

作为内存管理工具

前面提到,每个进程都有自己的虚拟地址空间,这样一来,对于进程来说,它们看到的就是简单的线性空间(但实际上在物理内存中可能是间隔、支离破碎的),具体的映射过程可以用下图表示:
在这里插入图片描述
在内存分配中没有太多限制,每个虚拟页都可以被映射到任何的物理页上。这样也带来一个好处,如果两个进程间有共享的数据,那么直接指向同一个物理页即可(也就是上图 PP 6 的状况,只读数据)

虚拟内存带来的另一个好处就是可以简化链接和载入的结构(因为有了统一的抽象,不需要纠结细节)

作为内存保护工具

页表中的每个条目的高位部分是表示权限的位,MMU 可以通过检查这些位来进行权限控制(读、写、执行),如下图所示:
在这里插入图片描述

地址翻译

开始翻译之前需要了解几个内容:
TLB
每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE,这相当于多了一次对存储器的取数据。为了消除这个开销,我们再次利用到局部性原理,在MMU中设置了一个缓存,用于存储PTE,称为TLB(tranlation lookaside buffer,翻译后备缓冲器)

相关参数
在这里插入图片描述其中 N 表示虚拟地址空间中的地址数量,M 表示物理地址空间中的地址数量,P 是每一页包含的字节数(page size)。

虚拟地址(VA, Virtual Address)中的元素:

  • TLBI: TLB 的索引值
  • TLBT: TLB 的标签(tag)
  • VPO: 虚拟页偏移量
  • VPN: 虚拟页编号

物理地址(PA, physical address)中的元素:

  • PPO: 物理页偏移量(与 VPO 的值总是相同的)
  • PPN: 物理页编号

然后我们通过一个具体的例子来说明如何进行地址翻译
在这里插入图片描述
具体的访问过程为:

  • 通过虚拟地址找到页表(page table)中对应的条目
  • 检查有效位(valid bit),是否需要触发页错误(page fault)
  • 然后根据页表中的物理页编号(physical page number)找到内存中的对应地址
  • 最后把虚拟页偏移(virtual page offset)和前面的实际地址拼起来,就是最终的物理地址了

这里又分两种情况:Page Hit 和 Page Fault,我们先来看看 Page Hit 的情况
在这里插入图片描述主要有 5 步,CPU 首先把虚拟地址发送给 MMU,MMU 检查缓存(缓存没有查内存),并把从页表中得到对应的物理地址,接着 MMU 把物理地址发送给缓存/内存,最后从缓存/内存中得到数据。

而 Page Fault 的时候就复杂一些,第一次触发页错误会把页面载入内存/缓存,然后再以 Page Hit 的机制得到数据:
在这里插入图片描述
这里有 7 步,前面和 Page Hit 是一致的,先把虚拟地址发给 MMU 进行检查,然后发现没有对应的页,于是触发异常,异常处理器会负责从磁盘中找到对应页面并与缓存/内存中的页进行置换,置换完成后再访问同一地址,就可以按照 Page Hit 的方式来访问了。

到这里只用到了缓存和内存,如果加上前面所讲的TLB,会是下面这样:
在这里插入图片描述这里 VPN + VPO 就是虚拟地址,而VPN被分成两部分TLBT和TLBI,分别用于匹配标签(tag)、确定集合(index),如果 TLB 中有对应的记录,那么直接返回对应页表项(PTE)即可,如果没有的话,就要从缓存/内存中获取,并更新 TLB 的对应集合。

多层页表 Multi-Level Page Table

虽然页表是一个表,但因为往往虚拟地址的位数比物理内存的位数要大得多,所以保存页表项(PTE) 所需要的空间也是一个问题。举个例子:

假设每个页的大小是 4KB(2 的 12 次方),每个地址有 48 位,一条 PTE 记录有 8 个字节,那么要全部保存下来,需要的大小是:
在这里插入图片描述
整整 512 GB!所以我们采用多层页表,第一层的页表中的条目指向第二层的页表,一个一个索引下去,最终寻找具体的物理地址,整个翻译过程如下:
在这里插入图片描述我们假定多级页表的层数为二,来看看它是怎么节约内存的。
第一,如果一级页表中的一个PTE是空的,那么相应的二级页表就根本不会存在。这表现出一种巨大的潜在节约,因为对于一个典型的程序,4GB的虚拟地址空间的大部分都会是未分配的。
第二,只有一级页表才需要总是在主存中。虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力。只有最经常使用的二级页表才需要缓存在主存中。

地址翻译实例

来看一个简单的例子,我们的内存系统设定如下:

  • 14 位的虚拟地址
  • 12 位的物理地址
  • 页大小为 64 字节

TLB 的配置为:

  • 能够存储 16 条记录
  • 每个集合有 4 条记录

系统本身缓存(对应于物理地址):

  • 16 行,每个块 4 个字节
  • 直接映射(即 16 个集合)

在这里插入图片描述TLB 中的数据为
在这里插入图片描述
页表中的数据为(一共有 256 条记录,这里列出前 16 个)
在这里插入图片描述

缓存中的数据为
在这里插入图片描述一定要注意好不同部分的所代表的位置,这里我也会尽量写得清楚一些,来看第一个例子:

虚拟地址为 0x03D4

具体的转换过程如下图所示:
在这里插入图片描述
具体来梳理一次:

先看 TLB 中有没有对应的条目,所以先看虚拟地址的第 6-13 位,在前面的 TLB 表中,根据 TLBI 为 3 这个信息,去看这个 set 中有没有 tag 为 3 的项目,发现有,并且对应的 PPN 是 0x0D,所以对应到物理地址,就是 PPN 加上虚拟地址的 0-5 位。MMU解析完得到物理地址后,就将物理地址发给缓存,在缓存中找到相对应的物理地址(利用 cache memory 的机制),就可以获取到对应的数据了。

下面的例子同样可以按照这个方法来进行分析

虚拟地址为 0x0020

在这里插入图片描述这里的例子只使用了一级页表,实际多级页表会将VPN分成多段,第一级VPN1指向一级页表PTE,第二级VPN2指向第二级PTE,以此类推,假设为二级页表,查询流程变为:

  • VPN1查询到一级页表PTE,表示指向对应的二级页表的起始地址
  • 在二级页表起始地址开始,VPN2查询到二级页表PTE
  • 二级页表指向PPN,查询出PPN
  • PPN加上PPO(等于VPO),得到物理地址
虚拟存储器系统

前面讲了系统给每个进程维护了一个虚拟地址空间,在链接章节已经展示了一个进程需要哪些结构,其实在虚拟地址也是类似的:
在这里插入图片描述
操作系统为每个进程通过一个任务结构(task struct)维护这个虚拟空间地址,task struct的元素包含或者指向内核运行该进程所需要的所有信息。例如,PID、指向用户栈的指针、可执行目标文件的名字,程序计数器等。‘

task_struct中的一个条目指向mm_struct,它描述了虚拟存储器的当前状态。我们感兴趣的两个字段是pgd和mmap,其中pgd指向页面目录表的基址,而mmap指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的一个区域(area)。当内核运行这个进程时,它就将pgd存放在PDBR控制寄存器中。

为了我们的目的,一个具体区域的区域结构(vm_area_struct)包含下面的字段:

  • vm_start:指向这个区域的起始处。
  • vm_end:指向这个区域的结束处。
  • vm_port:描述这个区域内包含的所有页面的读写许可权限。
  • vm_flags:描述这个区域内的页面是否是与其他进程共享的,还是这个进程私有的(还描述了其他一些信息)。
  • vm_next:指向链表中下一个区域结构。

task struct在内核内表示为任务,任务可以是线程或者是进程(线程在操作系统层面又被看做轻量级进程),内核通过两个id来区分线程或进程,process id(pid)和thread group ID(tgid):

  • 任何一个进程,如果只有主线程,那 pid 是自己,tgid 是自己,group_leader 指向的还是自己。
  • 但是,如果一个进程创建了其他线程,那就会有所变化了。线程有自己的 pid,tgid 就是进程的主线程的 pid,group_leader 指向的就是进程的主线程。

下面这个图是进程管理 task_struct 的的结构图,前面讲了这个图的任务ID和内存管理部分:
在这里插入图片描述

存储器映射(memory mapping)

关联进程中的1个虚拟内存区域 和 1个磁盘上的对象,使得二者存在映射关系,称为memory mapping(mmap),映射的过程为

  • 1.初始化该虚拟内存区域
  • 2.虚拟内存区域被初始化后,虚拟内存区域被初始化后,就会在交换区(swap area)之间换来换去。
  • 3.被映射的对象称为:共享对象(普通文件 / 匿名文件

若存在上述映射关系,则具备以下特征:在多个进程的虚拟内存区域已和同1个共享对象建立映射关系的前提下,若其中1个进程对该虚拟区域进行写操作,那么对于也把该共享对象映射到其自身虚拟内存区域的进程也是可见的。
在这里插入图片描述
这只针对读操作,对于写操作,进程1对共享数据进行修改了,如果进程2还是用这一份数据,就会出现数据不一致的情况。这里系统使用了CopyOnWirte的技术,也就是写时复制(fork函数也使用了这一特性),如果一个进程对共享数据进行改写,此时会复制一份新数据再进行改写,那这份数据就已经不算共享数据了。这里也体现了延迟改写的思想。

使用mmap的好处:

  • 提高数据的读、写和传输的时间性能, 减少了数据拷贝次数, 用户空间和内核空间的高效交互(通过映射的区域直接交互),用内存读写代替I/O读写
  • 提高内存利用率:通过虚拟内存 和 共享对象
总结

这一讲介绍了虚拟内存,理解了虚拟内存在缓存、内存管理与保护中所扮演的角色。由于篇幅问题,关于动态内存分配在下一篇文章中介绍。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值