计算机技术学习札记

树莓派折腾笔记


2021 年 2 月 1 日

(本文将持续更新)

树莓派是一种十分适合于开发的小型单板计算机。我所拥有的是树莓派的第四代,即 Raspberry Pi 4B 的 2GB 运存版本,平均售价在 250 元左右。它拥有一个 Broadcom 的 4 核心 ARM 处理器,2GB 运行内存,40 针 GPIO 插座,4 个 USB 端口(其中 2 个是 USB 3.0),2 个 microHDMI 输出口,1 个 GbE 网口和支持 802.11ac 的 WiFi 及 Bluetooth 5.0 的网络芯片。整机使用 5.1V 3A 即 15.3W 的电源。

目前,我手中的这块树莓派的使用情况为:

  • 寝室内,我的私人 Wi-Fi 热点,能够实现自动登录校园网认证;
  • 校园网内部,我(和另一个同学)的私人 NAS 与下载机。

在这篇持续更新的文章中,我将记录自己折腾这块树莓派的一些过程。

为树莓派选择一个适合她的系统 #

除非你想给自己找麻烦或者自己喜欢折腾,否则请不要使用除了 Raspberry Pi OS(原 Raspbian 系统)之外的任何系统。

除了原生的 Raspberry Pi OS(基于 Debian)外,我给自己的树莓派装过 Ubuntu Server、Ubuntu Mate、CentOS 等 Linux 发行版,但最终还是换回了 Raspberry Pi OS。最根本的原因只有一个:兼容性。Raspberry Pi OS 的软件源中的软件很多是针对树莓派特制的,能够稳定且顺畅地在树莓派上运行;网上能找到的树莓派教程很多都是基于 Raspberry Pi OS 的;Raspberry Pi OS 自带的 raspi-config 工具能够很轻松地配置 Pi 的各项配置……而使用其它系统,或硬件不完全兼容,或找不到需要的软件,或配置复杂且困难……总而言之,使用 Pi 原装的 Raspberry Pi OS,一定是所有选项中的极优解。

Raspberry Pi OS 更换软件源 #

以哈工大开源镜像站为例。更换 Raspberry Pi OS 的软件源需要更改两个文件,分别是 /etc/apt/sources.list/etc/apt/sources.list.d/raspi.list。使用合适的编辑器(如 GNU Nano)编辑这两个文件。

以 Debian 10(代号 buster)为例:

# /etc/apt/sources.list
deb https://mirrors.hit.edu.cn/raspbian/raspbian/ buster main non-free contrib rpi

# /etc/apt/sources.list.d/raspi.list
deb https://mirrors.hit.edu.cn/raspberrypi/ buster main ui

然后以 Root 执行 apt update 即可。

配置树莓派为无线路由器 #

本部分参考自 https://chenjiehua.me/linux/raspberry-pi-wireless-access-point.html

首先安装两个软件包 hostapddnsmasq

sudo apt install hostapd dnsmasq

为接口 wlan0 分配一个固定 IP 地址,例如 192.168.114.1。这意味着连接到树莓派的设备都在 192.168.114.0 网段之下。编辑文件 /etc/dhcpcd.conf,在文件尾部加上这一段:

interface wlan0
    static ip_address=192.168.114.1/24
    nohook wpa_supplicant

配置 dnsmasq,为连接的设备启用 DHCP 服务器。先执行下面的命令备份一下 dnsmasq.conf 文件:

sudo mv /etc/dnsmasq.conf{,.bak}

然后,编辑 /etc/dnsmasq.conf,在这个文件中写入:

interface=wlan0
dhcp-range=192.168.114.2,192.168.114.25,255.255.255.0,24h

其中的 192.168.114.2192.168.114.25 分别是 DHCP 地址池的起点和终点,255.255.255.0 是子网掩码,而 24h 是地址租期。

接着,配置 hostapd 文件。我所创建的是一个 802.11ac 的 5GHz Wi-Fi 热点。编辑 /etc/hostapd/hostapd.conf 文件,写入如下内容:

interface=wlan0
driver=nl80211

hw_mode=a
ieee80211n=1
ieee80211ac=1
ieee80211d=1
ieee80211h=1
require_ht=1
require_vht=1
wmm_enabled=1
country_code=US

