术语

主存(main memory)

通常是指物理内存,可以任务是计算机数据存储速度比较快的区域,通常使用DRAM。更多细节:百度百科、维基百科。

虚拟内存(virtual memory)

主存的一种抽象,通常是无限和非竞争的,虚拟内存不是真正的内存。

常驻内存(resident memory)

当前驻留在主存中的内存。

匿名内存(anonymous memory)

没有文件系统地址或者路经与之对应的内存,它包含一个进程地址空间中的工作数据,通常叫做堆(heap)。

地址空间(address space)

内存上下文。对于每一个用户进程和内核来说都有虚拟地址空间。

段(segment)

一个被标记为特殊用途的虚拟地址区域,例如存储可执行或可写的内存页。

OOM

内存溢出,当内核探测到可用内存比较少时。

页(page)

操作系统或者CPU使用的一个内存单元。历史上是4k或者8k,现代操作系统支持更多更大的页大小。

缺页异常(page fault)

非法内存访问。通常发生在虚拟内存按需满足的情况,比如Linux中的COW。

换页(paging)

内存页在主存和存储设备商相互转换。

交换(swapping)

Linux使用交换来泛指将匿名内存换页到交换设备。在Unix和其它操作系统是指将整个进程换页到交换设备。

交换设备(swap)

磁盘上用来保存换出的匿名内存,通常是一个设备或者一个文件,有些工具将交换分区和虚拟内存混到一起,含糊不清。

概念

虚拟内存

虚拟内存是一种抽象,它为每个用户进程和内核提供了大量、连续和私有的地址空间。它简化了软件开发,将复杂的内存管理留给了操作系统。进程虚拟内存示例如下图所示。
image.png
如上图所示,一个进程的内存被虚拟内存子系统映射到主存和交换设备。内存页可以按需被内核的swapping进程在上面两个设备中移动。

换页

换页是将内存页移动进/出主存的工作,与之相应的动作一般称之为换入/换出。后面将会涉及到共享文件系统页,所以有两种换页类型:文件系统换页(file system paging)和匿名内存换页(anonymous paging)。

文件系统换页

文件系统换页是由读写内存映射文件引起的。映射文件通常是用应用使用文件内存映射(mmap)或文件系统使用page cache引起的。如果一个文件系统页在主存中被修改(dirty),则对该页的换出需要将数据写回磁盘;如果没有修改则该页内存可以直接释放。

匿名内存换页

匿名页包含进程数据:堆和栈。把它们称为匿名是因为在操作系统中没有与之对应的位置(文件系统地址或者目录)。匿名内存换出需要将内存中的数据搬迁到物理交换设备或者文件。Linux使用swapping来特指这一类换页。
匿名内存换页对应用程序性能会有比较大的影响。

按需换页

操作系统支持按需将虚拟内存映射到物理内存,如下图所示。
image.png

  1. 将未分配的虚拟内存页通过malloc函数分配给应用
  2. 当应用使用store指令存储数据到第1步分配的内存
  3. MMU设备会查找虚拟内存和主存的映射关系,在该示例中,还未做映射
  4. 这会导致缺页异常(page fault)
  5. 缺页异常会触发按需映射
  6. 映射完成后匿名内存就可以进行换出了

步骤2可能是一个指令加载操作引起的映射。如果映射可以被内存中其它页满足,这会被称为minor fault。如果缺页异常需要访问存储设备(上图中没有展示),例如访问一个未在内存映射的文件,这会被称为major fault。

架构

内存架构这一块包含硬件和软件。

硬件

内存硬件包含主存、总线、CPU缓存和MMU。

主存

今天主存类型通常是指DRAM(dynamic random-access memory)。这是一种易失性存储,当断电后内存中的数据会丢失。DRAM提供高密度存储,每一个比特位由两种部件组成:电容和晶体管。电容需要定期刷新来保持电量。如下图所示是有两个处理的的一致性内存访问(UMA)结构
image.png
通过总线,每个处理器都可以访问到所有的内存。
如下图所示是有两个处理器的非一致内存访问(NUMA)。
image.png

