/ Windows / 99浏览

浅谈内存管理-01

引入

我们经常在任务管理器中看内存的占用,最常看到的就是【使用中(In use)】。有了解过的同学可能就立马知道,这个使用中,表示物理内存上的使用量。

没错,这个状态就是物理内存上的使用量。那我们更进一步,物理内存的各个状态是怎么转换的呢?

物理内存

所谓物理内存,就是我们平常用的内存条上的存储大小,也用 RAM 来表示。

内核中的对象

物理内存在内核中存在一个全局物理页面数组、7个物理页面链表。分别是:

  • PHYSICAL_PAGE MmPageArray[]: 这是一个全局的物理页面数组,用于表示系统中的所有物理内存。数组的大小取决于系统的物理内存大小。每个数组元素代表一个物理页面,包含有关该页面状态和使用情况的信息。
  • LIST_ENTRY FreeZeroedPageListHead: 这是一个链表,包含所有当前空闲且内容已被清零的物理页面。清零的页面可以直接用于需要初始化为零的内存请求,提高了分配效率。
  • LIST_ENTRY FreeUnzeroedPageListHead: 这个链表包含所有当前空闲但内容尚未被清零的物理页面。这些页面在分配前需要先清零,或者用于那些不要求初始内容的内存请求。
  • LIST_ENTRY UsedPageListHeads[4]: 这是一个包含四个链表的数组,用于管理不同用途的忙碌(已使用)物理页面。每个链表针对不同的消费用途,如操作系统核心、用户模式进程等,并按照LRU(最近最少使用)策略对页面进行排序。
  • LIST_ENTRY BiosPageListHead: 这个链表包含用于BIOS的物理页面。这些页面通常用于与系统的基本输入输出系统(BIOS)交互,或者存储与BIOS相关的重要信息。

特别地,物理页面数组中的索引我们称为:FPN(Page Frame Number),物理页面数组我们也称之为【页帧编号数据库(FPN Database)】

💡 应用层可以通过 Windows internals 中的 meminfo.exe -s 来查看;内核调试,可以通过 !memusage 来查看

物理页面的转换过程

  1. Process Working Set: 这是指一个进程当前在物理内存中活跃使用的页面集合。这些页面被频繁访问,因此被保留在内存中。
  2. Standby Page List: 包含那些不再属于任何进程工作集,但仍然在内存中的页面。这些页面的内容尚未更改,因此它们可以在需要时迅速重新分配给任何进程。
  3. Modified Page List: 包含那些已被修改但尚未写回磁盘的页面。Modified Page Writer负责将这些页面的更改写回磁盘。
  4. Free Page List: 包含完全未被使用的页面。这些页面可以被分配给任何需要新页面的进程。
  5. Zero Page List: 包含已经被清零的页面,即所有位都被设置为零。Zero Page Thread负责将页面清零,以便它们可以安全地分配给进程,而不会泄露任何先前的信息。

页面可以因为以下原因在不同的列表之间移动:

  • Demand Zero Page Faults: 当一个进程访问一个尚未映射到物理内存的虚拟页面时发生,操作系统将从Zero Page List中分配一个页面给该进程。
  • Soft Page Faults: 当一个进程访问的页面不在其工作集中,但在Standby List或Modified List中时发生。页面将被重新映射回进程的工作集。
  • Working Set Replacement: 当工作集变得太大,需要减少时,一些页面可能会从工作集移动到Standby List或Modified List。
  • Pages Read From Disk or Kernel Allocations: 当从磁盘读取页面或内核分配新页面时,这些页面可能会进入工作集或Standby List。
  • Private Bytes at Process Exit: 当进程退出时,其私有页面(只属于该进程的页面)会被移除,并根据它们的状态(是否已经修改)放入相应的列表。

💡 可结合 RAMMap.exe 来查看

虚拟内存

Windows 中通过一种基于平面(线性)地址空间的虚拟内存系统,让每个进程都可以觉得自己能够获得一个极大的私有空间。

