好像一年多没写东西了诶…… 其实寒假就想写点啥,但都因为拖延症没写成。可见维护博客也是个挺麻烦的事。这次为了避免拖延,写点简单的东西好了。

前言

Archwiki 的 Maximizing performance 一文很早就提到 “Compressing /usr” 这么个优化方式。就是用 squashfs 压缩 /usr(squashfs 是只读的),然后上面加一层 aufs 实现读写支持。好处就是减小磁盘空间占用,从而减轻 I/O 负担,

不过后来 Linux 内核把 aufs 踢出去了,这个优化也就坑了。加上现在 Arch 现在基本把所有东西都放进了 /usr,init 进程对 /usr 依赖特别强,分出去可能会有各种毛病。于是就不再建议搞这个优化了。

前阵子搞学校图书馆的网络启动查询机。实现是:Arch 系统,文件放在 NFS 上的 squashfs 里,用 aufs 读写。于是重新折腾起 aufs + squashfs 了。后来索性在自己机器上也搞了个,不是压 /usr,而是压 /usr/share。这里就记录一下配置吧。

为什么要压缩 /usr/share,而不是别的?

  1. 除了安装、卸载、升级软件,对 /usr 几乎没有写操作。如果你不进行这些操作,弄个只读的 /usr 都没问题。这就是为什么一般教程只有说压 /usr 的,没有说压 /var 的。
  2. 压缩 /usr/share 比压整个 /usr 安全,没有 /usr/share 系统也能启动进 single 模式(虽然有些功能会受影响)。
  3. /usr/share 几乎是 /usr 中占用空间最大的一部分,且其中有很多小文本文件,压缩效率很高。

配置

先说说目录结构。文件系统相关文件都放在 /sfs/usr_share/:

下面的操作其实都可以开着系统在线完成的,除了最后删除 /usr/share 的部分。

创建 squashfs 压缩档

一条命令的事儿:

 mksquashfs /usr/share /sfs/usr_share/filesystem.sfs \
        -b 32k -comp lzo -Xcompression-level 9

稍微解释:

  1. -b 32k:设置 squashfs 块大小为 32k。大概就是一次读取的最小单位。设大点压缩率会更高,但是读取一个 block 所需的 I/O 操作也会变多。所以要取个适当的值。我试了,32k 就差不多,再大压缩率增加的也不明显。(这主要是因为 /usr/share 里大多是很小的碎文件)
  2. -comp lzo:压缩算法设置为 lzo。这个压缩算法的特点是快速、省资源,压缩比一般般。但这样其实正好适合文件系统压缩,本来文件系统压缩就是牺牲内存/CPU资源来节省I/O资源的。可以看看网上的评测,这个压缩率比 xz/lzma 差一些,但压缩/解压速度快了很多。
  3. 注意压缩算法千万别选 lz7。这个算法也是跟 lzo 差不多的特点,不过内核的 squashfs 模块似乎还不支持该算法……
  4. -Xcompression-level 9:使用最大比率的 lzo 压缩。据说这样只是压缩速度会变慢,解压速度几乎不受影响。

写 systemd 的 mount 单元

按道理说,应该在 fstab 里加上相关项就可以了。但实际上我遇到了很严重的问题,开机经常没挂上 /usr/share。我没仔细追原因,感觉好像是因为 systemd 对挂载顺序作出了些调整,导致了一些问题。最后试了试 systemd 引入的新东西,解决了问题。

众所周知,systemd 造了好多轮子,什么 crontab、网络管理器、日志服务 它都想给接替了。fstab 也是这样,systemd 引入了 .mount 单元这么个东西,用于控制文件系统挂载。

虽然 Arch 还没抛弃 fstab,但其实 fstab 中有些内容已经被 systemd 完全取代了。老 Arch 用户可能记得,以前 /dev、/tmp 的挂载都是要写进 fstab 的,但现在都不用写了。Arch 用户可以看看 systemd 包提供的 /usr/lib/systemd/system/tmp.mount 这个文件,就是 /tmp 的挂载配置。内容很好理解,就不贴了。

正如 systemd 引入的各种单元(unit)一样,mount 单元也可以写各种依赖啊什么的东西,从这点来讲它比 fstab 的功能要强大。这里挂载 /usr/share 就要用到依赖关系:可读分支要在 /usr/share 挂载之前完成挂载。

直接贴文件:

[Unit]
Description = /usr/share, read-only branch

[Mount]
What = /sfs/usr_share/filesystem.sfs
Where = /sfs/usr_share/ro
Type = squashfs
Options = defaults

[Install]
WantedBy = local-fs.target

 

[Unit]
Description = /usr/share
Requires = sfs-usr_share-ro.mount
After = sfs-usr_share-ro.mount

[Mount]
What = none
Where = /usr/share
Type = aufs
Options = br:/sfs/usr_share/rw=rw:/sfs/usr_share/ro=ro

[Install]
WantedBy = local-fs.target

内容也很好看懂。Requires、After 这和 systemd service 的写法基本一致(其实我也不太懂),[Mount] 下就是挂载的说明了,What 后是要挂载的文件系统,Where 是挂载到的地方,Type 是文件系统类型,Options 是挂载参数。相当于:

mount -t <Type> -o <Options> <What> <Where>

不知道有人注意到这个文件名没,尤其是第一个。sfs-usr_share-ro.mount 这样命名其实是 systemd 强制要求的(抄自 man systemd.mount):

Mount units must be named after the mount point directories they control. Example: the mount point /home/lennart must be configured in a unit file home-lennart.mount. For details about the escaping logic used to convert a file system path to a unit name, see systemd.unit(5).

反正就是要和你挂载到的路径一致,把路径里的「/」换成「 -」。挺蛋疼的。

测试、清理

其实现在重启应该就可以了,/usr/share 放那儿反正会被 mount 盖掉。重启后确认 /usr/share 确实挂上 aufs 了,原来的文件是否删除就看心情了吧。

后期保养

说句实话,给 Debian stable 之类的上这个优化可能更有效。Arch 你得经常滚啊,滚一滚 aufs 可写分支就变得很大,这时就得重新压一下 squashfs 了。

重新压的步骤和前面差不多,就是把你现在的 /usr/share 当成没压的重新压一遍,把可写分支清空。不过清空可写分支的操作得离线进行(至少要进 single 模式卸载掉原来的 aufs)。注意安全,不多说了。

 

随便写写,没看懂的人千万不要乱试哦!