CPU缓存

CPU通常包含芯片上的硬件缓存以提高访问性能。按照访问速度又快到慢,大小由小到大的排序如下:

  1. 一级缓存:通常分为指令缓存和数据缓存
  2. 二级缓存:不区分指令还是数据的缓存
  3. 三级缓存:更大的空间的缓存,也不区分指令和数据

通常一级缓存使用虚拟地址引用,二级和三级使用物理地址。

MMU

MMU负责虚拟地址到物理地址的转换工作。MMU的如下图所示。
image.png

多种页大小

现代操作系统支持多种页大小,这允许操作系统和MMU使用不同的也大小(如4Kbytes、2Mbytes、1Gbytes)。Linux中的huge pages支持多种大页,比如2Mbytes,1Gbytes。

TLB

TLB(translation lookaside buffer)是第一级地址转换缓存。以为TLB大小是有限的,所以使用大页将会减少TLB未命中,提高性能。

软件

释放内存

当系统中可用内存比较少时,系统会通过多种方式释放内存以获取可用内存。
Linux系统中多种内存释放方法如下图所示。
image.png
说明如下:

  1. Free List:一个未使用的内存页列表,可以立刻用来分配使用。通常有多个free list与numa节点对应。
  2. Page Cache:文件系统缓存,可以通过swappiness参数设置是回收还是换页。
  3. Swapping:通过kswapd进程执行,找到最近使用的页加到free list。
  4. Reaping:当可用内存低到阈值时,内核模块和slab分配器将会被告知立刻释放可以被释放的内存。有时候也称作shrinking
  5. OOM Killer:out-of-memory killer将会通过寻找并杀死一部分进程来释放内存。使用select_bad_process()找到进程,并使用oom_kill_process()杀掉进程。

Linux的swapiness参数控制是通过换出应用内存还是回收page cache来获取可用内存。这是个0-100之间的值(默认60),值越大越倾向通过换出应用内存来获取可用内存,通过保留热文件系统缓存换出冷应用程序内存来提高性能。
如果没有交换设备或者文件,则内存分配会很快失败。在Linux系统中,这将意味着OOM killer成会很快被使用。

Free List

如下图所示,Free list允许可用内存被立即分配。
image.png
被释放的内存会放在列表的开头,通过换出还有有用数据的页会被加到列表的结尾,当有请求访问这些页而这些页还没被分配出去的时候可以从free list中移除。Free list在Linux系统中仍然有在使用,可以被分配器使用,例如内核的slab分配器,用户空间的libc函数malloc。
Linux使用buddy分配器管理页,拥有不同大小的free list,通常是2的指数模式。
Buddy free list如下所示:

  1. Nodes:内存银行,NUMA感知
  2. Zones:确定目的的内存范围(DMA[直接内存访问]、普通内存、高位内存)
  3. Migration types:不可移动、可回收、可移动等
  4. Sizes:2的指数数量的页
    页扫描
    通过换页来释放内存是通过内核换出进程管理的。当free list的可用内存低于阈值时,换出进程开始页扫描。页扫描仅在需要时启动。在Linux系统中换出进程叫kswapd,它将会扫描内存活跃和非活跃的LRU页列表来释放内存,它的唤醒阈值如下图所示。
    image.png
    一旦可用内存达到最低阈值,则kwapd会启动前台进程进行,同步进行内存回收。最低阈值可以通过vm.min_free_kbytes调整。Linux还提供了其它参数来进行更激进的扫描,例如:vm.watermark_scale_factor和vm.watermark_boost_factor。
    页缓存有活跃页和非活跃页列表。通过这两个列表配合LRU算法可以让kwapd更快的找到空闲页。如下图所示。
    image.png

虚拟地址空间

虚拟地址空间同时被硬件和软件管理。进程虚拟地址空间是虚拟也和物理也映射一个范围。地址被切分成被称为段的不同区域。如下图所示是一个32位x86/SPARC处理器的进程地址空间。
image.png