Linux系统的swap机制

1. swap 原理

当系统发生内存泄露,或者运行了占用大量内存的进程,导致系统的内存资源紧张时,会导致两种后果,内存回收和OOM杀死进程。OOM这里就不赘述了,来看看内存回收,也就是系统释放掉可以回收的内存,比如前面讲过的buff/cache,就属于可回收内存。在内存管理中,它们就叫文件页

大部分文件页都可以被回收,以后有需要时再从磁盘读取就可以了,而那些被程序修改过且暂未被写入磁盘的数据(脏页),就得先写入磁盘,然后才能进行内存释放。这些脏页,一般可以两种方式写入磁盘:

  • 在应用程序中,通过系统调用fsync,把脏页数据同步到磁盘中。
  • 也可以交给系统,由内核线程pdflush负责脏页数据的刷新。

除了buff/cache,通过内存映射获取的文件映射页,也是一种常见的文件页。先释放,再访问时可重新读取。到这里,我们知道了文件页可以被暂时回收供其他使用,那么匿名页呢,比如被应用程序动态分配的堆内存,是不是也可以回收?答案是不可直接回收,但可以暂存到磁盘中,释放给其他的进程使用。这正是Linux系统下的Swap机制。

所谓的Swap机制,就是把一块磁盘或者一个本地文件,当作内存使用,它包括啷个过程:

  • 换出,把进程暂时不用的内存数据保存到磁盘中,在释放内存给其他进程使用。
  • 换入,当进程再次访问内存时,从磁盘读取数据到内存中。

因此,Swap机制其实扩大了系统的可用内存,即使服务器内存不足,也可以运行大内存的应用程序。比如早年时候,内存太贵,普通学生学习Linux操作系统时,根本用不起大内存电脑,但是可以开启Swap来运行Linux桌面。但是现今时代,内存已经很便宜,Swap是否还有用武之地?当然有。一个很典型的场景,即使内存不足,有些应用程序并不想被OOM机制杀死,而是能希望缓一段时间,等待人工介入,或者等待系统自动释放其他进程的内存,在分配给它。除此之外,我们常见的笔记本电脑的休眠和快速开机功能,也基于Swap。休眠时,把系统内存存到磁盘,再次开机,只要从磁盘加载到内存,这样就省去很多应用程序的初始化过程,加快开机速度。

话说回来,既然Swap是为回收内存,那么何时去回收,又该怎么去衡量内存是否紧张呢?最容易想到的场景,有新的大块内存请求,且内存不足,这个时候系统就要回收一部分内存(比如前面提到的buff/cache),去满足新内存请求,这个过程称为直接内存回收

除了直接内存回收,还有一个专门的内核线程定期回收内存,也就是kswapd0。为了衡量内存使用情况,kswapd0定义了三个内存阈值(watermark, 水位):页最小阈值(pages_min)、页低阈值(pages_low)、页高阈值(pages_high),剩余内存则使用pages_free表示。下图展示了它们的关系:

                                    

kswapd0定期扫描内存的使用情况,并根据 pages_free 落在三个阈值空间的位置,判定回收内存的操作:

  • pages_free 小于 pages_min,说明进程的可用内存都耗尽,只有内核可以分配内存。
  • pages_free 落在 pages_min 和 pages_low之间,内存压力较大,pages_free 不多了,kswapd0线程回收内存,直到pages_free 大于 pages_high 为止。
  • pages_free 落在pages_low 和 pages_high 之间,内存有一定压力,但还可以满足新内存请求。
  • pages_free 大于 pages_high ,内存充足,无需回收内存。

我们可以知道,一旦 pages_free 小于 pages_low,就会触发内存回收。这个页低阈值,其实可以通过间接的设置内核选项 /proc/sys/vm/min_free_bytes (页最小阈值)达成。然后其他两个阈值可以根据这个页最小值来计算生成:

pages_low = pages_min*5/4
pages_high = pages_min*3/2

2. NUMA 和 Swap 以及 swappiness

很多情况下,明明发现了swap升高,但是分析系统内存使用情况时,却发现系统剩余内存还多着呢。这正是处理器的NUMA架构导致的。在NUMA架构下,多个处理器会被划分到不同Node上,且每个Node都有自己的本地内存空间。这个内存空间又被分成不同的内存阈(Zone),比如直接内存访问区(DMA)、普通内存区(NORAML)、伪内存区(MOVABLE)等,如下图:

                                        

既然NUMA架构下,每个Node都有自己的本地内存空间,那么分析系统内存使用情况,也要从每个Node角度去分析:

$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...

