树莓派快速入门
前言
实际上这又是一篇类似于 持续更新中的稀奇古怪技巧的文章,但内容主要是使用和测试树莓派时踩过的坑与解决方法。
文章的内容从树莓派系统的安装配置,到程序的编程开发都有涉及。但都不是什么保姆式的指引,毕竟树莓派的资料众多,可以在网上轻松搜索到。
背景
前段时间突发奇想,想要在房间里搭建一些物联网模块,自己动手实现一个包括温湿度监测、异常状态检测、稀奇古怪的音乐播放等功能。
购入一块树莓派Zero 2后,又发现了还有包括STM32/ESP32等等架构可以试玩,还有像CH32这种国产化的同型号替代可以测试,正是一入硬件深似海啊。
至于树莓派,主要是作为原型测试使用,毕竟相较于硬件性价比更高的国产开发板,树莓派的活跃社区和丰富文档可能是仅有的优势之一了。之后只要有条件,肯定会测试采用香橙派、幸狐这种国产板子的方案的。届时再在本站点上进行更新。
不过,也别太认真,这些方案更多是测试和整活为主,事实上要实现的内容已经有相当成熟的产品可以直接购买。就请把这一系列当作是一个大学生消遣娱乐所玩弄的东西好了。
树莓派Raspberry Pi OS防止Wifi自动休眠
问题
由于是嵌入式场景,给树莓派安装的是官方的Raspberry Pi OS Lite(也就是以前的Raspbian,无GUI版),并使用SSH通过局域网访问树莓派。
但在使用的头几天里,总是遇到一个问题:静置一段时间后,树莓派就从网络上消失了。路由器里也找不到树莓派的连接。
一开始以为,是系统存在自动休眠机制,但按网上的说法,无GUI版RPiOS
Lite不存在X
Window,按理说不会自动休眠。同时查看dmesg
,也不存在休眠的记录。
后来查阅资料才知道,树莓派官方Raspberry Pi OS默认启用Wifi网卡自动休眠,以节约能耗。
但对于嵌入式与服务器这种,需要长期开机联网的场景来说,这简直是灾难性的。
解决方法
解决方法当然是关闭Wifi网卡的自动休眠。只需要一句命令:
1 | iw dev wlan0 set power_save off |
首先创建一个systemd服务:
1 | sudo systemctl --full --force edit wifi-powersave-off.service |
填入以下内容:
1 | [Unit] |
保存上述文件,启用该服务,并重启系统,网络就不会随意中断了。
1 | sudo systemctl enable wifi-powersave-off |
设置chroot交叉编译环境
问题
不同的体系结构
树莓派Zero 2采用的是ARM64(aarch64, ARMv8)处理器架构,因此不能简单使用x86平台工具链为树莓派开发应用。
想要针对树莓派开发应用,一种最简单能想到的方法是,直接在树莓派上安装和设置编译工具链,在树莓派上进行编译和生成应用程序。
这种方法最为直接且麻烦最少,但最大的瓶颈在于树莓派极其有限的硬件资源。对于一个四核A53 SoC来说,编译大型应用有点强人所难了。
因此,通常还希望寻找在普通x86电脑上编译ARM应用的方法,以充分利用不同平台上的硬件优势。
为了在x86 PC上编译ARM应用,一种方法是直接在x86系统中引入ARM的编译工具链,就可以生成对应的可执行文件。针对ARM的GNU工具链可以在ARM官网Home / Downloads / Arm GNU Toolchain Downloads找到。
使用上述工具,编写如Hello World等程序就是没有问题的了。这种方法同样具有简单易行的优点。
不同的依赖库体系结构
然而,事实上,我们编写的程序常常还需要链接到各种第三方库,如ncurses, openmp等等。而在我们x86主机上,这些库文件通常是x86版本的,不能被正常链接到ARM的应用。
既然如此,那是否可以将ARM的库文件放到x86系统里供编译器查找链接呢?答案当然是可以的。
例如在Debian/Ubuntu系统中,可以使用多架构功能,为系统添加不同的体系结构,之后就可以直接使用apt
安装指定的库文件。如:
1 | sudo dpkg --add-architecture arm64 |
不同的库环境
然而,上述方法存在一些问题。首先,向系统中添加多体系结构软件包容易造成混乱,毕竟并非人人都是Debian系统运维专家。
更重要的是,通过上述方法获得的ARM库文件,其版本号直接由x86主机系统决定,这可能与树莓派Raspberry Pi OS的版本号不同,从而造成一些奇怪的问题。
这并非是什么稀奇事,因为Linux系统的诸多发行版,库文件之间常常存在兼容性差异。
就我个人经历而言,也遭遇了在x86 Ubuntu系统上编译好的程序,在Raspberry
Pi OS Bookworm中,由于Debian
Bookworm和Ubuntu的GLIBCXX
库版本号有3个小版本的差距,从而导致找不到库文件,程序无法正确运行的情况。
虚拟化的选择
到了这里,鉴于直接使用x86主机系统进行编译时存在的诸多限制和挑战,一个答案呼之欲出:虚拟化。我们希望在虚拟化的环境中完成对树莓派应用的编译。
目前为止,能在Linux上虚拟出ARM系统的,似乎只有QEMU。但QEMU的完全虚拟化ARM虚拟机性能极其感人,不信可以自己试试看。
且慢,我们只是需要一套虚拟的环境,并不需要一个完全隔离的系统。因此,我们可以使用轻量级的chroot
设置一个隔离的系统目录环境。
到此为止,我们就确定了接下来的目标:在x86系统中设置针对树莓派开发的ARM体系结构chroot环境。
本文还使用了在Debian系统下的chroot
管理小工具schroot
。要是不喜欢这玩意,也没关系,它最大的作用就是简化chroot
之前的挂载步骤和设置资源隔离(但实际上在我们当前场景中安全并不是很重要)。只需要自行在chroot
前设置好/dev
,
/sys
, /proc
等挂载点,两者效果完全相同。
设置x86主机系统
在开始前首先需要设置一下x86主机上的主系统。
笔者使用的x86主机系统是Debian 12,其他发行版可能需要按需修改一下。经测试,加入一些简单的额外步骤后也可以用于WSL内的Debian/Ubuntu系统。
安装schroot
首先需要安装schroot
(会一同安装build-essential
):
1 | sudo apt install sbuild |
安装debootstrap
如果希望创建一个Debian的chroot环境,那么肯定需要用到debootstrap
。
1 | sudo apt install debootstrap |
如果需要使用基于Ubuntu的chroot环境,可以使用Ubuntu Base。其他发行版需要自行寻找方法。
(仅WSL)开启Systemd
接下来使用的binfmt_misc模块需要systemd
的支持,因此需要提前为WSL子系统开启systemd
。
首先编辑WSL子系统内的/etc/wsl.conf
,填入以下内容:
1 | [boot] |
然后在子系统内安装systemd
组件:
1 | sudo apt install systemd systemd-sysv |
完成后退出WSL,在Windows命令行中执行wsl --shutdown
以彻底关闭WSL系统,再重新打开WSL即可。
配置ARM二进制文件转译支持
首先安装:
1 | sudo apt install qemu-user-static binfmt-support |
两个软件包中,qemu-user-static
可以在允许用户直接执行另一个体系结构的可执行文件。
而binfmt-support
会为系统添加binfmt_misc模块,从而在直接调用ARM体系结构ELF二进制文件时,自动判断所属体系结构,并自动调用qemu-user-static
翻译器。查看该模块的运行状态与已注册的文件类型可以前往/proc/sys/fs/binfmt_misc/
查看。
完成安装后重启系统或WSL子系统。到此为止,x86系统就可以(高性能损失地)翻译执行ARM应用了。以Debian软件源中的hello
示例软件包为例:
1 | # Check package version and filename if failed or 404 |
安装libc以支持动态链接
为了支持ARM虚拟环境中的动态链接,需要在主机上安装ARM体系结构的libc
库。
1 | sudo dpkg --add-architecture arm64 |
至此,主系统上的前期配置就已经全部完成。下面将开始设置chroot
环境。
创建ARM架构chroot环境
在接下来的文段中,会把chroot
出来的虚拟化ARM环境,称为虚拟环境。
初始化虚拟环境文件目录(rootfs)
以创建Debian Bookworm ARM64的虚拟环境为例,需要运行以下命令:
1 | sudo debootstrap --arch=arm64 --foreign bookworm /srv/chroot/bookworm-arm64-build https://deb.debian.org/debian/ |
着重解释:
--foreign
参数会跳过debootstrap
的第二阶段,即apt
执行软件包配置代码的阶段。由于体系结构的不同,在当前阶段直接执行配置代码会出错,因此需要推迟到chroot
进入系统之后执行(后文介绍步骤)。本参数不能省略!/srv/chroot/bookworm-arm64-build
是自行指定的chroot
环境存放目录,爱放哪放哪。不过/srv/chroot
是schroot
推荐的目录。https://deb.debian.org/debian/
是指定的软件源,国内可以使用如http://mirrors.163.com/debian/
等镜像站替代。
执行上述命令后,/srv/chroot/bookworm-arm64-build
就是新创建的ARM架构系统根目录(rootfs,
/
)。
接着还需要复制QEMU翻译器到chroot
目录下:
1 | sudo cp /usr/bin/qemu-aarch64-static /srv/chroot/bookworm-arm64-build/usr/bin |
挂载文件系统
进入虚拟环境前还需要给它挂载一些有用的虚拟系统。以下命令根据chroot - Debian Wiki编写:
1 | for f in dev dev/pts sys proc run ; do sudo mount --bind /$f /srv/chroot/bookworm-arm64-build/$f ; done |
(另一种方案)不过在chroot - ArchWiki上,介绍的挂载点略有差异,但似乎都能运行,暂时没遇到问题:
1 | cd /srv/chroot/bookworm-arm64-build/ |
完成Debootstrap第二阶段
如果使用上上节中的debootstrap
命令创建Debian系统,那么现在还需要执行debootstrap
的第二阶段,以完成虚拟环境的配置。
1 | sudo chroot /srv/chroot/bookworm-arm64-build /debootstrap/debootstrap --second-stage |
经过漫长的等待后,配置过程总算结束了。
使用schroot管理chroot环境
schroot配置chroot环境
实际上,sbuild
等工具可以帮助我们自动创建虚拟环境并自动配置。但是!相较于完全透明的完整工具,我还是更享受将这些过程掌握在手里的感觉。
schroot
可以帮忙管理chroot
环境,简化激活chroot
环境所需要的命令。
如果希望使用schroot
而非chroot
,只需要编写配置文件即可。
在/etc/schroot/chroot.d/
下创建配置文件,如/etc/schroot/chroot.d/bookworm-arm64-build.conf
:
1 | [bookworm-arm64-build] |
这样就足以直接开始使用:
1 | # List all chroot(s) |
上述配置是最简单的配置,可以附加其他很多选项,参见:schroot(1)和schroot.conf(5)。
例如,希望使用一个更短的别名:
1 | [bookworm-arm64-build] |
之后就可以直接使用以下命令访问容器:
1 | schroot -c d12arm64 -d / |
schroot配置文件目录挂载
有时我们希望允许虚拟环境访问指定的某个目录,但在chroot
环境下,软连接是无法访问的(超过了访问路径范围),而硬链接又只能链接单个文件。
因此,通常的做法是把主机上希望允许访问的目录mount --bind
到虚拟环境中。例如:
1 | sudo mount --bind /host/example /srv/chroot/bookworm-arm64-build/chroot/some/example |
这种方法,在使用原始的chroot
,或是当上一节schroot
配置文件第一行指定环境类型为type=plain
时是可行的。
需要首先介绍一下schroot
的大致工作机制。schroot
为了允许指定的非root用户访问虚拟环境,在用户要求访问虚拟环境时,会先将虚拟环境的rootfs,也就是/srv/chroot/bookworm-arm64-build/
,使用mount
挂载到/run/schroot/mount/
下,再chroot
到这一映射后的目录下,允许用户访问。
当配置文件type=plain
时,schroot
在mount
rootfs时使用的参数是--rbind
,此时原先挂载到rootfs下的所有挂载点都能被正常跟随处理。
但在type=directory
时,由于schroot
改而使用--bind
参数(有什么差别?参见mount(8) -
Linux manual
page)挂载rootfs,此时原先挂载到虚拟环境中的挂载点将不会被正常跟随,在打开的虚拟环境中,所见目标目录为空。
解决这一问题的办法一是,直接将配置文件中的type=directory
改回type=plain
:
1 | ```diff |
然而plain
类型的schroot
环境需要手动挂载dev
/sys
/proc
等路径,和chroot
没有大差异。在不需要考虑安全等因素的场景中,选择使用schroot
正是为了免去这些麻烦而准备的,现在直接改回去大有一夜回到解放前的感觉。
因此尝试以下第二种方法,也就是schroot.conf(5)所提到的,通过指定挂载虚拟环境时的fstab
文件配置挂载点。该文件格式与/etc/fstab
格式相同。
下面以挂载/host/example
到虚拟环境中的/chroot/example
为例。注意这里的/chroot/example
是虚拟环境中的路径,不是相对主机系统而言的。
首先在/etc/schroot/
下创建一个fstab.d
文件夹,再创建一个独立的fstab
文件(实际上后缀名不作强制要求)。
1 | sudo mkdir -p /etc/schroot/fstab.d |
填入以下内容,其中有一部分内容可以直接照搬/etc/schroot/default/fstab
:
1 | # Note that the mount point will be prefixed by the chroot path |
再修改/etc/schroot/chroot.d/bookworm-arm64-build.conf
中的内容,添加一行setup.fstab
,指向刚刚创建的fstab
文件,该路径是相对于/etc/schroot/
而言的。
1 | [bookworm-arm64-build] |
保存后,还需要在虚拟环境文件系统中创建挂载点:
1 | sudo mkdir -p /srv/chroot/bookworm-arm64-build/chroot/example |
重新进入schroot
,就可以在/chroot/example
下见到主机/host/example
中的文件了。