Swap 分区 也称交换分区,它可以是硬盘上的一个分区,也可以是一个指定的 Swap File 。
当内存不足时,内存中长时间不用的数据会被写到 Swap 并从内存删除,从而空出内存给其他程序使用。
当然,这也意味着,CPU 从 Swap 中,即硬盘中,读写数据的速度比从内存中的速度慢得多。
Swap 分区可以使用 fdisk 或者其他工具进行创建,但我更倾向于使用 Swap 文件。
使用 sudo fallocate -l 8G /swapfile
在根目录下创建一个名为 swapfile 大小 8G 的文件。
非 Debian 系系统,比如 OpenSuSE 用 sudo dd if=/dev/zero of=/swapfile count=8192 bs=1MiB
有相同效果。
sudo chmod 600 /swapfile
修改 /swapfile 的权限,只有 root 用户才可读写。
sudo mkswap /swapfile
格式化 swap 文件。
sudo swapon /swapfile
激活 swap。
sudo swapon --show
查看是否开启 swap,结果类似下面这样:
NAME TYPE SIZE USED PRIO
/swapfile file 8G 0B -2
把 /swapfile 添加到 /etc/fstab 使系统自动挂载 swap:
/swapfile swap swap defaults 0 0
或者直接使用以下命令:
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab
在删除一个 swap file 前,必须先停用它。
执行 sudo swapoff <name>
停用指定的 swap,其中 <name>
可在 sudo swapon --show
中查询。
从 /etc/fstab 中删除相关条目,最后 rm -rf /swapfile
删除文件。
如果有多个 swap 文件或者分区,可以给它们各自优先级值,范围是 0-32767 。数字越大,越优先被使用。
优先级可以在 /etc/fstab 中通过 pri 参数指定:
/dev/sda1 none swap defaults,pri=100 0 0
/dev/sdb2 none swap defaults,pri=10 0 0
或者通过 swapon 的 −p 即 −−priority 参数指定:
sudo swapon -p 100 /dev/sda1
如果两个或以上的 swap 有同样的优先级,并且它们都是当前可用的最高优先级,page 会按照循环的方式在它们之间分配。
内存回收操作针对的 page 由定义在 mm/vmscan.c 内一个 enum 中的链表组成。主要有 4 个:
就是说,内存回收操作主要针对的就是内存中的文件页 file cache 和匿名页 anon 。
关于内存页活跃 active 与不活跃 inactive 的判断由内核使用 LRU 算法进行处理并标记,不详细展开这个过程。
整个扫描的过程分几个循环:
进行 swap 操作时,先换出 inactive page。
如果 page 是 file cache,则判断其是否为脏数据,如果是脏数据就写回,不是脏数据可以直接释放。
这样看来,内存回收这个行为会对两种内存的使用进行回收:
因为针对 filebased 的内存,没必要交换,其数据原本就在硬盘上,回收这部分内存只要在有脏数据时写回,并清空内存就好,以后有需要再从对应的文件读回来。
内存对匿名页和文件缓存一共用了四条链表进行组织,回收过程主要是针对这四条链表进行扫描和操作。
sysctl
的参数中有一个叫 swappiness ,取值范围 0-100 ,默认 60 。
cat /proc/sys/vm/swappiness
60
注意,这个 60 不是百分比,只用来定义内核释放内存的倾向:
在这里可以理解 file-backed page 这个词的含义了,实际上就是上文所说的文件映射页的数据。
如果回收内存可以有两种途径:
那么内存回收的时候要平衡两种回收手段的使用,以达到最优。除此之外,在处理匿名缓存时,如果符合交换条件的内存较长,比如可以交换的内存有 100M,但是目前只需要 50M,实际交换 50M 就好,不用交换所有的数据。
Linux 内核中处理上述逻辑的代码写在 get_sacn_count() ( 这个方法被 shrink_lruvec() 调用 ) 中,swappiness 是它所需要的一个参数。这个参数影响内核在清空内存的时,对于 file-backed 回写与匿名缓存进入 swap 的倾向。
get_sacn_count() 函数的处理部分代码,关于 swappiness 的注释的很清楚: 如果 swappiness 设为 100,那么匿名缓存和文件缓存将用同样的优先级进行回收。
很明显,使用清空文件的方式将有利于减轻内存回收时可能造成的 I/O 压力。因为如果 file-backed 中的数据不是脏数据的话,可以不用写回,直接丢弃,这样就没有 I/O 发生,若进行交换,就一定会由 I/O 操作。
所以系统默认将 swappiness 的值设置为 60,这样回收内存时,对 file-backed 内存的清空比例会更大,内核将会更倾向于进行缓存清空而不是交换。
这里的 swappiness 取值 60 不是说内核回收的时会按照 60:40 的比例去做相应的 swap 和清空 file-backed。在做这个比例计算的时候,内核还要参考当前内存使用的其他信息。get_sacn_count() 函数中有详细的实现算法。
要明确的概念是: swappiness 的值是用来控制内存回收时,回收的匿名页更多一些还是回收的文件缓存更多一些。
swappiness 设置为 0 时,如果 file-backed 数据清理完成而内存还是不够用的话,内核依旧会进行 swap 操作。
除此之外,还有一种情况会导致内核直接使用 swap: 触发全局回收,并且 zonefile + zonefree <= high_wmark_pages(zone) 条件成立时,就将 scan_balance 这个标记置为 SCAN_ANON 。后续处理 scan_balance 时,内核检测到它的值是 SCAN_ANON ,则一定会进行针对匿名页的 swap 操作。
要理解这个行为,首先要搞清楚什么是高水位标记 ( high_wmark_pages )。
Linux 内核使用 水位标记 watermark 来描述内存压力情况,由 3 种标记 high low min 构成。
标记的含义分别为:
而小于 min 的这部分内存,内核是保留给特定情况下使用的,一般不会分配。
内存回收行为基于 watermark 进行决策:
明白了水位标记的概念之后, zonefile + zonefree <= high_wmark_pages(zone) 这个公式就能理解了:
内核一般认为,如果 zonefile 还有的话,就可以尽量通过清空文件缓存获得部分内存,不必只用 swap 方式处理 anon 内存数据。
就是说在全局回收的状态下 ( 有 global_reclaim(sc) 标记 ),如果当前的 文件映射内存总量 + 剩余内存总量 <= watermark[high] 时直接使用 swap。
这个判断对系统的影响是, swappiness 设置为 0 时,即使有剩余内存,也可能出现 swap 操作 。
watermark 是根据 内存总大小 和 /proc/sys/vm/min_free_kbytes 参数进行运算得来的,这个参数是:
这个参数本身决定了系统中每个 zone 的 watermark[min] 的值大小。然后内核根据 min 的大小并参考每个 zone 的内存大小分别算出每个 zone 的 low 水位和 high 水位值。
具体逻辑可以参见源代码目录下的 mm/page_alloc.c。
可以从 /proc/zoneinfo 文件中查看当前系统的相关的信息和使用情况。
/etc/sysctl.d/90-mysettings.conf
vm.swappiness=20
参数 vm.vfs_cache_pressure 表示内核回收 directory 和 inode cache 内存数据的 倾向:
/etc/sysctl.d/90-mysettings.conf
vm.vfs_cache_pressure=50
频繁使用 swap 会降低内存性能。要避免内存性能降低有两个思路,一个是提高读写磁盘的速度,比如将机械硬盘换成固态盘,一个是降低使用 swap 的频率。
Linux kernel 3.11 引入了 zswap 功能。
zswap 单独使用一部份 RAM 作为存储池,将原本要被交换的 RAM 数据进行压缩,放入存储池中,一旦池满或 RAM 耗尽,最近最少使用 ( LRU ) 的页就会被解压缩并写入磁盘,就像 zswap 不存在时一样。将池中的页解压缩到 swap 后,会释放池中的压缩版本。
这个思路可以延缓发生 swap 的进度。
sudo echo 1 | tee /sys/module/zswap/parameters/enabled
可以临时启用 zswap。
若要 zswap 随内核启动,则需要修改内核的启动参数:
编辑 /etc/default/grub 配置文件。在 GRUB_CMDLINE_LINUX_DEFAULT 中加上:
GRUB_CMDLINE_LINUX="..."
GRUB_CMDLINE_LINUX_DEFAULT="... zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=20 zswap.zpool=z3fold"
其中:
在 Debian 系发行版上执行 sudo update-grub
或者 sudo grub-mkconfig -o /boot/grub/grub.cfg
重新生成 GRUB 配置文件后,重启系统。
在 RedHat 系发行版上执行 sudo grub2-mkconfig -o /boot/grub2/grub.cfg
重新生成 GRUB 配置文件后,重启系统。
重启后,执行 dmesg | grep zswap
确认 zswap 是否正常开启。
在 archlinux 上,还可以通过 systemd-swap ( 这包是 AUR 源里的,Debian 和 Ubuntu 默认没装这个野包 ) 开启 zswap:
注意: 并不推荐使用这个软件。
sudo pacman -S systemd-swap --noconfirm
sudo systemctl enable systemd-swap
sudo systemctl restart systemd-swap
若想要通过 systemd-swap 管理 zswap 可以通过 /etc/systemd/swap.conf 进行配置:
$ grep -i zswap /etc/systemd/swap.conf
# Zswap
# Zswap create compress cache between swap and memory for reduce IO
# https://www.kernel.org/doc/Documentation/vm/zswap.txt
zswap_enabled=1
zswap_compressor=lz4 # lzo lz4
zswap_max_pool_percent=25 # 1-99
zswap_zpool=zbud # zbud z3fold
zram 与 zswap 类似,都要在内存中单独开辟一块空间。区别在于,zram 使用内存模拟 swap 设备,不会把 swap 中的数据放入磁盘中。
zram 在内存中单独开辟一块空间并将其模拟成为磁盘设备,当 swap 行为产生时,数据被压缩后放在 zram 模拟的磁盘设备中 ( 还是在内存里 )。
由于在内存中压缩和解压缩的速度远比在磁盘中快,因此 zram 在移动终端设备广泛被应用。zram 是基于 ram 的 block device,默认的 swap priority 会很靠前。只有 zram 空间满了,系统才会考虑其他的 swap 设备。当然这个优先级可以自行配置。
注意: zram + swap 的性能可能差于 zswap,zram 只在没有 swap 的情况下很好用。
echo lz4 > /sys/block/zram0/comp_algorithm
echo 2G > /sys/block/zram0/disksize
echo $((2*1024*1024*1024)) > /sys/block/zram0/disksize
mkswap /dev/zram0 && swapon /dev/zram0
insmod zram.ko num_devices=4
压缩流是内核 v3.14 新加入的功能。
v3.14 之前的 zram 仅使用一个压缩流,每个写 ( 压缩 ) 操作独享各自的压缩流 ( 主要是独享缓存,还能给流指定压缩算法 )。
单压缩流的情况下,如果写 ( 压缩 ) 操作出现卡顿或崩溃,所有的写 ( 压缩 ) 操作将一直处于等待状态;而多压缩流的架构会让写 ( 压缩 ) 操作并行执行,可以提高压缩效率和稳定性。
cat /sys/block/zram0/max_comp_streams
可以查看压缩流数量,默认数量是 1。
可以 echo 3 > /sys/block/zram0/max_comp_streams
直接向 proc 文件写入要使用几个压缩流。
/sys/block/zram0 路径下的文件的其他参数:
Name | Access | Description |
---|---|---|
disksize | RW | 显示和设置该块设备的内存大小 |
initstate | RO | 显示设备的初始化状态 |
reset | WO | 重置设备 |
num_reads | RO | 读数据的个数 |
failed_reads | RO | 读数据失败的个数 |
num_write | RO | 写数据的个数 |
failed_writes | RO | 写数据失败的个数 |
invalid_io | RO | 非页面大小对齐的I/O请求的个数 |
max_comp_streams | RW | 最大可能同时执行压缩操作的个数 |
comp_algorithm | RW | 显示和设置压缩算法 |
notify_free | RO | 空闲内存的通知个数 |
zero_pages | RO | 写入该块设备的全为的页面的个数 |
orig_data_size | RO | 保存在该块设备中没有被压缩的数据的大小 |
compr_data_size | RO | 保存在该块设备中已被压缩的数据的大小 |
mem_used_total | RO | 分配给该块设备的总内存大小 |
mem_used_max | RW | 该块设备已用的内存大小,可以写 1 重置这个计数参数到当前真实的统计值 |
mem_limit | RW | zram 可以用来保存压缩数据的最大内存 |
pages_compacted | RO | 在压缩过程中可用的空闲页面的个数 |
compact | WO | 触发内存压缩 |
RedHat 系发行版中的 zram-generator 可以提供 zram 的自动化管理功能。这个包最早出现在 Fedora 29 与 Debian 12 上,属于 systemd 的一个子项目。
详细配置可以看 man 页面或者 Github 的文档。