1. 什么是swap
从功能上讲,交换分区主要是在内存不够用的时候,将部分内存上的数据交换到 swap空间上,以便让系统不会因内存不够用而导致oom或者更致命的情况出现。
也就是说,当内存不够用时,我们使用 swap 分区来临时顶替。这种“拆东墙,补西墙”的方式应用于几乎所有的操作系统中。
使用 swap 交换分区,显著的优点是,通过操作系统的调度,应用程序实际可以使用的内存空间将远远超过系统的物理内存。由于硬盘空间的价格远比 RAM 要低,因此这种方式无疑是经济实惠的。当然,频繁地读写硬盘,会显著降低操作系统的运行速率,这也是使用 swap 交换分区最大的限制。
2. 什么是swap cache
交换区可以包括一个或多个交换区设备(裸盘、逻辑卷、文件都可以充当交换区设备),每一个交换区设备在内存里都有对应的swap cache,可以把swap cache理解为交换区设备的”page cache”:page cache对应的是一个个文件,swap cache对应的是一个个交换区设备,kernel管理swap cache与管理page cache一样,用的都是radix-tree,唯一的区别是:page cache与文件的对应关系在打开文件时就确定了,而一个匿名页只有在即将被swap-out的时候才决定它会被放到哪一个交换区设备,即匿名页与swap cache的对应关系在即将被swap-out时才确立。
并不是每一个匿名页都在swap cache中,只有以下情形之一的匿名页才在:
- 匿名页即将被swap-out时会先被放进swap cache,但通常只存在很短暂的时间,因为紧接着在pageout完成之后它就会从swap cache中删除,毕竟swap-out的目的就是为了腾出空闲内存;
【注:参见 mm/vmscan.c: shrink_page_list(),它调用的 add_to_swap() 会把swap cache页面标记成 dirty,然后它调用 try_to_unmap() 将页面对应的page table mapping都删除,再调用pageout() 回写dirty page,最后 try_to_free_swap() 会把该页从swap cache中删除。】
- 曾经被 swap-out 现在又被 swap-in 的匿名页会在swap cache中,直到页面中的内容发生变化、或者原来用过的交换区空间被回收为止。
【注:当匿名页的内容发生变化时会删除对应的swap cache,代码参见mm/swapfile.c: reuse_swap_page()。】
3. 为什么进行内存回收
- 内核需要为任何时刻突发到来的内存申请提供足够的内存。所以一般情况下保证有足够的free空间对于内核来说是必要的。
另外,Linux内核使用cache的策略虽然是不用白不用,内核会使用内存中的page cache对部分文件进行缓存,以便提升文件的读写效率。
所以内核有必要设计一个周期性回收内存的机制,以便cache的使用和其他相关内存的使用不至于让系统的剩余内存长期处于很少的状态。
- 当真的有大于空闲内存的申请到来的时候,会触发强制内存回收(direct page reclaim)。
4. 页面回收机制
- 周期性回收内存机制:这就是 kswapd 内核线程的工作职责。当内核路径调用 alloc_pages() 分配物理页面时,由于系统内存短缺,没法在低水位情况下分配内存,因此会唤醒 kswapd 内核线程来异步回收内存;详细可以查看:kswapd 详解
- 使用直接内存回收(direct page reclaim):在内核态里调用页面分配接口函数 alloc_pages() 分配物理页面时,由于系统内存短缺,不能满足分配请求,因为内核会直接自陷到页面回收机制,尝试回收内存来解决当前的燃眉之急;详细可以查看:直接内存回收详解
- slab 收割机(slab shrinker)机制:这是用来回收 slab 对象的。当内存短缺时,直接页面回收和周期性回收内存两种机制都会被调用 slab 收割机机制来回收slab 对象。slab 机制分配的内存主要用于slab 对象和 kmalloc 接口,也可用于内核空间的内存分配。详细可以查看:slub 分配器
直接回收和周期性内存回收的触发路径不同:
- 一个是由内核线程 kswapd0 直接调用内存回收的逻辑进行内存回收,是因为用户进程在内存分配时,发现在低水位情况下无法分配出内存时,唤醒kswapd 进行异步回收; 参见mm/vmscan.c 中的 kswapd() 主逻辑;
- 一个是内存申请的时候进入slow path 的内存申请逻辑进行回收,是用户进程在内存分配时,因为内存短缺而陷入直接页面回收机制,执行页面回收的是进程本身,是同步回收;参见mm/page_alloc.c 中的 __alloc_pages_slowpath();
这两个方法中实际进行内存回收的过程殊途同归,最终都是调用 shrink_zones() 方法进行针对每个zone 的内存页缩减。
5. LRU 算法
Least Recently Used 的缩写,即最近很少使用页面置换算法,是为虚拟页式存储管理服务,是根据页面调入内存后的使用情况进行决策。
这个算法的核心思想是:回收的页面应该是最近使用得最少的,为了实现这个目标,最理想的情况是每个页面都有一个年龄项,用于记录最近一次访问页面的时间,可惜x86 CPU硬件并不支持这个特性,x86 CPU只能做到在访问页面时设置一个标志位Access Bit,无法记录时间,所以Linux Kernel使用了一个折中的方法:它采用了LRU list列表,把刚访问过的页面放在列首,越接近列尾的就是越长时间未访问过的页面,这样,虽然不能记录访问时间,但利用页面在LRU list中的相对位置也可以轻松找到年龄最长的页面。Linux kernel设计了两种LRU list:active list 和 inactive list,刚访问过的页面放进active list,长时间未访问过的页面放进inactive list,这样从inactive list回收页面就变得简单了。内核线程 kswapd 会周期性地把 active list 中符合条件的页面移到 inactive list中,这项转移工作是由 shrink_active_list() 完成的。
6. 换出、换入
- 换出(swap-out),把进程暂时不用的内存数据(anon page)存储到磁盘中,并释放这些数据占用的内存。
- 换入(swap-in),进程再次访问这些内存时,将数据从磁盘中读到内存中
7. swappiness
/proc/sys/vm/swappiness 的取值范围是0 ~ 100,这里不是百分比,而是比重,值越高,内核会越积极的使用swap,如果值为0,在free 和file-backed page 没有小于高水位前不做swap。
This control is used to define how aggressive the kernel will swap memory pages. Higher values will increase agressiveness, lower values decrease the amount of swap.
A value of 0 instructs the kernel not to initiate swap until the amount of free and file-backed pages is less than the high water mark in a zone.
The default value is 60.
详细的swapiness 的发展,可以查看:linux内存中 swappiness=0究竟意味着什么?
为了衡量内存的使用情况,kswapd0 定义了三个内存阈值(watermark,也称水位):
- pages_min,页最小阈值
- pages_low,页低阈值
- pages_high,页高阈值
下面这张时序图能很好地表示水位的变化:
- 剩余内存高于pages_high,说明剩余内存比较多,没有内存压力;
- 剩余内存小于pages_high,说明内存有一定压力,但还可以满足新内存请求;
- 剩余内存小于pages_low,说明内存压力比较大,剩余内存不多了。这时,kswapd0 会被唤醒,执行内存回收,直至剩余内存大于pages_high;
- 剩余内存小于pages_min,说明进程可用的内存都耗尽,仅内核才可以分配内存;
- 如果内存消耗导致剩余内存达到或超过了pages_min时,就会触发直接回收(direct page reclaim);
下图是根据源码总结的算法:
详细的水位分析可以查看:《内存watermark 详解》 一文。
其中extra_free_kbytes取自 /proc/sys/vm/extra_free_kbytes
scale_factor 取自 /proc/sys/vm/watermark_scale_factor
- high 水位和low 水位之间的间隔不能太小,这样在内存使用紧张时,会大大影响kswapd0 触发的频率。例如,kswapd 刚到high 水位,因为内存时候又快速低于low 水位,kswapd会被唤醒,等到high 水位才结束,但是刚到high 如果又被唤醒,这样反复会影响到kswapd 的启动频率;
- low 水位不能太低,有时候太低了kswapd 有可能无法唤醒,大量的后台cache 内存可能并不需要,需要kswapd 尽快的压缩。当然也不能太高,太高容易触发kswapd;
- min 水位是极限水位,不能太小了,保证内核的正常运行,目前ARM 架构的手机平台都默认为64KB;
8. 查看内存水位
通过 /proc/zoneinfo 可以确认水位等信息:
1|ja310:/proc # cat /proc/zoneinfo
Node 0, zone DMA
per-node stats
nr_inactive_anon 1411
nr_active_anon 115369
nr_inactive_file 38062
nr_active_file 72703
nr_unevictable 2347
nr_slab_reclaimable 7856
nr_slab_unreclaimable 14374
nr_isolated_anon 0
nr_isolated_file 0
workingset_refault 98
workingset_activate 98
workingset_nodereclaim 0
nr_anon_pages 115321
nr_mapped 113129
nr_file_pages 114603
nr_dirty 8
nr_writeback 0
nr_writeback_temp 0
nr_shmem 1698
nr_shmem_hugepages 0
nr_shmem_pmdmapped 0
nr_anon_transparent_hugepages 0
nr_unstable 0
nr_vmscan_write 0
nr_vmscan_immediate_reclaim 0
nr_dirtied 43328
nr_written 42100
pages free 166428
min 1393 -----最小阈值为1393 pages,即 5572KB
low 7841
high 8328
spanned 520192
present 505072
managed 487644
protection: (0, 0, 0)
nr_free_pages 166428
nr_zone_inactive_anon 1411 ----不活跃的匿名页数
nr_zone_active_anon 115369 ----活跃的匿名页数
nr_zone_inactive_file 38062 ----不活跃的文件页
nr_zone_active_file 72703 ----活跃的文件页
nr_zone_unevictable 2347
nr_zone_write_pending 8
nr_mlock 2347
nr_page_table_pages 6617
nr_kernel_stack 13520
nr_bounce 0
nr_zspages 0
nr_free_cma 800
某个 Node 内存不足时,系统可以从其他 Node 寻找空闲内存,也可以从本地内存中回收内存。具体选哪种模式,你可以通过 /proc/sys/vm/zone_reclaim_mode 来调整
- echo 0 > /proc/sys/vm/zone_reclaim_mode:意味着关闭zone_reclaim模式,可以从其他zone或NUMA节点回收内存。
- echo 1 > /proc/sys/vm/zone_reclaim_mode:表示打开zone_reclaim模式,这样内存回收只会发生在本地节点内。
- echo 2 > /proc/sys/vm/zone_reclaim_mode:在本地回收内存时,可以将cache中的脏数据写回硬盘,以回收内存。
- echo 4 > /proc/sys/vm/zone_reclaim_mode:可以用swap方式回收内存。
zoneinfo 详解可以查看:《Linux 内核参数:zoneinfo》一文。
9. 创建swap
fallocate -l 8G /mnt/swapfile
file /mnt/swapfile
chmod 700 /mnt/swapfile
mkswap /mnt/swapfile
Setting up swapspace version 1, size = 8 GiB (8589930496 bytes)
no label, UUID=92e57c51-23e6-4976-acc4-e0745c870bb7
swapon /mnt/swapfile
file swapfile
swapfile: Linux/i386 swap file (new style), version 1 (4K pages), size 2097151 pages, no label, UUID=92e57c51-23e6-4976-acc4-e0745c870bb7
10. 释放内存
通过 /proc/sys/vm/drop_caches 的配置释放内存:
- 0 值:不释放
- 1 值:释放页缓存
- 2 值:释放dentries 和inodes
- 3 值:释放所有缓存
11. 释放swap
- free -m
- swapon -s 获取swap 挂在点(例如/dev/swapfile)
- swapoff /dev/swapfile
- swapon -s
- free -m
- swapon /dev/swapfile
- swapon -s
- free -m
12. swap 分区的优先级(priority)
在使用多个swap分区或者文件的时候,还有一个优先级的概念(Priority)。在swapon的时候,我们可以使用-p参数指定相关swap空间的优先级,值越大优先级越高,可以指定的数字范围是-1到32767。
内核在使用swap空间的时候总是先使用优先级高的空间,后使用优先级低的。当然如果把多个swap空间的优先级设置成一样的,那么两个swap空间将会以轮询方式并行进行使用。如果两个swap放在两个不同的硬盘上,相同的优先级可以起到类似RAID0的效果,增大swap的读写效率。
另外,编程时使用mlock()也可以将指定的内存标记为不会换出,具体帮助可以参考man 2 mlock。
13. page-cluster
/proc/sys/vm/page-cluster是用来控制从swap空间换入数据的时候,一次连续读取的页数,这相当于对交换空间的预读。这里的连续是指在swap空间上的连续,而不是在内存地址上的连续。
因为swap空间一般是在硬盘上,对硬盘设备的连续读取将减少磁头的寻址,提高读取效率。
这个文件中设置的值是2的指数。就是说,如果设置为0,预读的swap页数是2的0次方,等于1页。如果设置为3,就是2的3次方,等于8页。
同时,设置为0也意味着关闭预读功能。文件默认值为3。我们可以根据我们的系统负载状态来设置预读的页数大小。
参考: