深入理解计算机系统 第九章

虚拟内存


虚拟寻址

CPU 通 过生成 一个虚 拟地址 (Virtual AddressÿVA) 来访 问主存 ,这个虚拟 地址在 被送到 内存之 前先转 换成适 当的物 理地址 。将一 个虚拟 地址转 换为物 理地址的任务 叫做地 址翻译 (address translation)ÿ 就像异 常处理 一样, 地址翻 译需要 CPU 硬件和 操作系 统之间 的紧密 合作。 CPU 芯片 上叫做 内存管 理单元 (Memory ManagementUnit,MMU) 的专用 硬件, 利用存 放在主 存中的 查询表 来动态 翻译虚 拟地址 ,该表 的内容 由操作系统 管理。

虚拟内 存被组 织为一 个由存 放在磁 盘上的 JV 个连 续的字 节大小 的单元组成 的数组 。每字 节都有 一个唯 一的虚 拟地址 ,作为 到数组 的索引 。磁 盘上 数组的 内容被缓存在 主存中 。和 存储 器层次 结构中 其他缓 存一样 ,磁盘 (较 低层) 上的数 据被分 割成块 ,这 些块作 为磁盘 和主存 (较 高层) 之间 的传输 单元。 VM 系统通 过将虚 拟内存 分割为 称为虚拟页 (Virtual Page, VP) 的大小 固定的 块来处 理这个 问题。

在任 意时刻 ,虚 拟页面 的集合 都分为 三个不 相交的 子集:

未分配的: VM 系统还 未分配 (或者 创建) 的页。 未分配 的块没 有任何 数据和 它们相关联 ,因此 也就不 占用任 何磁盘 空间。

缓存的 :当前 已缓存 在物理 内存中 的已分 配页。

未缓存的: 未缓存 在物理 内存中 的已分 配页。

VM 简化 了链接 和加载 、代码 和数据 共享, 以及应 用程序 的内存 分配。


简化内 存分配

虚拟 内存为 向用户 进程提 供一个 简单的 分配额 外内存 的机制 。当一个运行 在用户 进程中 的程序 要求额 外的堆 空间时 ( 如调用 malloc 的结果 ), 操作系统分配 一个适 当数字 (例如 个连 续的虚 拟内存 页面, 并且将 它们映 射到物 理内存中任意 位置的 k个任 意的物 理页面 。由 于页 表工作 的方式 ,操 作系统 没有必 要分配k个 连续的 物理内 存页面 。页 面可以 随机地 分散在 物理内 存中。

内存映射

Linux 通过 将一个 虚拟内 存区域 与一个 磁盘上 的对象 (object) 关 联起来 ,以初 始化这个 虚拟内 存区域 的内容 ,这 个过程 称为内 存映射 (memory mapping)ÿ 虚拟 内存区 域可以映射到 两种类 型的对 象中的 一种:

1) Linux 文件 系统中 的普通 文件: 一个区 域可以 映射到 一个普 通磁盘 文件的 连续部分 ,例如 一个可 执行目 标文件 。文 件区 (section) 被分 成页大 小的片 ,每一 片包含 一 个虚拟页 面的初 始内容 。因 为按需 进行页 面调度 ,所 以这些 虚拟页 面没有 实际交 换进人 物理内存 ,直到 CPU 第一 次引用 到页面 ( 即发射 一个虚 拟地址 ,落在 地址空 间这个 页面的 范围之内 )。 如 果区域 比文件 区要大 ,那么 就用零 来填充 这个区 域的余 下部分。

2) 匿名文 件:一 个区域 也可以 映射到 一个匿 名文件 ,匿名 文件是 由内核 创建的 ,包含的全 是二进 制零。 CPU 第一次 引用这 样一个 区域内 的虚拟 页面时 ,内核 就在物 理内存中找到 一个合 适的牺 牲页面 ,如 果该 页面被 修改过 ,就 将这个 页面换 出来, 用二进 制零覆盖 牺牲页 面并更 新页表 ,将 这个页 面标记 为是驻 留在内 存中的 。注意 在磁盘 和内存 之间并没有实 际的数 据传送 。因 为这 个原因 ,映 射到匿 名文件 的区域 中的页 面有时 也叫做 请求二进制 零的瓦 (demand-zero page)。

