|
| 1 | ++++ |
| 2 | +date = '2025-11-01T21:57:31+08:00' |
| 3 | +draft = true |
| 4 | +title = '关于Linux的swap那些事情' |
| 5 | ++++ |
| 6 | + |
| 7 | +## 引子 |
| 8 | + |
| 9 | +无论在哪个安装 Arch 的教程里面,你都能看到要求你设置一个 `swap` 分区,但是,这个分区真的那么有必要吗? |
| 10 | + |
| 11 | +我最早听到 `swap` 这个字,大概是安卓 2.3-4.0 时代吧。那时候的 CPU 性能不行,存储性能也很差,但是人们却执着于把 `swap` 放在存储卡上面,总共 `128M` 的内存,要在卡上放 `64M` 进行交换。不过对于本来就慢的安卓手机,能够有更大的内存以便运行大型一点的游戏,就算慢一点,也比较值得。 |
| 12 | + |
| 13 | +然而在现代系统上面,手机厂商又把这个“内存拓展”拿出来,许多人对此嗤之以鼻,然而这完全不是一回事。 |
| 14 | + |
| 15 | +## 把内存变小,而不是放到低速存储上面 |
| 16 | + |
| 17 | +首先,能够实现这一切的前提,是内存虚拟化。 |
| 18 | + |
| 19 | +一个程序看到的大量的地址空间,其实并不是 1:1 的映射到物理内存上面的。通过系统调用 `mmap`,操作系统记录了一个程序看到的内存是怎么对应到物理内存上去,但是,为什么一定是物理内存上面呢? |
| 20 | + |
| 21 | +于是有了 `swap`,当我内存不够的时候,直接把程序运行时的内存(匿名页)驱逐到 `swap` 上面,然后往映射表记录,当再次需要访问他的时候,触发一个“缺页中断”,就把 `swap` 的内存重新搬回物理内存上面,这一切对于程序来说都是透明的。 |
| 22 | + |
| 23 | +随着性能的发展,人们发现,这个把匿名页搬回物理内存的过程实在是太慢了,浪费了大量的 CPU 时间,那么为什么不让做点事情?于是,内存压缩出现了。 |
| 24 | + |
| 25 | +### ZRAM |
| 26 | + |
| 27 | +`ZRAM`,顾名思义,就是把内存压缩。 |
| 28 | + |
| 29 | +不过,人们用的还是 `swap` 的那套思路,把内存中的一部分划分出来,然后创建一个块设备,然后需要的时候,把内存压缩进去,这就是 `ZRAM`。 |
| 30 | + |
| 31 | +我们可以通过 `swappiness` 参数,控制操作系统内核把内存压缩进入 `swap` 的激进性,现在的值取 `0-200` 了,值越高,越倾向于把内存放入交换分区。如果是传统的 `swap`,那么我们希望不要那么激进,因为磁盘总归是慢的;但是对于一个压缩的内存块来说,应该把这个值取很高,因为对于 `zstd` 和 `lz4` 这样的算法来说,压缩/解压的速度非常快。 |
| 32 | + |
| 33 | +#### 存到极限 |
| 34 | + |
| 35 | +如果内存的压力持续增大,`ZRAM` 会有用完的时候。这又分为两种情况: |
| 36 | + |
| 37 | +#### 1. `ZRAM` 达到了设置的压缩前的数据的最大值。 |
| 38 | + |
| 39 | +这时候如果没有配置回写块的时候,系统就会去寻找一个优先级更低的 `swap` 了。如果没有,那么就很可能触发 `OOM`。 |
| 40 | + |
| 41 | +配置了回写块的话,`ZRAM` 会把其中最少使用的数据驱逐到回写块中。这个回写块跟 `swap` 是不相容的,只能用于一种用途。 |
| 42 | + |
| 43 | +如果这时候需要提取写入到回写块中的数据,那么就直接从磁盘解压到物理内存中,因为是压缩的数据,不仅减少了对磁盘的读写,还不让 CPU 闲着。 |
| 44 | + |
| 45 | +#### 2. `ZRAM` 还没满,但是物理内存就要满了 |
| 46 | + |
| 47 | +这种情况一般是 `ZRAM` 块最大值太高了。 |
| 48 | + |
| 49 | +同样也会造成 `ZRAM` 往回写块驱逐匿名页,但是如果回写块写入很慢造成了阻塞,同时内存占用还在增加,那么操作系统可能就触发 `OOM` 开始杀进程了。 |
| 50 | + |
| 51 | +### ZSWAP |
| 52 | + |
| 53 | +还有另外一种技术,但是我之前都没听说过。 |
| 54 | + |
| 55 | +这项技术配置起来感觉更加 native 一点,因为是直接往内核参数里面写。 |
| 56 | + |
| 57 | +`ZSWAP` 其实不会创建一个块,但是需要 `swap` 的存在,当系统打算往 `swap` 里面写入数据的时候,`ZSWAP` 会拦截写入,然后进行压缩到内存中的一部分区域。 |
| 58 | + |
| 59 | +`ZSWAP` 同样会在内存压力高的时候驱逐匿名页到 `swap` 中,这一点跟 `ZRAM` 很像,从 `Linux 6.8` 开始,支持禁用回写块,仅仅用作内存压缩。 |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +所以你会发现,`ZRAM` 加上了回写块,那么就是普通的 `ZSWAP`;如果 `ZSWAP` 禁用了回写块,那么就相当于一个普通的 `ZRAM`,从使用的角度,确实是这样的。 |
| 64 | + |
| 65 | +## 如何选择 |
| 66 | + |
| 67 | +既然这两者都可以在使用时相互替代,那么究竟如何选择呢? |
| 68 | + |
| 69 | +我的推荐是,如果内存足够,也要使用休眠的设备,比如笔记本之类,使用 `ZSWAP` 并且禁用回写块。这样 `swap` 就专注于配置休眠,至于避免 `OOM` 来说,笔记本应该不会有需要很多内存的场景...吗? |
| 70 | + |
| 71 | +如果服务器,或者高性能的PC,那么还是推荐使用 `ZRAM` 加上一个小 `SWAP`,这样可以很好的避免了内存使用的尖锋,也享受了内存压缩的红利。 |
| 72 | + |
| 73 | +但是全都要呢?还是配置 `ZRAM` 加上一个回写块,和一个休眠 `swap`。不过如果使用内存过多,还是会溢出到休眠的swap导致休眠失败。想要修改,可能得 HACK 一点的办法,在休眠前激活这个分区,然后再绕过检查... |
| 74 | + |
| 75 | +不推荐同时使用 `ZSWAP` 和 `ZRAM`,这样会造成冲突。 |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +两者有什么优势呢? |
| 80 | + |
| 81 | +`ZSWAP`:管理起来更直观,因为是钩子,所以不会出现一个 `swap` 块。 |
| 82 | + |
| 83 | +`ZRAM`:不仅仅是内存压缩,实际上块设备可以格式化来存东西。 |
| 84 | + |
| 85 | +## 观察 |
| 86 | + |
| 87 | + |
| 88 | + |
| 89 | +这是一个 `ZSWAP` 的 `htop` 图,其中深蓝色的一部分就是 `ZSWAP` 做的事情。其中 `266M` 的大部分都是压缩在内存中的,不过显示在了 `swap` 中。 |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +如果是一个 `ZRAM` 的设备,那么 `htop` 甚至可以单独调出一条来先是它,从观察角度来说,`ZRAM` 相对方便一些。 |
0 commit comments