树莓派的本领就不多说了。但是在树莓派的应用场合,关机的时候还是显得尴尬,先不说执行 sudo halt 要么需要ssh上去,要么需要有键盘和显示器,更不要说,有的场景可能连网络和显示器都没有,真正的 headless。 但是如果不执行sudo halt直接关电源,那么有很大的概率会损坏SD卡上的文件系统,甚至损坏SD卡。
overlayfs是linux系统下的一种影子文件系统,它可以把真正的存储文件系统作为只读挂载,而把所有文件改动都存放在RAM中,关机或者重启就失效,真正的保护了存储文件系统。 下面就说一下我在树莓派中实施overlayfs的方法,其实很简单。
我参考了这个帖子中介绍的方法:
https://www.raspberrypi.org/forums/viewtopic.php?t=173063#p1151405
1. 首先,禁用交换空间,毕竟树莓派里的交换空间也是占用RAM(tempfs)的,所以如果使用了overlayfs后,交换空间就显得没有意义了。
执行如下3条命令:
- sudo dphys-swapfile swapoff
- sudo dphys-swapfile uninstall
- sudo update-rc.d dphys-swapfile remove
2. 更新系统,保证系统为最新状态
- sudo apt-get update
- sudo ap-get upgrade
3. 保存如下脚本为 overlayRoot.sh
- #!/bin/sh
- # Read-only Root-FS for Raspian using overlayfs
- # Version 1.0
- #
- # Created 2017 by Pascal Suter @ DALCO AG, Switzerland
- # to work on Raspian as custom init script
- # (raspbian does not use an initramfs on boot)
- #
- # Modified 2017-Apr-21 by Tony McBeardsley
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see
- # <http://www.gnu.org/licenses/>.
- #
- #
- # Tested with Raspbian mini, 2017-01-11
- #
- # This script will mount the root filesystem read-only and overlay it with a temporary tempfs
- # which is read-write mounted. This is done using the overlayFS which is part of the linux kernel
- # since version 3.18.
- # when this script is in use, all changes made to anywhere in the root filesystem mount will be lost
- # upon reboot of the system. The SD card will only be accessed as read-only drive, which significantly
- # helps to prolong its life and prevent filesystem coruption in environments where the system is usually
- # not shut down properly
- #
- # Install:
- # copy this script to /sbin/overlayRoot.sh and add "init=/sbin/overlayRoot.sh" to the cmdline.txt
- # file in the raspbian image's boot partition.
- # I strongly recommend to disable swapping before using this. it will work with swap but that just does
- # not make sens as the swap file will be stored in the tempfs which again resides in the ram.
- # run these commands on the booted raspberry pi BEFORE you set the init=/sbin/overlayRoot.sh boot option:
- # sudo dphys-swapfile swapoff
- # sudo dphys-swapfile uninstall
- # sudo update-rc.d dphys-swapfile remove
- #
- # To install software, run upgrades and do other changes to the raspberry setup, simply remove the init=
- # entry from the cmdline.txt file and reboot, make the changes, add the init= entry and reboot once more.
-
- fail(){
- echo -e "$1"
- /bin/bash
- }
-
-
- # Load overlay module
- modprobe overlay
- if [ $? -ne 0 ]; then
- fail "ERROR: missing overlay kernel module"
- fi
-
-
- # Mount /proc
- mount -t proc proc /proc
- if [ $? -ne 0 ]; then
- fail "ERROR: could not mount proc"
- fi
-
-
- # Create a writable fs on /mnt to then create our mountpoints
- mount -t tmpfs inittemp /mnt
- if [ $? -ne 0 ]; then
- fail "ERROR: could not create a temporary filesystem to mount the base filesystems for overlayfs"
- fi
-
-
- # Mount a tmpfs under /mnt/rw
- mkdir /mnt/rw
- mount -t tmpfs root-rw /mnt/rw
- if [ $? -ne 0 ]; then
- fail "ERROR: could not create tempfs for upper filesystem"
- fi
-
-
-
- # Identify root fs device, PARTUUID, mount options and fs type
-
- #rootDev=`blkid -o list | awk '$3 == "/" {print $1}'`
- # Changed here(point to / ) in case the cmd above doesn't work # By ChenYang 20171122
- rootDev=/dev/mmcblk0p2
- rootPARTUUID=`awk '$2 == "/" {print $1}' /etc/fstab`
- rootMountOpt=`awk '$2 == "/" {print $4}' /etc/fstab`
- rootFsType=`awk '$2 == "/" {print $3}' /etc/fstab`
-
-
- # Mount original root filesystem readonly under /mnt/lower
- mkdir /mnt/lower
- mount -t ${rootFsType} -o ${rootMountOpt},ro ${rootDev} /mnt/lower
- if [ $? -ne 0 ]; then
- fail "ERROR: could not ro-mount original root partition"
- fi
-
-
- # Mount the overlay filesystem
- mkdir /mnt/rw/upper
- mkdir /mnt/rw/work
- mkdir /mnt/newroot
- mount -t overlay -o lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work overlayfs-root /mnt/newroot
- if [ $? -ne 0 ]; then
- fail "ERROR: could not mount overlayFS"
- fi
-
-
- # Create mountpoints inside the new root filesystem-overlay
- mkdir /mnt/newroot/ro
- mkdir /mnt/newroot/rw
-
- # Remove root mount from fstab (this is already a non-permanent modification)
- grep -v "$rootPARTUUID" /mnt/lower/etc/fstab > /mnt/newroot/etc/fstab
- echo "#the original root mount has been removed by overlayRoot.sh" >> /mnt/newroot/etc/fstab
- echo "#this is only a temporary modification, the original fstab" >> /mnt/newroot/etc/fstab
- echo "#stored on the disk can be found in /ro/etc/fstab" >> /mnt/newroot/etc/fstab
-
-
- # Change to the new overlay root
- cd /mnt/newroot
- pivot_root . mnt
- exec chroot . sh -c "$(cat <<END
-
- # Move ro and rw mounts to the new root
- mount --move /mnt/mnt/lower/ /ro
- if [ $? -ne 0 ]; then
- echo "ERROR: could not move ro-root into newroot"
- /bin/bash
- fi
- mount --move /mnt/mnt/rw /rw
- if [ $? -ne 0 ]; then
- echo "ERROR: could not move tempfs rw mount into newroot"
- /bin/bash
- fi
-
- # Unmount unneeded mounts so we can unmout the old readonly root
- umount /mnt/mnt
- umount /mnt/proc
- umount /mnt/dev
- umount /mnt
-
- # Continue with regular init
- exec /sbin/init
- END
- )"
注意,我的脚本和帖子里面的有些不同,我把
- rootDev=`blkid -o list | awk '$3 == "/" {print $1}'`
改成了
不改的话,我这里overlayRoot.sh会运行失败
4. 将脚本拷贝到 /sbin/overlayRoot.sh
- sudo cp overlayRoot.sh /sbin/overlayRoot.sh
- sudo chmod a+x /sbin/overlayRoot.sh
5. 修改 cmdline.txt 文件,在行尾加上 "init=/sbin/overlayRoot.sh",如下是我的 /boot/cmdline.txt 文件的内容
- pi@raspberrypi:~ $ cat /boot/cmdline.txt
- dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=0e82c2e4-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait init=/sbin/overlayRoot.sh
6. 为了保证整个SD卡都是只读,将 /boot 分区也修改为只读, 修改 fstab 文件,把/boot 对应的行改为ro,我的/etc/fstab文件内容如下
- pi@raspberrypi:~ $ cat /etc/fstab
- proc /proc proc defaults 0 0
- PARTUUID=0e82c2e4-01 /boot vfat defaults,ro 0 2
- PARTUUID=0e82c2e4-02 / ext4 defaults,noatime 0 1
7. 到这里 sudo reboot 重启,重启后,文件系统就处于影子系统的保护之下了,所有对于文件系统的改动,在重启后都将恢复原状。 用mount命令可以确认overlayfs的正常工作。
- root-rw on /rw type tmpfs (rw,relatime)
- /dev/mmcblk0p2 on /ro type ext4 (ro,noatime,data=ordered)
- overlayfs-root on / type overlay (rw,relatime,lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work)
- sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
- proc on /proc type proc (rw,relatime)
- devtmpfs on /dev type devtmpfs (rw,nosuid,size=470160k,nr_inodes=117540,mode=755)
- tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
- devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
- tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
- tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
- tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
- cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
- cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
- cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
- cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
- cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
- cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
- cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
- cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
- systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=25,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)
- debugfs on /sys/kernel/debug type debugfs (rw,relatime)
- sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime)
- mqueue on /dev/mqueue type mqueue (rw,relatime)
- configfs on /sys/kernel/config type configfs (rw,relatime)
- /dev/mmcblk0p1 on /boot type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
- tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94952k,mode=700,uid=1000,gid=1000)
原来的根文件系统 /dev/mmcblk0p2 改为挂载在 /ro ,并且是只读;/boot 也挂载为只读;而 / 的 type 变成了overlay。
好了,直接拔电试试看吧。事实上你可以反复尝试不正常关机,都不会有问题。
8. 以后如果想禁用overlayfs,可以修改 cmdline.txt 把 "init=/sbin/overlayRoot.sh" 删掉,重启即可。 当然修改前要先把 /boot remount 成可写状态。
更新系统等操作都需要先禁用overlayfs后再执行。
9. 如果临时想修改原 /boot 以及根文件系统的内容可以如下命令remount
要remount /boot为可写
- pi@raspberrypi:~ $ sudo mount -o remount,rw /boot
原来的根文件系统在overlayfs下是挂载在 /ro 的,要remount的话
- pi@raspberrypi:~ $ sudo mount -o remount,rw /ro
注意,remount文件系统为可写后,要重启和关机需要使用sudo reboot或者sudo halt,不可以强行非正常关机,以免损坏文件系统。
PS. 网上有人报告,如果在不接显示器的情况下(即 headless),如上脚本会运行失败,但是我不接显示器时并没有遇到此问题 。
PS2. 网上有另外一个脚本,链接如下
https://gist.githubusercontent.com/niun/34c945d70753fc9e2cc7/raw/3d60338cd8d8daf740692f426a5a1ec17839d613/root-ro
使用了 update-initramfs 的方式,我在官方系统中可以使用,但是在修改了内核的系统中,运行失败。