地址空间布局

在 Windows 中主要有以下三类数据会被映射到虚拟地址空间去:

  • 每个进程的私有代码和数据
  • 会话范围的代码和数据
  • 系统范围的代码和数据

X86 应用地址空间布局

💡 arm 的跟 X86 基本一致。不过 arm 没有 3+1 的布局

X64 应用地址空间布局

页面

内存管理工作通过一种名为“页面”的块进行的。

硬件内存管理单元需要以页面为单位进行虚拟和物理地址的转换。

页面是最小的保护单元。

分为大页面小页面

物理页面

物理页面,也就是物理内存的块。通常我们用 PFN 来描述物理页面的索引。PFN 数据库中包含所有物理页面的状态。

虚拟页面

虚拟页面,也称为页面文件中的页面。虚拟内存系统会将虚拟地址空间分成大小相等的块,这些块称为“页面”或“虚拟页面”。

一个物理页面可以映射到N个进程的N个虚拟页面中

一个虚拟页面同一时刻只能映射到一个物理页面

每个虚拟页面又分四种映射状态:

  1. 映射着某个物理页面(已分配且已映射)
  2. 映射着某个磁盘页文件中的某个页面(已分配且已映射)
  3. 没映射到任何物理存储介质(对应的PTE=0),但是可能被预定了(已分配,但尚未映射)
  4. 裸奔(尚未分配,以上情况都不满足)

页面状态

虚拟页面的状态主要有:

  1. Free: “Free”状态的页面是尚未被分配的,即它们没有被任何进程使用。在虚拟地址空间中,这些页面既没有数据也没有保留。
  2. Reserved: 进程可以预留一段连续的虚拟地址空间,但实际上并不分配任何物理或者页面文件存储。这些页面在逻辑上是为将来使用而保留的。它们不能用于其他任何目的,直到它们被取消预留或提交。
  3. Committed: 当虚拟页面被提交时,系统保证有足够的物理存储(RAM或页面文件)来存储它们。提交的页面是有数据的,可以被读写。如果访问未映射到物理内存的已提交页面,会触发页面错误,操作系统将确保这些页面被加载到物理内存中。
  4. Shareable: 这些页面可以被映射到多个进程的地址空间,允许共享数据。例如,DLL的代码段通常是共享的,因为代码不会改变,所以不同的进程可以共享相同的物理页面,以节省内存。

提交用量和提交限制

提交量:整个系统下必须保存在物理内存中或者页面文件中的所有已提交内存分配的总量

提交限制 = 物理内存 + 分页文件当前大小

💡 内核调试下,通过 !vm 1 查看系统所有的页面文件

共享内存和映射文件

内存管理器中用于实现共享内存的底层原语名为区域对象(section object),在 Windows API 中通过文件映射对象的形式暴露

💡 共享内存(映射文件)下,如果要修改文件,就要用到写入时复制(copy-on-write)

写入时复制

写入的时候,会复制一个新的页面,进行编辑。

写入时复制时一种惰性计算(lazy evaluation)技术的一个范例,惰性计算可在必要前避免开销昂贵的操作。

💡 如果要看写入时复制的错误速率,在性能监视器中的 Memory 分类下的 Write Copies/Sec 性能计数器

查看内存使用情况

  • 任务管理器
  • Process Explorer:View → System Infomation → Memory
  • PoolMon:可以查看分页和非分页池的的使用情况
  • VMMap:可以非常详细的看到进程中的虚拟内存使用情况
  • RAMMap:可以非常详细的看到物理内存的使用情况
  • Windbg 命令:!vm、!memusage

参考:

如何通过 C++ 实时监听 ETW 事件
如何通过 C++ 实时监听 ETW 事件
【译】调查并确定 Windows 运行速度变慢问题
【译】调查并确定 Windows 运行速度变慢问题
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 采样
【译】丢失的 WPA 文档 —— CPU 采样
如何通过 PDH(Performance Data Helper) 获取性能计数器的值

0

  1. This post has no comment yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注