0%

虚拟内存

**转载请注明出处!http://joakimliu.github.io/2018/03/16/Virtual-Memory/ 谢谢! **

地址空间

应用程序需要一个简单的执行环境(地址空间+CPU),地址空间我们可以把它想象成一个很大的数组,每个数组的元素就是一个字节,这个数组大小由地址空间的长度决定。比如 32 位的地址空间大小为 2^32 byte = 2^22 KB = 2^12 MB = 2^2 GB = 4BG,用16进制表示就是 0x00000000~0xFFFFFFFF,32位 iPhone 机型里面的应用地址空间如下图所示。地址空间分为两种:虚拟地址空间(Virtual Address Space)和物理地址空间(Physical Address Space)。物理地址空间是实实在在存在的,可以把它理解为物理内存,如 32 位的机器,它的物理空间就是 4GB。而虚拟地址空间是虚拟的,人们想象出来的,它并不存在。

Address Space Fundamentals

如果程序直接运行在物理空间上会怎样呢?假设我们的计算机有 128M 内存,有三个程序A、B、C,其中 A 需要 10M,B 需要 100MB,C 需要 20MB。如果同时运行程序 A 和 B,那么比较直接的做法就是将内存前 10M 分给程序 A,接着把 10MB~110MB 分给程序 B。但是这样有以下问题:

  • 地址空间不隔离 所有程序都直接访问物理地址,它们所使用的内存空间不是相互隔离的。其他恶意的程序可以轻易的修改破坏其他程序的内存数据。
  • 内存使用率低 没有有效的内存管理机制,通常要执行一个程序时,就将整个程序装入内存然后开始执行。上面那个例子,如果我要执行程序 C,那么就得把其他程序的数据先写到磁盘中,等要用的时候再读回来。由于程序所需要的空间是连续的,所以这里只能将程序 B 换出到磁盘。大量的数据换入换出,导致效率十分低下。
  • 运行的地址不确定 程序每次需要装入运行时,我们都需要给它从内存中分配一块足够大的空间区域,而这块空间区域的位置是不确定的。
  • 避开物理地址空间的限制 如果我们要同时运行程序 ABC 呢。

按照计算机领域的名言 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。所以我们这里可以增加一个中间层:虚拟内存地址。但是这样,内存使用率低的问题还没处理。根据程序的局部性原理,当一个程序运行时,在某个时间段,它只是频繁的用到了一小部分数据。所以我们就采取更小粒度的内存分割和映射方案:分页(paging)。即将物理空间地址等分成固定大小的页,每一页的大小由硬件决定,在 iOS 中,每页是 4KB。我们提到了两个概念:虚拟内存地址和分页。还有其他的陌生概念等着我们去了解。

虚拟内存

虚拟内存允许一个操作系统避开物理内存(4GB)的限制。虚拟内存为每个进程创建了一个逻辑地址空间(也可以理解为虚拟地址空间),它将逻辑地址空间划分成同等大小的内存块,被称为 pages 。处理器和它的内存管理单元(memory management unit (MMU))维持一个 page table 表(从程序的逻辑内存映射到电脑的RAM硬件内存)。当程序代码访问内存地址的时候,MMU 用 page table 将逻辑内存转化为实际的硬件内存,这个过程对于运行的程序是自动并且透明的。

Virtual Memory

从 WWDC2012 session 242 里的这张虚拟内存图,我们可以看出其他几个概念:region、VM Object、Resident Memory、Free Memory、Heap。我们一个一个来了解(Heap 下次讲解)。

page

逻辑内存地址一直有效,如果一个程序访问的逻辑内存地址没有相对应的物理地址时,page fault 就会发生。当它发生时,虚拟内存系统马上会调用一个特殊的 page-fault handler 处理器去响应它。page-fault handler 会停止当前代码的运行,在物理内存中找到 free page,然后从磁盘中加载包含所需数据的这个 page,并且马上返回去控制程序的代码,这样就能正常的获取内存地址了,这个过程就叫 paging。当然很有可能物理内存中没有可用的 free pages,那么 page-fault handler 会马上释放一个存在的 page,给新的 page 腾出空间。当然系统怎样释放 pages 取决于平台。在 OS X 中,虚拟内存系统经常将 pages 写入 backing store(可以理解为文件交换,它就是一个进程里,用磁盘作为的一个仓库,里面存储的是内存 page 的拷贝)。将数据从物理内存移到 backing store,称为 paging in (or “swapping in”);反之将数据从 backing store 移到物理内存,称为 paging in (or “swapping in”)。但是 iOS 中是没有 backing store 的,所以不存在 paging out。下面详细讲解 paging in out。

region