vht_oper_chwidth=1
channel=149
vht_oper_centr_freq_seg0_idx=155
ht_capab=[HT40-][HT40+][SHORT-GI-40][DSSS_CCK-40]

wpa=2
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP

ssid=<你想要的 SSID>
wpa_passphrase=<你想要的密码>

关于这个文件的具体配置,可以参考 https://blog.csdn.net/u013316124/article/details/82781339

然后,编辑 /etc/default/hostapd,找到带有 DAEMON_CONF 的那一行,修改为

DAEMON_CONF=\"/etc/hostapd/hostapd.conf\"

依次启动上面配置好的服务:

sudo service dhcpcd restart
sudo services dnsmasq restart
sudo systemctl unmask hostapd
sudo systemctl enable hostapd
sudo systemctl start hostapd

配置系统的路由转发。编辑 /etc/sysctl.conf,去掉 net.ipv4.ip_forward=1 这一行前面的注释符号。然后依次执行:

sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo sh -c \"iptables-save > /etc/iptables.ipv4.nat\"

编辑 /etc/rc.local,配置启动时自动加载路由表。

# 在 exit 0 之前加上:
iptables-restore < /etc/iptables.ipv4.nat

重新启动树莓派。

使用 OverlayFS 保护树莓派上的 SD 卡 #

一直以来,像树莓派这样的单板微型计算机都是使用 microSD 卡(TF 卡)作为“硬盘”使用。然而,SD 卡,尤其是质量一般的 SD 卡,采用的存储介质往往能接受的擦写次数较低,经受不住 ext4 这种日志式文件系统和 Linux 这种运行过程中产生 log 量非常庞大的操作系统的“折磨”。在使用了两个月之后,我的 Pi 开始出现问题:

前几天,在 Pi 上使用 apt install 安装软件时,意外地出现了大量类似于 dpkg: warning: files list file for package \'xxxx\' missing 这样的错误。一些系统服务当天并没有正常运行(我的树莓派每天会重启),更匪夷所思的是,树莓派的主机名变成了一串意义不明的字符串 iersdes,经过排查,我初步认定这是 SD 卡出现了坏块导致的一系列问题。

但关于自己的主机名从 hansdepi 变成了 iersdes 这件事,我并没有头绪,只是猜测这是由于存储主机名的文件有若干块出错或者丢失所造成的。

今天中午,在经历一番折腾之后,我的树莓派重新恢复了各项服务上线。为了让由于 SD 卡出现“磨损”造成系统宕机的“悲剧”不再发生,我开始寻找一种更好地保护树莓派上的 SD 卡的方法。使用 OverlayFS 保护 SD 卡,可能是这里面比较容易实现的一种。

什么是 OverlayFS #