结果显示,系统只有一个Node0,编号为0、1的CPU都位于Node0上,且Node0的内存有7977MB,剩余内存有4416MB。

那么这些又跟Swap有什么关系呢?实际上,前面提到的三个内存阈值,都可以通过内存阈在 proc 文件系统中的接口 /proc/zoneinfo 的文件内容查看:

$ cat /proc/zoneinfo
...
Node 0, zone   Normal
 pages free     227894
       min      14896
       low      18620
       high     22344
...
     nr_free_pages 227894
     nr_zone_inactive_anon 11082
     nr_zone_active_anon 14024
     nr_zone_inactive_file 539024
     nr_zone_active_file 923986
...

只解释几个比较重要的指标:

  • pages 处的 min、low、high,跟上面提到的三个阈值对应,而 free 是剩余内存页数,跟后面 nr_free_pages相同。
  • nr_zone_inactive_anon 和 nr_zone_active_anon ,分别是非活跃、活跃的匿名页数。
  • nr_zone_inactive_file 和 nr_zone_active_file,分别是非活跃、活跃的文件页数。

当某个Node的内存不足时,系统可以从其他Node寻找空闲内存,也可以在本地回收内存,具体选用哪种模式,可以通过 /proc/sys/vm/zone_reclaim_mode 来调整:

  • 默认的0,表示既可以从其他Node寻找空闲内存,也可以在本地回收内存。
  • 1、2、4表示只回收本地内存,2表示回写脏数据以回收内存,4表示可以用Swap方式回收内存。 

综合上面讲述,知道了Linux下的两种内存回收机制,那么在实际使用是,应该怎么去定优先使用那种回收机制?其实可以配置内核选项,/proc/sys/vm/swappiness 来调整使用 Swap 的积极程度,范围在0~100。值越大,使用Swap越积极,即更倾向于匿名页内存回收,值越小,越消极使用 Swap,也就更倾向于使用文件页回收。注意,这里的0~100不是百分比,而是权重,即使设置为0,即当剩余内存 + 文件页 小于 页高阈值,也仍然会有Swap发生。

3. 实战分析

前面介绍free的时候,应就能看到输出结果中的swap一项,如下:

$ free
             total        used        free      shared  buff/cache   available
Mem:        8169348      331668     6715972         696     1121708     7522896
Swap:             0           0           0

Swap大小是0,说明机器没有配置Swap。下面说说如何开启Swap,首先要知道,Linux本身支持两种类型的Swap,即 Swap分区和 Swap文件。以Swap文件为例,这里配置8GB 的 Swap文件:

# 创建Swap文件
$ fallocate -l 8G /mnt/swapfile
# 修改权限只有根用户可以访问
$ chmod 600 /mnt/swapfile
# 配置Swap文件
$ mkswap /mnt/swapfile
# 开启Swap
$ swapon /mnt/swapfile

$ free
             total        used        free      shared  buff/cache   available
Mem:        8169348      331668     6715972         696     1121708     7522896
Swap:       8388604           0     8388604

Swap空间变为8GB,说明Swap 已经正常开启。运行下面的dd命令,模拟大文件读取:

# 写入空设备,实际上只有磁盘的读请求
$ dd if=/dev/sda1 of=/dev/null bs=1G count=2048

在另一个终端使用 sar 命令查看内存个指标的变化情况:

# 间隔1秒输出一组数据
# -r表示显示内存使用情况,-S表示显示Swap使用情况
$ sar -r -S 1
04:39:56    kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
04:39:57      6249676   6839824   1919632     23.50    740512     67316   1691736     10.22    815156    841868         4

04:39:56    kbswpfree kbswpused  %swpused  kbswpcad   %swpcad
04:39:57      8388604         0      0.00         0      0.00

04:39:57    kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
04:39:58      6184472   6807064   1984836     24.30    772768     67380   1691736     10.22    847932    874224        20

04:39:57    kbswpfree kbswpused  %swpused  kbswpcad   %swpcad
04:39:58      8388604         0      0.00         0      0.00
…

04:44:06    kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
04:44:07       152780   6525716   8016528     98.13   6530440     51316   1691736     10.22    867124   6869332         0

04:44:06    kbswpfree kbswpused  %swpused  kbswpcad   %swpcad
04:44:07      8384508      4096      0.05        52      1.27

sar 的输出有两个表格,第一个显示了内存使用情况,第二个显示了Swap 使用情况。其中,各个指标的 kb 前缀表示这些指标的单位是 KB。这里介绍几个之前没有见过的指标:

  • kbcommit:表示当前系统负载需要的内存。实际上是为了保证系统内存不溢出,对需要内存的估计值。%commit,就是这个值相对总内存的百分比。
  • kbactive :表示活跃内存,也就是最近系统使用过的内存,不会被回收。
  • kbinact :表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。  