一个进程的逻辑地址空间由内存的映射区域(region)组成。 每个映射内存区域包含多个虚拟内存页(page)。每个区域有特殊的属性控制,如继承,写保护,是否联动(wired)等。由于区域包含很多页,它们是页对齐的,意味着区域的起始地址也是一个页的起始地址,结束地址也是一样的。

VM Object

内核用一个 VM 对象与逻辑地址空间的每个区域关联起来。内核用 VM 对象去跟踪和管理相关区域的居住(resident)和不居住(nonresident)的页(page)。区域能够在文件系统里面映射到 backing store 的一部分或者 memory-mapped 文件。每个 VM 对象包含一个用 the default pager 或者 the vnode pager 关联区域的 map。这跟前面的映射一一对应, the default pager 对应 backing store,the vnode pager 对应 memory-mapped 文件。除了映射区域到 default 或者 vnode pager,一个 VM 对象也映射区域到另一个 VM 对象。 内核用这个自己引用计数去实现 copy-on-write 区域。copy-on-write 区域允许多个不同的进程去共享一个页,只要它们没有写数据到这个页。当一个进程打算向这个页进行写操作时,当它写的时候,这个页的拷贝在这个进程的逻辑地址空间就被创建了。copy-on-write 区域常用于从系统框架里面加载页。读写锁也是这个原理。

Resident Memory

也叫联动(wired)内存,它存储内核的代码和数据结构,不能发生 page out 行为。应用,框架以及其他用户级别的软件不能创建联动内存,但是它们能够影响任何时候已经存在的联动内存。

Free Memory

顾名思义,就是没有被占用,随时可以拿来用的内存。

Clean memory

在磁盘中有备份的,能够再次读取。包括:

  • system framework
  • binary executable of your app
  • memory mapped files

Dirty memory

所有非 clean memory 都是的,它不能被系统回收,包括

  • Heap allocations
  • decompressed images
  • database caches

在 iOS 中发生内存警告时,我们要在相关警告方法里面处理这些内存,不然应用会被系统杀掉的。

Page Lists

内核维持和查询三种物理内存页面系统级别的数组

  • The active list contains pages that are currently mapped into memory and have been recently accessed.
  • The inactive list contains pages that are currently resident in physical memory but have not been accessed recently. These pages contain valid data but may be removed from memory at any time.
  • The free list contains pages of physical memory that are not associated with any address space of VM object. These pages are available for immediate use by any process that needs them.

这三种是相互影响的,例如

  • 当 free list 低于一定阈值时,它会从 inactive list 里面拉去一些页过来;
  • 当一个页被访问时,它会被放到 active list 的后面;
  • 当 inactive 页的数据最近没有写入到 backing store,在它可以放在 active list 里面之前,它必须 page out 到磁盘;

Paging Out Process

当 free list 里面的页面数量低于一定阈值时,内核会通过将 inactive pages 从内存中移除,增加 free list 数量,此时会发生 page out 行为(而 iOS 中是没有这种行为的,直接就是内存警告)。相关步骤如下

  • If a page in the active list is not recently touched, it is moved to the inactive list.
  • If a page in the inactive list is not recently touched, the kernel finds the page’s VM object.
  • If the VM object has never been paged before, the kernel calls an initialization routine that creates and assigns a default pager object.
  • The VM object’s default pager attempts to write the page out to the backing store.
  • If the pager succeeds, the kernel frees the physical memory occupied by the page and moves the page from the inactive to the free list.

Paging In Process

当获取的虚拟内存没有映射到物理内存时,就会出现错误,包括两种:

  • A soft fault occurs when the page of the referenced address is resident in physical memory but is currently not mapped into the address space of this process.
  • A hard fault occurs when the page of the referenced address is not in physical memory but is swapped out to backing store (or is available from a mapped file). This is what is typically known as a page fault.

而我们上面所说的 paging,就是 hard fault。

总结

我们对虚拟内存空间有了一点的理解,但是里面有各种内存,现在对它们做个总结。

  • free 空闲内存,随时都可以用的,当这个内存降到了一定阈值,系统内核会进行一些操作,例如从 inactive 拉取一些页过来,发生 page out;
  • inactive 不活跃的内存,但可被分页;
  • active 活跃的内存,已使用的内存,但是可被分页;
  • wire(resident) 已使用的内存,且不可被分页的,不能发生 page out;
  • clean 在磁盘中有备份的,能够再次读取;
  • dirty 所有非 clean memory 都是的,它不能被系统回收,我们创建在堆上的大部分内存都属于这种。

它们之间的关系为:

1
2
virtual memory = clean memory + dirty memory + free memory(下面 stackoverflow 的链接没有包括它,但从 WWDC 2012 Session 242 的 PDF 上看,应该是包括的,见下图)
resident memory = dirty memory + clean memory that loaded in physical memory(如果 App 出现低内存警告了,首先想到的应该是它)

Managing System-Wide Memory

参考链接