什么是swap
swap主要是在内存不够用的时候,将部分内存上的数据交换到swap空间上,以便让系统不会因为内存不够用而导致oom或者更致命的情况出现。当内存使用存在压力的时候,开始触发内存回收行为,就可能会使用swap空间。
内核将很少使用的部分内存换出到块设备,相当于提供了更多的主内存,这种机制成为页交换(swapping)或者换页(paging),由内核实现,对应用程序是透明的。
如果一个很少使用的页的后备存储器是一个块设备,那么就无需换出被修改的页,而是可以直接与块设备同步。腾出的页帧可以重用,如果再次需要修改数据,可以从来源重新建立该页。
如果页的后备存储器是一个文件,但不能在内存中修改,那么在当前不需要的情况下,可以直接丢弃该页。
以上这三种技术,连同选择很少使用页的策略,统称为页面回收。
为什么要进行内存回收?
1.内核需要为突发到来的内存申请提供足够的内存,所以一般情况下保证有足够的free空间对于内核来说是必要的。
另外,Linux内核使用cache策略虽然是不用白不用,内核会使用内存中的page cache对部分文件进行缓存,一边提升文件的读写效率。
所以内核有必要设计一个周期行回收内存机制,以便cache的使用和其他相关内存的使用不至于让系统的剩余内存长期处于很少的状态。
2.当真的有大于空闲内存的申请到来的是偶,会触发强制内存回收。
所以内核在应对这两类回收的需求下,分别实现两种不同的机制:
一个是使用kswapd进程对内存进行周期检查,以保证平常状态下剩余内存尽可能够用。
另一个是页面回收(page reclaim),就是当内存分配时没有空闲内存可以满足要求时,触发直接内存回收。
这两种内存回收的触发路径不同:
kswapd作为一个后台守护进程,会定期检查内存的使用情况,并检测即将发生的内存不足。可使用该守护进程换出页,并释放那些最不常用的。
参见mm/vmscan.c中的kswapd()主逻辑。
kswapd -->balance_pgdat -->kswapd_shrink_zone -->shrink_zone -->shrink_lruvec |
如果内存检测到在某个操作期间内存严重不足,将调用try_to_free_pages。该函数检查当前内存域中所有页,并释放最不常用的那些。
参见内核代码中的mm/page_alloc.c中的__alloc_pages_slowpath方法。
__alloc_pages_nodemask -->__alloc_pages_slowpath -->__alloc_pages_direct_reclaim 直接页面回收然后分配内存 -->__perform_reclaim 执行同步直接页面回收 -->try_to_free_pages -->do_try_to_free_pages -->shrink_zones -->shrink_zone -->shrink_lruvec |
这两个方法实际进行内存回收的过程殊途同归,最终头时调用shrink_zone()方法针对每个zone的内存页缩减。
这个方法中在调用shrink_lruvec()这个方法对每个组织页的链表进行进程检查,找到这个线索之后,我们就可以清晰地看到内存回收操作究竟这对的page有哪些了。
static void shrink_lruvec(struct lruvec *lruvec, int swappiness, for_each_evictable_lru(lru) { nr_reclaimed += shrink_list(lru, nr_to_scan, |
内存回收主要需要进行少秒的链表有如下4个:
anon的inactive,anon的active,file的inactive,file的active。
就是说内存回收主要针对的就是内存中的文件页(file cache)和匿名页。
关于活跃还是不活跃的判断内核会使用lru算法进行并行处理并进行标记。
#define LRU_BASE 0 enum lru_list { |
整个扫描的过程分几个循环:
1.首先扫描每个zone删的cgroup组;
2.然后再以cgroup的内存为单元进行page链表的扫描;
3.内核会先扫描anon的active链表,将不频繁的放进inactive链表中,然后扫描inactive链表,将里面活跃的移回active链表中;
4.进行swap的时候,先对inactive的页进行换出;
5.如果是file的文件映射page页,则判断其是否为脏数据,如果是脏数据就写回,不是脏数据可以直接释放。
这样看来,内存回收这个行为会对两种内存的使用进行回收:
一种是anon的匿名页内存,主要回收手段是swap;另一种是file-backed的文件映射页,主要释放手段是写回和清空。
内存对匿名页和文件缓存一共用了四条链表进行组织,回收过程主要是针对这四条链表进行扫描和操作。
kswapd什么时候进行swap操作?
内核的两种内存回收机制kswapd周期检查和直接内存回收,直接内存回收比较好理解,当申请的内存大于剩余内存的时候,就会触发直接回收。
kswapd进程在周期检查的时候触发回收的条件是什么呢?
kswapd进程要周期对内存进行检测,达到一定阈值的时候开始进行内存回收。这个所谓的阈值可以理解为内存目前的使用压力。虽然我们还有剩余内存,但是当剩余内存比较小的时候,就是内存压力较大的时候,就应该开始试图回收写内存了,这样才能保证系统尽可能有足够的内存给突发的内存申请所使用。
基于swappiness调优
详情请参考:http://blog.csdn.net/tenfyguo/article/details/50185915
swappiness是一个swap相关百分比参数,默认值是60,范围是0-100。
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. |
从上面这段话可知,swappiness表示内核swap内存页的积极程度。值越高越是积极的进行swap;降低表示积极性越低。当swappiness值为0是,不是不进行swap,只是一个zone中内存的free和file-backed页低于高水位标记才会进行swap。
shrink_lruvec()调用get_scan_count()方法,get_scan_count用于决定anon和文件LRU列表被扫描的积极程度。swappiness参数实际上是知道内核在清空内存的时候,是更倾向于清空file-backed内存还是匿名页的swap。
static void get_scan_count(struct lruvec *lruvec, int swappiness, /* /* If we have no swap space, do not bother scanning anon pages. */ /* /* /* zonefree = zone_page_state(zone, NR_FREE_PAGES); if (unlikely(zonefile + zonefree <= high_wmark_pages(zone))) { /* scan_balance = SCAN_FRACT; /* 1.如果swappiness设置为100,那么匿名页和文件将用同样的优先级进行回收。 2.如果swappiness值是60,内核回收的时候也不是完全按照60:140比例清空。在计算具体回收大小的时候,还需要参考当前内存使用的其他信息。 3.swappiness为0的话,也不是根本不进行swap。 /* anon = get_lru_size(lruvec, LRU_ACTIVE_ANON) + spin_lock_irq(&zone->lru_lock); if (unlikely(reclaim_stat->recent_scanned[1] > file / 4)) { /* fp = file_prio * (reclaim_stat->recent_scanned[1] + 1); fraction[0] = ap; size = get_lru_size(lruvec, lru); if (!scan && pass && force_scan) switch (scan_balance) { *lru_pages += size; /* |
可以使用sysctl vm.swappiness=10对swappiness进行设置。
dirty_ratio
同步刷脏页,会阻塞应用程序。这个参数控制文件系统的同步写缓冲区的大小,单位是百分比,表示当写缓冲区使用到系统内存多少的时候(即指定了当文件系统缓存脏页数量达到系统内存百分比时),开始向磁盘写出数据,及系统不得不开始处理缓存脏页,在此过程中很多应用程序可能会因为系统转而处理文件I/O而阻塞变慢。
增大会使系统内存更多用于磁盘写缓冲,也可以极大提高系统的写性能。
dirty_background_ratio
异步刷脏页,不会阻塞应用程序。这个参数控制文件系统的后台进程,在合适刷新磁盘。当写缓冲使用到系统内存一定百分比的时候,就会触发pdflush/flush/kdmflush等后台回写进程运行,将一定缓存的脏页异步地输入外存。
一般dirty_ratio比dirty_background_radio要大,先达到dirty_background_ratio的条件然后触发flush进程进行异步的回写操作,但是这一过程应用进程仍然可以进行写操作,如果多个应用程序写入的量大于刷出的量那自然会达到dirty_ratio的标准,此时操作系统会进入同步刷出脏页的过程,阻塞应用进程。
增大会使更多系统内存用于磁盘写缓冲。
dirty_expire_centisecs
申明Linux内核写缓冲区里面的数据多旧了之后,pdflush进程就开始考虑写到磁盘中去,单位是1/100秒。缺省是3000,即20秒的缓存就会被刷新到磁盘。如果太小,刷新磁盘就会比较频繁,导致I/O占用太多时间。如过太大,在某些异常情况会造成数据丢失,同时暂用太多内存。
dirty_writeback_centisecs
这个参数控制内核额脏数据树新进程pdflush的运行间隔,单位是1/100秒,缺省是500,即5秒。
vfs_cache_pressure
设置了虚拟内存回收directory和inode缓存的倾向,这个值越大,越倾向于回收。
缺省值为100;低于100,将导致内核更倾向于保留directory和inode cache;高于100,将导致内核倾向于回收directory和inode cache。
以上这些数据还是要根据系统实际内存以及不同的工作场景进行调优。
内存水位标记(watermark)
Linux为内存的使用设置了三种内存水位标记:high、low、min。
high:剩余的内存在high以上表示内存剩余较多,目前内存使用压力不大。
high-low:表示剩余内存存在一定压力。
low-min:表示内存开始使用有较大压力,剩余内存不多了。
min:是最小水位标记,当剩余内存达到这个状态时,就说明内存面临很大压力。小于min这部分内存,内核是保留给特定情况下使用的,一般不分配。
内存回收行为就是基于剩余内存的水位标记进行的:
当系统内存低于watermark[low]的时候,内核的kswapd开始起作用,进行内存回收。直到剩余内存达到watermark[high]的时候停止。
如果内存消耗导致达到了或者低于watermark[min]时,就会触发直接回收(direct reclaim)。
min_free_kbytes
内存的watermark标记是根据当前内存总大小和min_free_kbytes进行运算的来的。
/proc/sys/vm/min_free_kbytes决定了每个zone的watermark[min]的大小,然后根据min的大小和每个zone内存大小分别算出每个zone的low和high水位标记。
init_per_zone_wmark_min 初始化min_free_kbytes -->setup_per_zone_wmarks -->__setup_per_zone_wmarks 根据min_free_kbytes计算low和high的值 |
下面来分析一下low和high水位值是如何计算的:
static void __setup_per_zone_wmarks(void) /* Calculate total number of !ZONE_HIGHMEM pages */ for_each_zone(zone) { 遍历所有zone spin_lock_irqsave(&zone->lock, flags); if (is_highmem(zone)) { min_pages = zone->managed_pages / 1024; zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + __mod_zone_page_state(zone, NR_ALLOC_BATCH, spin_unlock_irqrestore(&zone->lock, flags); /* update totalreserve_pages */ |
通过cat /proc/zoneinfo可以看到不同zone的watermark:
cat /proc/zoneinfo |
zone_reclaim_mode
当一个内存区域内部的内存耗尽时,是从其内部进行内存回收还是可以从其他zone进行回收的选项。在申请内存时(get_page_from_freelist()),内核在当前zone内没有足够的内存可用的情况下,会根据zone_reclaim_mode的设置来决策是从下一个zone找空闲内存还是在zone内部进行回收。
0:意味着关闭zone_claim模式,可以从其他zone或NUMA节点回收内存。
1:打开zone_claim模式,这样内存回收只会发生在本地节点内。
2:在本地回收内存时,可以将可以将cache中的脏数据回写硬盘,以回收内存。
3:可以用swap方式回收内存。
不同的参数配置会在NUMA环境中对其他内存节点的内存使用产生不同的影响,大家可以根据自己的情况进行设置以优化你的应用。
默认情况下,zone_reclaim模式是关闭的。这在很多应用场景下可以提高效率,比如文件服务器,或者依赖内存中cache比较多的应用场景。
这样的场景对内存cache速度的依赖要高于进程进程本身对内存速度的依赖,所以我们宁可让内存从其他zone申请使用,也不愿意清本地cache。
如果确定应用场景是内存需求大于缓存,而且尽量要避免内存访问跨越NUMA节点造成的性能下降的话,则可以打开zone_reclaim模式。
此时页分配器会优先回收容易回收的可回收内存(主要是当前不用的page cache页),然后再回收其他内存。
打开本地回收模式的写回可能会引发其他内存节点上的大量的脏数据写回处理。如果一个内存zone已经满了,那么脏数据的写回也会导致进程处理速度收到影响,产生处理瓶颈。
这会降低某个内存节点相关的进程的性能,因为进程不再能够使用其他节点上的内存。但是会增加节点之间的隔离性,其他节点的相关进程运行将不会因为另一个节点上的内存回收导致性能下降。
除非针对本地节点的内存限制策略或者cpuset配置有变化,对swap的限制会有效约束交换只发生在本地内存节点所管理的区域上。
page-cluster
page-cluster是用来控制从swap空间换入数据的时候,一次连续读取的页数这相当于对交换空间的预读取。这里的连续指的是swap空间上的连续,而不是内存地址上的连续。
这个文件中设置的是2的指数。0,则预读取1页;3,则预读取8页。
创建、删除swap分区
查看swap相关信息
swapon –s和cat /proc/swaps的结果是一样的,可以用于查看当前系统的swap分区总结。
Filename Type Size Used Priority |
free -m用于显示系统空闲和使用的内存量,其中包括swap信息。
total used free shared buffers cached |
创建swap分区
有两种方式创建swap分区:一种是基于物理分区,另一种是基于一个文件。
基于物理分区创建
- 创建一个swap分区:fdisk -l /dev/sdb1
- 格式化分区:mkswap -c v1 /dev/sdb1
- 修改/etc/fstab文件,增加:
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# swap was on /dev/sdb1 during installation
UUID=47b966f2-8157-4650-a2c2-32f0907081bf none swap sw 0 0 - 激活swap分区:swapon –a /dev/sdb1
- 查看swap分区:swapon -s 或cat /proc/swaps
基于文件创建
- dd if=/dev/zero of=/home/lubaoquan/swap/tmp.swap bs=1G count=1,创建空文件
- mkswap tmp.swap,创建为swap文件
Setting up swapspace version 1, size = 1048572 KiB |
- swapon /tmp/tmp.swap,激活swap分区
- 修改/etc/fstab文件
/tmp/tmp.swap swap swap default 0 0 |
- 查看swap分区信息swapon –s
Filename Type Size Used Priority |
删除swap分区
- swapoff /dev/sdb1
- 修改/etc/fstab文件
关于swap的一个测试
关闭打开清空swap
sudo swapoff -a关闭swap,然后free –m查看使用情况
total used free shared buffers cached |
sudo swapon -a再打开,查看free -m:
total used free shared buffers cached |
swap变化
使用eat_mem.c不断分配大内存。
#include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char** argv) { int max = -1; int mb = 0; char* buffer; if(argc > 1) max = atoi(argv[1]); while((buffer=malloc(10*1024*1024)) != NULL && mb != max) { memset(buffer, 0,10*1024*1024); mb = mb + 10; printf("Allocated %d MB\n", mb); sleep(1); } return 0; } |
执行./eat_mem,使用watch 'free -m'可以动态查看Mem和Swap的使用情况:
Every 2.0s: free -m Fri Jan 20 16:41:31 2017 total used free shared buffers cached |
在eat_mem执行到11710 MB的时候,被Killed了。
./eat_mem |
在这个过程中可以看到,Mem的free越来越小,Swap的used空间越来越多,越来越多的内容被置换到Swap区域。
在内容开始置换到swap分区过程中,系统的响应速度越来越慢。最后达到swap分区也承受不了,eat_mem就会被Killed。
swap分区的优先级
在使用多个swap分区或者文件的时候,还有一个优先级的概念。
在swapon的时候,可以使用-p指定相关swap空间的优先级,值越大优先级越高,范围是-1到32767。
内核在使用swap空间的时候总是先使用优先级搞得空间,后使用优先级低的。
如果把多个swap空间的优先级设置成一样的,那么swap空间将会以轮询的方式并行使用。
如果两个swap放在不同硬盘上,相同优先级可以起到类似的RAID效果,增大swap的读写效率。
编程时使用mlock()可以将指定的内存标记为不换出。
参考文档
http://blog.csdn.net/cyuyan112233/article/details/18803589
http://blog.csdn.net/tenfyguo/article/details/50185915
http://blog.chinaunix.net/uid-24774106-id-3954137.html