再来分析一下相关的现象,总的内存使用率(%memused)在不断增长,从23%涨到98%,并且主要内存都被 kbbuffers 占用。具体来说:

  • 刚开始时,剩余内存(kbmemfree)不断减少,而缓冲区(kbbuffers )不断增大,因此剩余内存是分配给了缓冲区。
  • 一段时间后,剩余内存很小,而缓冲区占用了大部分内存。Swap的使用开始增大,kbmemfree 和 kbbuffers只在小范围波动。

停止sar命令,使用cachetop 命令,观察缓存使用情况:

$ cachetop 5
12:28:28 Buffers MB: 6349 / Cached MB: 87 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
   18280 root     python                 22        0        0     100.0%       0.0%
   18279 root     dd                  41088    41022        0      50.0%      50.0%

可以看到 dd 进程的读写请求只有50%的命中率,未命中的缓存页数(MISSES) 41022 ,可以得出结论,正是dd导致了缓冲区升高。但是,为什么Swap也会跟着升高?缓冲区占了大部分内存,而且是可回收内存,在内存不够用时,不应该优先回收缓冲区吗?

这种情况,我们还得查看 /proc/zoneinfo,观察剩余内存、内存阈值、匿名页和文件页的活跃情况,停止cachetop命令,执行以下命令:

# -d 表示高亮变化的字段
# -A 表示仅显示Normal行以及之后的15行输出
$ watch -d grep -A 15 'Normal' /proc/zoneinfo
Node 0, zone   Normal
  pages free     21328
        min      14896
        low      18620
        high     22344
        spanned  1835008
        present  1835008
        managed  1796710
        protection: (0, 0, 0, 0, 0)
      nr_free_pages 21328
      nr_zone_inactive_anon 79776
      nr_zone_active_anon 206854
      nr_zone_inactive_file 918561
      nr_zone_active_file 496695
      nr_zone_unevictable 2251
      nr_zone_write_pending 0

你会发现,剩余内存在小范围内波动,当它小于页低阈值,又会突然增大到大于页高阈值的一个值。再结合上面sar 和 cachetop的结果综合得出结论,剩余内存和缓冲区的波动变化,正是内存回收和缓存再次分配的循环往复。

还有一个有趣的现象,当多次运行dd 和 sar ,发现有时候Swap用的比较多,有时候又用的比较少,但是缓冲区波动更大。也就是系统回收内存时,有时候回收文件页更多,有时候回收匿名页更多。显然回收不同类型的内存的倾向不明显,去查看swappiness 的配置:

$ cat /proc/sys/vm/swappiness
60

默认值60,这是相对中和的配置,系统会根据实际运行情况,选择合适的回收类型,比如回收不活跃的匿名页,或者不活跃的文件页。

到这里我们已经找出了Swap发生的根源,另一个问题,刚才的Swap影响了那些程序,也就是Swap换出了哪些进程的内存?可以查看 /proc/pid/status,进程Swap换出的虚拟内存大小保存在VmSwap:

# 按VmSwap使用量对进程排序,输出进程名称、进程ID以及SWAP用量
$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
dockerd 2226 10728 kB
docker-containe 2251 8516 kB
snapd 936 4020 kB
networkd-dispat 911 836 kB
polkitd 1004 44 kB

这也说明了一点,虽然缓存属于可回收内存,但在类似大文件拷贝的场景下,系统还是会用Swap回收匿名内存,而不仅仅是回收占用绝大多数内存的文件页。

最后,案例结束后记得关闭Swap,即 swapoff -a。实际上,关闭再打开,也是一种常用的Swap空间清理方法,即 swapoff -a && swapon -a 。

通常,降低Swap的使用,可以提升系统的整体性能。这里也总结几种降低Swap的方法:

  • 禁止Swap,现在服务器的内存足够大,一般禁掉Swap即可。大部分云平台中的虚拟机都是默认禁掉Swap。
  • 如果实在需要Swap,可以降低swappiness的值,减少内存回收时 Swap的使用倾向。
  • 响应延迟敏感的应用,如果在开启Swap的服务器中运行,可以使用库函数mlock() 或者 mlockall() 锁定内存,阻止它们内存换出。

以上是工作之余的学习总结,因CSDN网站的变态要求,这里不能提供内容来源。

发布了37 篇原创文章 · 获赞 20 · 访问量 4936

猜你喜欢

转载自blog.csdn.net/qq_24436765/article/details/103822548