CSSAPP稀里糊涂的读书笔记(九)虚拟内存

虚拟内存(VM)为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要的能力:1)它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。2)它为每个进程提供了一致的地址空间,从而简化了内存管理。3)它保护了每个进程的地址空间不被其他进程破坏。
本章从两个角度来看虚拟内存。前一部分描述虚拟内存是如何工作的。后一部分描述的是应用程序如何使用和管理虚拟内存。

  1. 计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址(Physical Address,PA)。给定这种简单的结构,CPU访问内存的最自然的方式就是使用物理地址。我们把这种方式成为物理寻址。然而现代处理器使用的是虚拟寻址。
    使用虚拟寻址,CPU通过生成一个虚拟地址(Virtual Address,VA)来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。将一个虚拟地址转换为物理地址的任务叫做地址翻译。
    CPU芯片上叫做内存管理单元(Memory Management Unit,MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

  2. 地址空间是一个非负整数地址的有序集合:
    {0,1,2,3,……,N-1}
    一个地址空间的大小是由表示最大地址所需要的位数来描述的。例如,一个包含N=2^n个地址的虚拟地址空间就叫做一个n 位地址空间。
    一个系统还有一个物理地址空间,对应于系统中的物理内存的M个字节,M=2^m.
    {0,1,2,……,M-1}
    虚拟内存的基本思想就是,允许每个数据对象有多个独立地址,其中每个地址都选自一个不同的地址空间。主存中的每个字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。

  3. VM系统将虚拟内存分割位虚拟页(Virtual Page,VP)的大小固定的块,每个虚拟页的大小为P=2^p字节。类似的,物理内存被分割为物理页(Physical Page,PP),大小也为P字节(物理页也被称为页帧(page frame))。
    任何时刻,虚拟页面的集合都分为三个不相交的子集:

  • 未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
  • 缓存的:当前已缓存在物理内存中的已分配页。
  • 未缓存的:未缓存在物理内存中的已分配页。
    在这里插入图片描述
  1. 物理内存中有一个叫做页表的数据结构,它将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。
    页表就是一个页表条目(Page Table Entry,PTE)的数组。虚拟地址空间中的每个页在页表中一个固定偏移量处都有一个PTE。我们假设每个PTE是由一个有效位和一个n位地址字段组成的。如下图
    在这里插入图片描述
    上图展示了一个有8个虚拟页和4个物理页的系统的页表。四个虚拟页(VP 1、VP 2、VP 4和VP 7)当前被缓存在DRAM中。两个页(VP 0 和VP 5)还未被分配,而剩下的页(VP 3 和 VP 6)已经被分配了,当时当前还未被缓存。

  2. 页命中
    当CPU想要读包含在VP 2中的虚拟内存的一个字时:
    在这里插入图片描述

  3. 缺页
    CPU 引用了 VP 3 中的一个字,VP 3 并未缓存在DRAM 中。
    在这里插入图片描述
    例子中牺牲了页 VP 4,然后将VP 3 缓存到DRAM中,如下图
    在这里插入图片描述

  4. 分配页面
    当操作系统分配一个新的虚拟内存页时
    在这里插入图片描述

  5. 地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射。
    如下图是用页表实现地址翻译
    在这里插入图片描述

  6. TLB
    每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE,以便将虚拟地址翻译未物理地址。在最糟糕的情况下,这会要求从内存多取一次数据,代价是几十到几百个周期;如果PTE缓存在L1中,那么开销就下降到1个或2个周期。这时我们使用被称为翻译后备缓冲器(Translation Lookaside Buffer,TLB)来降低开销。
    在这里插入图片描述
    TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。

  7. 多级页表
    假设我们有一个32位的地址空间、4KB的页面和一个4字节的PTE,那么即使应用所引用的只是虚拟地址空间中很小的一部分,也总是需要一个4MB的页表驻留在内存中。
    因此,我们产生了一种分级的想法
    在这里插入图片描述
    在这里插入图片描述
    这里大概看下图吧,就不详细描述了,太费口舌。

  8. Linux 通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射。虚拟内存区域可以映射到两种类型的对象中的一种:
    1)Linux文件系统中的普通文件
    2)匿名文件。由内核创建,包含的全是二进制零。
    无论在哪种情况中,一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫做交换空间或者交换区域。在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。

  9. 动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap)。
    在这里插入图片描述
    分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

  10. 造成堆利用率很低的主要原因是一种称为碎片的现象,当虽然有未使用的内存但不能用来满足分配请求时,就发生这种现象。有两种形式的碎片:内部碎片和外部碎片。
    内部碎片是在一个已分配块比有效载荷大时发生的。内部碎片的大小就是已分配块大小和它们的有效载荷大小之差的和。
    外部碎片是当空闲内存合计起来足够满足一个分配请求,但是没有一个单独的空闲块足够大可以用来处理这个请求时发生的。

  11. 隐式空闲链表
    在这里插入图片描述
    一个块是由一个字的头部、有效载荷。以及可能的一些额外的填充组成的。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。
    在这里插入图片描述
    我们称这种结构为隐式空闲链表,因为空闲块是通过头部中的大小字段隐含地连接着地。分配器可以通过遍历堆中所有地块,从而间接地遍历整个空闲块地集合。

  12. 当一个应用请求一个k 字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。分配器执行这种搜索的方式是由放置策略确定的。常见的策略是首次适配、下一次适配和最佳适配。
    首次适配从头开始搜索空闲链表,选择第一个合适的空闲块。下一次适配和首次适配很相似,只不过不是从链表的起始处开始每次搜索,而是从上一次查询结束的地方开始。最佳适配检查每个空闲块,选择适合所需请求大小的最小空闲块。
    如果匹配的不好,那么分配器通常会选择将这个空闲块分割为两部分。第一部分变成分配块,而剩下的变成一个新的空闲块。
    在这里插入图片描述

  13. 当分配器释放一个已分配块时,可能有其他空闲块与这个新释放的空闲块相邻。这些邻接的空闲块可能引起一种现象,叫做假碎片,就是有许多可用的空闲块被分割成为小的、无法使用的空闲块。
    在这里插入图片描述
    为了解决假碎片问题,任何实际的分配器都必须合并相邻的空闲块,这个过程称为合并。
    分配器可以选择立即合并,也就是在每次一个块被释放时,就合并所有的相邻块。或者它也可以选择推迟合并,也就是等到某个稍晚的时候再合并空闲块。
    在这里插入图片描述
    Knuth 提出了一种聪明而通用的技术,叫做边界标记,允许在常数时间内进行堆前面块的合并。即在每个块的结尾处添加一个脚部,脚部就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,这个脚部总是在距当前块开始位置一个字的距离。
    在这里插入图片描述

  14. 隐式空闲链表为我们提供了一种介绍一些基本分配器概念的简单方法。然而,因为块分配与堆块的总数呈线性关系,所以对于通用的分配器,隐式空闲链表是不适合的。
    在这里插入图片描述
    一种更好的方法是使用双向链表,而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。

  15. 一个使用单向空闲块链表的分配器需要与空闲块数量呈线性关系的时间来分配块。一种流行的减少分配时间的方法,通常称为分离存储,就是维护多个空闲链表,其中每个链表中的块有大致相等的大小。一般思路是将所有可能的块大小分成一些等价类,也叫做大小类。不如可以根据2的幂来划分块大小:
    {1},{2},{3,4},{5~8},……,{1025~2048},{2049~4096},{4097~∞}
    这里就不详细介绍分离存储的方法了,可以看书。

  16. 垃圾收集器是一种动态内存分配器,它自动释放程序不再需要的已分配块。这些块被称为垃圾。自动回收堆存储的过程叫做垃圾收集。
    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值