overlay ['oʊvər.leɪ] n.涂层 v.覆盖

OverlayFS 是一种“堆叠文件系统”,它依赖并建立在其它的文件系统之上,不直接参与磁盘空间结构的划分,仅将原来文件系统中不同目录和文件进行“合并”。关于 OverlayFS 的详细介绍,可以参见这两篇博客:

本文中,我们不对 OverlayFS 做什么细致的探讨,只需要知道我们使用了 OverlayFS 即可。我们将通过 OverlayFS,将 SD 卡挂载到一个只读的目录上,而将 / 挂载为一个由只读的 SD 卡和内存中的 tmpfs 堆叠而成的 OverlayFS。这样实现的效果就是,系统对 SD 卡的所有写入都将在重启之后消失——有如安装了还原卡或者“冰点还原”软件。

禁用交换分区 #

依次执行:

$ sudo dphys-swapfile swapoff
$ sudo dphys-swapfile uninstall
$ sudo update-rc.d dphys-swapfile remove

重新启动树莓派,使用 free -h 查看 Swap 一栏是否显示均为 0。

配置 overlayRoot 脚本 #

我们使用这里的脚本来实现自动配置 OverlayFS。

$ sudo nano /etc/overlayRoot.sh

复制粘贴这些代码:

#!/bin/sh
#  Read-only Root-FS for Raspian using overlayfs
#  Version 1.1
#
#  Version History:
#  1.0: initial release
#  1.1: adopted new fstab style with PARTUUID. the script will now look for a /dev/xyz definiton first 
#       (old raspbian), if that is not found, it will look for a partition with LABEL=rootfs, if that
#       is not found it look for a PARTUUID string in fstab for / and convert that to a device name
#       using the blkid command. 
#
#  Created 2017 by Pascal Suter @ DALCO AG, Switzerland to work on Raspian as custom init script
#  (raspbian does not use an initramfs on boot)
#
#  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, 2018-10-09
#
#  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, make it executable 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 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 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
mkdir /mnt/lower
mkdir /mnt/rw
mount -t tmpfs root-rw /mnt/rw
if [ $? -ne 0 ]; then
    fail \"ERROR: could not create tempfs for upper filesystem\"
fi
mkdir /mnt/rw/upper
mkdir /mnt/rw/work
mkdir /mnt/newroot
# mount root filesystem readonly 
rootDev=`awk \'$2 == \"/\" {print $1}\' /etc/fstab`
rootMountOpt=`awk \'$2 == \"/\" {print $4}\' /etc/fstab`
rootFsType=`awk \'$2 == \"/\" {print $3}\' /etc/fstab`
echo \"check if we can locate the root device based on fstab\"
blkid $rootDev
if [ $? -gt 0 ]; then
    echo \"no success, try if a filesystem with label \'rootfs\' is avaialble\"
    rootDevFstab=$rootDev
    rootDev=`blkid -L \"rootfs\"`
    if [ $? -gt 0 ]; then
        echo \"no luck either, try to further parse fstab\'s root device definition\"
        echo \"try if fstab contains a PARTUUID definition\"
        echo \"$rootDevFstab\" | grep \'PARTUUID=\\(.*\\)-\\([0-9]\\{2\\}\\)\'
        if [ $? -gt 0 ]; then 
	    fail \"could not find a root filesystem device in fstab. Make sure that fstab contains a device definition or a PARTUUID entry for / or that the root filesystem has a label \'rootfs\' assigned to it\"
        fi
        device=\"\"
        partition=\"\"
        eval `echo \"$rootDevFstab\" | sed -e \'s/PARTUUID=\\(.*\\)-\\([0-9]\\{2\\}\\)/device=\\1;partition=\\2/\'`
        rootDev=`blkid -t \"PTUUID=$device\" | awk -F : \'{print $1}\'`p$(($partition))
        blkid $rootDev
        if [ $? -gt 0 ]; then
	    fail \"The PARTUUID entry in fstab could not be converted into a valid device name. Make sure that fstab contains a device definition or a PARTUUID entry for / or that the root filesystem has a label \'rootfs\' assigned to it\"
        fi
    fi
fi
mount -t ${rootFsType} -o ${rootMountOpt},ro ${rootDev} /mnt/lower
if [ $? -ne 0 ]; then
    fail \"ERROR: could not ro-mount original root partition\"
fi
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 \"$rootDev\" /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
)\"

按 C-X, Y, Return 保存退出。然后,执行 sudo chmod a+x /sbin/overlayRoot.sh 来赋予这个脚本可执行权限。

接着,我们需要将这个脚本配置到树莓派的启动过程中。执行 sudo nano /boot/cmdline.txt,在那一行的行尾加上:

init=/sbin/overlayRoot.sh

按 C-X, Y, Return 保存退出。这时,我们将系统重启,接下来的系统就处在“还原卡激活”的状态了。可以尝试对系统进行一些改动,例如安装软件、新建一些文件等。重新启动,你会发现这些改动都消失了。

注意:被保护的只有 /dev/mmcblk0p2,也就是 rootfs,/boot 分区以及树莓派上所接驳的 U 盘、移动硬盘等都不会被保护。

临时解除保护 #

如果想要安装软件,需要临时解除保护,请先执行 sudo mount -o remount,rw /ro 以重新挂载 /ro 目录(这个目录是“真正的”SD 卡根分区)。然后,使用 sudo chroot /ro 命令,进入一个以 /ro 为根的 root 环境。在这个环境里,可以安装软件或者修改配置。结束之后,使用 exit 退出这个环境。直到重启之前,都可以再用 sudo chroot /ro 进入可写环境。

永久解除保护 #

直接编辑 /boot/cmdline.txt,删除行尾的那句 init=/sbin/overlayRoot.sh,重启即可。