Blog:
使用Squashfs和Overlayfs提高嵌入式Linux文件系统可靠性
在使用嵌入式 Linux 系统的时,会出现由于设备意外断电引起文件系统损坏而最终使该设备无法启动的现象。为了应对这种情况,通常会从硬件设计如采用备用电源,无论是锂电池还是超级电容等,或者从系统软件设上加以规避。本文接下来将介绍如何使用 squashfs 只读文件系统制作 Linux 系统文件,并采用 overlayfs 为用户目录增加可写权限。演示采用 Colibri iMX6 计算机模块,该方法同样也适用于 Toradex 其他产品,如 iMX8 计算机模块。
Squashfs 是一种只读压缩文件系统,通常被用于数据备份或者系统资源受限的计算机系统上使用,如 Linux 发行版的 LiveCD,OpenWRT 系统也采用 squashfs。OverlayFS 一个结合其他文件系统的联合挂载,将多个挂载点叠加为一个目录。常见的应用是在一个只读的分区上叠加可读写的另一个分区。嵌入式 Linux 设备通常的功能都是被设计好的,极少需要在后期安装其他软件或更改 Linux 系统软件,更多的是更新设备应用程序和相关数据。因此基于 squashfs 的只读文件系统,结合 overlayfs 为用户应用和数据提供读写操作,能够提高嵌入式 Linux 文件系统可靠性。
在 Colibri iMX6 的 eMMC 上我们将使用以下分区规划。BootFS 为 FAT32 格式,该分区上包含 Linux 内核文件,device tree 等启动文件,如果是 iMX8,则还包含一些其他固件文件。该分区通常只在文件系统烧写阶段被写入。RootFS 分区是 Linux 运行的文件系统,usr, bin, lib, etc, home 等目录都在上面。一般该分区是EXT3,EXT4 格式,支持文件的写入和删除。而文件系统的损坏也常发生于此,最终导致设备启动失败。因此我们这里会采用只读格式的 squashfs 。UserData 是能够读写的 EXT4 分区。该分区通过 overlayfs 会被挂载到原本位于只读 squashfs 中的 /home/root 目录。用户应用可以毫无察觉得使用该目录,在上面写入和删除文件,但不破坏只读 squashfs 文件系统,所有的操作都会被转移到 UserData 分区上。用户的应用也会存在 UserData 分区上,启动的时候从这里加载应用程序。该分区是可写的,所有这上面的文件是可以被更新。
下面我们会具体说明如何在 Yocto 环境生成符合上面规划的 BSP ,并通过 Toradex Easy Installer 工具实现这些分区和写入文件。
首先需要在 Yocto 环境中生成 squashfs 格式的系统文件 rootfs。修改 build/conf/local.conf ,结尾添加以下内容。
IMAGE_FSTYPES_append = " squashfs"
默认 Linux 内核配置下 squahfs 是通过加载内核驱动模块实现对其支持,而模块驱动文件位于 rootfs 中,为了保证 Linux 内核在启动时能够正确处理该格式,需要将 squahfs 内核驱动模块配置为静态驱动,直接编译进内核中。为内核配置的文件系统支持添加 squahfs 和 overlay 格式。
$ MACHINE=colibri-imx6 bitbake -c menuconfig virtual/kernel
→ File systems
<*> Overlay filesystem support
→ File systems → Miscellaneous filesystems
<*> SquashFS 4.0 - Squashed file system support
EXT4 格式的 UserData 分区会通过 overlay 机制挂载到只读的 rootfs 上的 /home/root,但这之前需要通过 fstab 将该分区挂载到系统中,/dev/mmcblk0p3 -> /media/data-> /home/root。在 Yocto 中,layers/meta-toradex-demos/recipes-core/base-files/base-files/fstab 会被编译到 Colibri IMX6 BSP 中,在该文件中添加:
/dev/mmcblk0p3 /media/data auto defaults,sync,noauto 0 0
然后再添加一个开机自启动脚本,将 /media/data 使用 overlay挂载到 /home/root。 在 layers/meta-toradex-bsp-common/recipes-core目录中添加 mount-overlayfs 文件夹,里面包含编译需要的 bb 文件和 systemd service。mount-overlayfs.bb 中 install -d ${D}/media/data 会在 /media 目录中创建 data 文件夹,FILES_${PN} = "/media/data" 将该空文件夹添加到 BSP 中。 mount-overlayfs.service 通过 RequiresMountsFor 保证 /media/data 目录通过 fstab 挂载后才运行。Overlay 的目录结构如下,lowerdir 为/home/root upperdir 为 /media/data/home/root/upper,workdir 为 /media/data/home/root/work。这样应用可以在 /home/root 下直接读写文件,overlayfs 对应用和用户都是透明的。
[Service]
Type=simple
ExecStart=/bin/sh -c 'mount -t overlay -o lowerdir=/home/root,upperdir=/media/data/home/root/upper,workdir=/media/data/home/root/work overlay /home/root'
在 build/conf/local.conf 中将 mount-overlayfs 添加到 BSP 中。
IMAGE_INSTALL_append = " mount-overlayfs"
由于 rootfs 是只读格式,无法像之前一样直接在开发板上运行 systemctl 命令添加开机自启动脚本。我们需要像上面一样,在 Yocto 中增加一个 test-app 来开机自动运行位于 UserData 分区(该分区通过 overlay 会被最终挂载到 /home/root 目录)上的测试程序 test。在 layers/meta-toradex-bsp-common/recipes-core目录中添加 test-app 文件夹,里面包含编译需要的 bb 文件和 systemd service。
在 build/conf/local.conf 中将 test-app 添加到 BSP 中。
IMAGE_INSTALL_append = " mount-overlayfs test-app"
至此我们已经通过修改 Yocto 能够生成所需结构的BSP,执行下面命令完成编译任务。
$ MACHINE=colibri-imx6 bitbake tdx-reference-minimal-image
在 build/deploy/images/colibri-imx6 有相关文件生成,我们需要下面两个文件,文件名字中的时间戳对应具体编译的日期。
- Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0.tar
- Colibri-iMX6_Reference-Minimal-Image.rootfs.squashfs
解压 Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0.tar。因为rootfs 是 squahfs 格式,需要使用定制的 boot.src 来启动。
$ cd ~/
$ tar vxf Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0.tar
$ cd Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0
$ tar vxf Reference-Minimal-Image-colibri-imx6.bootfs.tar.xz
修改其中的 emmcargs_set 参数。
env set emmcargs_set 'env set rootfsargs root=/dev/mmcblk0p2 rootfstype=squashfs ro rootwait'
可以使用该 boot.cmd 直接生成对应的 boot.src, 替换解压目录中同名文件, 重新打包 Reference-Minimal-Image-colibri-imx6.bootfs.tar.xz
$ cd Reference-Minimal-Image-colibri-imx6.bootfs
$ rm boot.src
$ mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "Distro boot script" -d boot.cmd boot.scr
$ cd ..
$ tar cJf Reference-Minimal-Image-colibri-imx6.bootfs.tar.xz -C Reference-Minimal-Image-colibri-imx6.bootfs .
将 squashfs 的 rootfs 文件系统复制到 Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0 目录中。
$ cp Colibri-iMX6_Reference-Minimal-Image.rootfs.squashfs Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0
接下来创建需要复制到 UserData 分区中的文件。如上面提到的 overlay 挂载需要有对应目录结构,依次创建以下目录,并将测试程序 test 复制到 upper 目录中。这里的测试程序 test 是一个非常简单的 C 应用,它会输出“Hello Toradex!”到系统日志中。
$ cd ~/Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0
$ mkdir rootfs
$ cd rootfs
$ mkdir -p home/root/upper
$ mkdir -p home/root/work
$ cp ~/test home/root/upper
$ tree
打包为 rootfs.tar.xz。
$ cd ..
$ sudo tar cJf rootfs.tar.xz -C rootfs .
修改 image.json 文件,在blockdevs 中增加 RAW 格式分区用于直接写入 squashfs 系统文件,以及 EXT4 格式的DATA分区用于写入上面生成的 rootfs.tar.xz。
Colibri-iMX6_Reference-Minimal-Image-Tezi_5.1.0-devel-20201112022057+build.0 目录下最终包含以下文件。
将上面的 BSP 通过 Toradex Easy Installer 重新安装到 Colibri iMX6 上,安装的时候注意需要点击 Erase 擦除 eMMC 上的内容。
烧写后重启模块。可以通过 mount 命令查看挂载分区的情况。
root@colibri-imx6:~# mount
/dev/mmcblk0p2 on / type squashfs (ro,noatime)
……
/dev/mmcblk0p3 on /media/data type ext4 (rw,relatime,sync)
overlay on /home/root type overlay (rw,relatime,lowerdir=/home/root,upperdir=/media/data/home/root/upper,workdir=/media/data/home/root/work)
mmcblk0p2 为写入 squashfs 只读文件系统的分区,例如在 /etc 目录无法创建文件。
root@colibri-imx6:/etc# mkdir test-folder
mkdir: can't create directory 'test-folder': Read-only file system
EXT4 格式的 /dev/mmcblk0p3 分区上是用户文件,被挂载到 /media/data。而 /home/root 会通过 overlay 具有可读写权限。
root@colibri-imx6:~# cd ~/
root@colibri-imx6:~# pwd
/home/root
root@colibri-imx6:~# mkdir test-folder
root@colibri-imx6:~# ls
test test-folder
root@colibri-imx6:~# ls -lh
-rwxrwxr-x 1 1000 1000 11.3K Nov 11 2020 test
drwxr-xr-x 2 root root 4.0K Feb 7 16:25 test-folder
位于 UserData 上的测试程序 test 也在开机的时候自动运行。
root@colibri-imx6:~# journalctl -u test-app
-- Logs begin at Fri 2020-02-07 15:50:53 UTC, end at Fri 2020-02-07 16:11:49 UTC. --
Feb 07 15:50:57 colibri-imx6 systemd[1]: Started start a demo on overlay mount folder.
Feb 07 15:50:57 colibri-imx6 test[456]: Hello Toradex!
Feb 07 15:50:57 colibri-imx6 systemd[1]: test-app.service: Succeeded.
总结
上面我们演示结合使用 squashfs 和 overlay,将系统文件放在一个只读分区,把读写操作在单独的分区上进行。降低系统文件因意外断电受损从而导致无法启动的风险。在使用只读 squashfs 只读文件系统时需要保证 rootfs 尽量精简,建议在 Reference-Minimal-Image 基础上构建用户自己的 BSP,甚至对其进行裁剪。