术语
主存(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)
磁盘上用来保存换出的匿名内存,通常是一个设备或者一个文件,有些工具将交换分区和虚拟内存混到一起,含糊不清。
概念
虚拟内存
虚拟内存是一种抽象,它为每个用户进程和内核提供了大量、连续和私有的地址空间。它简化了软件开发,将复杂的内存管理留给了操作系统。进程虚拟内存示例如下图所示。
如上图所示,一个进程的内存被虚拟内存子系统映射到主存和交换设备。内存页可以按需被内核的swapping进程在上面两个设备中移动。
换页
换页是将内存页移动进/出主存的工作,与之相应的动作一般称之为换入/换出。后面将会涉及到共享文件系统页,所以有两种换页类型:文件系统换页(file system paging)和匿名内存换页(anonymous paging)。
文件系统换页
文件系统换页是由读写内存映射文件引起的。映射文件通常是用应用使用文件内存映射(mmap)或文件系统使用page cache引起的。如果一个文件系统页在主存中被修改(dirty),则对该页的换出需要将数据写回磁盘;如果没有修改则该页内存可以直接释放。
匿名内存换页
匿名页包含进程数据:堆和栈。把它们称为匿名是因为在操作系统中没有与之对应的位置(文件系统地址或者目录)。匿名内存换出需要将内存中的数据搬迁到物理交换设备或者文件。Linux使用swapping来特指这一类换页。
匿名内存换页对应用程序性能会有比较大的影响。
按需换页
操作系统支持按需将虚拟内存映射到物理内存,如下图所示。
- 将未分配的虚拟内存页通过malloc函数分配给应用
- 当应用使用store指令存储数据到第1步分配的内存
- MMU设备会查找虚拟内存和主存的映射关系,在该示例中,还未做映射
- 这会导致缺页异常(page fault)
- 缺页异常会触发按需映射
- 映射完成后匿名内存就可以进行换出了
步骤2可能是一个指令加载操作引起的映射。如果映射可以被内存中其它页满足,这会被称为minor fault。如果缺页异常需要访问存储设备(上图中没有展示),例如访问一个未在内存映射的文件,这会被称为major fault。
架构
硬件
主存
今天主存类型通常是指DRAM(dynamic random-access memory)。这是一种易失性存储,当断电后内存中的数据会丢失。DRAM提供高密度存储,每一个比特位由两种部件组成:电容和晶体管。电容需要定期刷新来保持电量。如下图所示是有两个处理的的一致性内存访问(UMA)结构
通过总线,每个处理器都可以访问到所有的内存。
如下图所示是有两个处理器的非一致内存访问(NUMA)。
CPU缓存
CPU通常包含芯片上的硬件缓存以提高访问性能。按照访问速度又快到慢,大小由小到大的排序如下:
- 一级缓存:通常分为指令缓存和数据缓存
- 二级缓存:不区分指令还是数据的缓存
- 三级缓存:更大的空间的缓存,也不区分指令和数据
MMU
MMU负责虚拟地址到物理地址的转换工作。MMU的如下图所示。
多种页大小
现代操作系统支持多种页大小,这允许操作系统和MMU使用不同的也大小(如4Kbytes、2Mbytes、1Gbytes)。Linux中的huge pages支持多种大页,比如2Mbytes,1Gbytes。
TLB
TLB(translation lookaside buffer)是第一级地址转换缓存。以为TLB大小是有限的,所以使用大页将会减少TLB未命中,提高性能。
软件
释放内存
当系统中可用内存比较少时,系统会通过多种方式释放内存以获取可用内存。
Linux系统中多种内存释放方法如下图所示。
说明如下:
- Free List:一个未使用的内存页列表,可以立刻用来分配使用。通常有多个free list与numa节点对应。
- Page Cache:文件系统缓存,可以通过swappiness参数设置是回收还是换页。
- Swapping:通过kswapd进程执行,找到最近使用的页加到free list。
- Reaping:当可用内存低到阈值时,内核模块和slab分配器将会被告知立刻释放可以被释放的内存。有时候也称作shrinking
- 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允许可用内存被立即分配。
被释放的内存会放在列表的开头,通过换出还有有用数据的页会被加到列表的结尾,当有请求访问这些页而这些页还没被分配出去的时候可以从free list中移除。Free list在Linux系统中仍然有在使用,可以被分配器使用,例如内核的slab分配器,用户空间的libc函数malloc。
Linux使用buddy分配器管理页,拥有不同大小的free list,通常是2的指数模式。
Buddy free list如下所示:
- Nodes:内存银行,NUMA感知
- Zones:确定目的的内存范围(DMA[直接内存访问]、普通内存、高位内存)
- Migration types:不可移动、可回收、可移动等
- Sizes:2的指数数量的页
页扫描
通过换页来释放内存是通过内核换出进程管理的。当free list的可用内存低于阈值时,换出进程开始页扫描。页扫描仅在需要时启动。在Linux系统中换出进程叫kswapd,它将会扫描内存活跃和非活跃的LRU页列表来释放内存,它的唤醒阈值如下图所示。
一旦可用内存达到最低阈值,则kwapd会启动前台进程进行,同步进行内存回收。最低阈值可以通过vm.min_free_kbytes调整。Linux还提供了其它参数来进行更激进的扫描,例如:vm.watermark_scale_factor和vm.watermark_boost_factor。
页缓存有活跃页和非活跃页列表。通过这两个列表配合LRU算法可以让kwapd更快的找到空闲页。如下图所示。
虚拟地址空间
虚拟地址空间同时被硬件和软件管理。进程虚拟地址空间是虚拟也和物理也映射一个范围。地址被切分成被称为段的不同区域。如下图所示是一个32位x86/SPARC处理器的进程地址空间。