无论 在哪种 情况中 旦一个 虚拟页 面被初 始化了 ,它就 在一个 由内核 维护的 专门的交 换文件 (swap file) 之间换 来换去 。交换 文件也 叫做交 换空间 (swap space) 或者交 换区域需 要意识 到的很 重要的 一点是 ,在任 何时刻 ,交 换空间 都限制 着当前 运行着的 进程能 够分配 的虚拟 页面的 总数。

一个对 象可以 被映射 到虚拟 内存的 一 个区域 ,要么 作为共 享对象 ,要 么作为 私有对象 。如 果一个 进程将 一个共 享对象 映射到 它的虚 拟地址 空间的 一个区 域内, 那么这 个进程对这个 区域的 任何写 操作, 对于那 些也把 这个共享对象 映射到 它们虚 拟内存 的其他 进程而言 ,也是 可见的 。而且 ,这 些变化 也会反 映在磁 盘上的 原始对 象中。

另 一方面 ,对 于一个 映射到 私有对 象的区 域做的 改变, 对于其 他进程 来说是 不可见的 ,并 且进程 对这个 区域所 做的任 何写操 作都不 会反映 在磁盘 上的对 象中。 一个映 射到共享对 象的虚 拟内存 区域叫 做共享 区域。 类似地 ,也 有私有 区域。

私有 对象使 用一种 叫做写 时复制 (copy-on-write) 的巧 妙技术 被映射 到虚拟 内存中 。

动态内存分配

动态 内存分 配器维 护着一 个进程 的虚拟 内存区域, 称为堆 (heap) 。

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


分 配器有 两种基 本风格 。两 种风格 都要求 应用显式地 分配块 。

显式 分配器 (explicit allocator), 要 求应用 显式地 释放任 何已分 配的块 。例如 , C 标准库 提供一 种叫做 mall0C 程 序包的 显式分 配器。 C 程序通 过调用 malloc 函数来.分配 一个块 ,并通 过调用 free 函数来 释放一 个块。 C++ 中的符与 C 中的 malloc 和 f ree 相当。

隐式 分配器 (implicit allocator)ÿ 另 一方面 ,要求 分配器 检测一 个已分 配块何 时不再被程序 所使用 ,那么 就释放 这个块 。隐 式分配 器也叫 做垃圾 收集器 , 而自 动释放 未使用 的已分 配的块 的过程 叫做垃 级收集 (garbage collection)ÿ例如 ,诸如 Lispÿ ML 以及 Java 之类 的高级 语言就 依垃 圾收集 来释放 已分配的块。

使用动态内存的原因:

程序 使用动 态内存 分配的 最重要 的原因是经常 直到程 序实际 运行时 ,才 知道 某些数据结构 的大小 。

隐式空 闲链表:

通过标记来标记出当前块是空闲还是使用,因 为空闲 块是通 过头部 中的大 小字段 隐含地 连接着的 。分 配器可 以通过 遍历堆 中所有 的块, 从而间 接地遍 历整个 空闲块 的集合 。

显式空 闲链表:

明确通过某种数据结构来连接空闲块。

垃圾收集

垃圾 收集器 (garbage collector) 是一 种动态 内存分 配器, 它自动 释放程 序不再 需要的已 分配块 。这些 块被称 为垃圾 (garbage) (因 此术语 就称之 为垃圾 收集器 )。 自动回 收堆存储 的过程 叫做垃 圾收集 (garbage collection)ÿ 在 一个支 持垃圾 收集的 系统中 ,应用 显式分配堆块 ,但是 从不显 示地释 放它们 。

收集方式分两种:

1.引用计数

2.可达性分析,书中介绍的这种

C 程序中常见的与内存有关的错误

间接引用坏指针

读未 初始化的内存

允许栈缓冲区溢出

假设指 针和它 们指向 的对象 是相同小的

造成错 位错误

引 用指针 ,而不是它 所指向 的对象

误解指 针运算

引用 不存在 的变量

引 用空闲 堆块中 的数据

引起内 存泄漏

具体解释查看书中内容和示例

你可能感兴趣的:(深入理解计算机系统 第九章)