使用 Buildroot 为 Pi Zero W 构建了一个极简的 Linux 内核
我遇到的许多现代设备通常都搭载了定制的操作系统,运行在其中。当我尝试配置路由器时,了解到它自带操作系统,我才开始意识到这一点。
本项目的目标是使用 Buildroot 为树莓派 Zero W 开发一个定制的嵌入式 Linux 发行版。系统设计仅包含应用程序所需的基本组件,例如 Wi-Fi 连接、SSH 和 SCP 访问、I2C 支持以及 Python。系统还集成了 SSD1306 OLED 显示屏,用于构建菜单式仪表板,可显示系统信息和网络状态。
通过精心选择所需软件包并优化启动流程,最终系统在启动后内存占用约为25 MB RAM,并在约6.77秒内完成应用程序启动。本项目展示了如何使用Buildroot为嵌入式应用创建一个轻量、高效且可定制的Linux平台。
Buildroot
Buildroot 是一个开源的构建自动化框架,可简化自定义嵌入式 Linux 系统的开发。它通过自动完成下载、配置、编译和集成所有所需软件组件的过程,为目标设备生成完整的 Linux 镜像,提供了一种简便高效的解决方案。
本项目使用 Buildroot 为 Raspberry Pi Zero W 创建了一个自定义的 Linux 发行版。在一台配备 8 GB 内存和 64 GB 存储空间的 Raspberry Pi 5 上配置并构建了 Buildroot 环境,该设备作为主机开发平台。通过 Buildroot,生成了一个定制化的 Linux 镜像,其中仅包含应用程序所需的功能组件,包括 Python 支持、Wi-Fi 连接、SSH 访问、文件传输的 SCP、OLED 显示支持以及一个基于菜单的定制仪表板应用。最终生成的镜像提供了一个轻量级的嵌入式 Linux 环境,优化了快速启动和低资源消耗,同时保留了项目所需的所有功能。
按照以下步骤在 Raspberry Pi 5 上创建 Buildroot 配置:
1. 获取 Buildroot
2. 加载 Raspberry Pi Zero W 配置:此操作用于设置正确的 CPU(ARM1176)、内核配置和引导加载程序设置
3. 自定义配置
Buildroot 配置(make menuconfig)
Python 支持:已安装的包包括:
•python3-pip
•python3-pillow
•python3-setuptools
已启用 Python 3,以支持 boot_manager.py、OLED 显示应用、Wi-Fi 初始化脚本以及菜单驱动界面。其他软件包通过 pip 安装(具体说明见下文)。
已启用 OpenSSH 服务器支持,以允许:
•远程登录
•通过SSH控制菜单
•远程调试与监控
无线网络:Wi-Fi 支持通过以下方式配置:
•WiFi网络安全存取请求者程序
•无线固件包
这使得树莓派Zero W在启动时能够自动连接无线网络。
OLED显示库
已包含用于SSD1306 OLED显示屏的Python包,可实现以下功能:
•启动状态消息
•IP地址显示
•菜单渲染
•Linux 内核配置(make linux-menuconfig)
•I2C 支持
已启用 I2C 支持,以便与 SSD1306 OLED 显示屏进行通信。
启用的关键组件:
•I2C子系统
•BCM2835 I2C 控制器驱动程序
这使得可以通过 /dev/i2c-* 访问 OLED 显示屏。
无线局域网支持
已启用适用于树莓派Zero W无线芯片组的内核支持。
相关驱动程序:
•cfg80211
•brcmutil
•brcmfmac
最初这些是作为内核模块构建的,并由引导管理器在启动时加载。
设备树支持已启用,以实现以下功能:
•树莓派硬件检测
•外设配置
•叠加层支持
这是为了正确初始化Wi-Fi、I2C和GPIO外设所必需的。
树莓派固件
已包含Linux固件支持,以提供:
•Broadcom Wi-Fi 固件
•Pi Zero W 无线芯片组所需的固件文件
如果没有这些固件文件,brcmfmac 驱动程序将无法初始化无线接口。
按照配置命令的顺序进行操作。
1. 设置 buildroot 配置
2. 保存配置
3. 配置 Linux 内核配置
4. 构建
在树莓派5上编译耗时将近2到3小时。编译完成后,镜像文件将位于 output/images/sdcard.img。
在 Pi Zero W 上刷写 Linux 镜像
按照以下步骤在 Mac OS 上将镜像刷入 SD 卡。我已通过 scp 将镜像从我的 Pi 5 复制到 Mac 上。
1. 识别SD卡设备
连接到系统的存储设备通过以下方式列出:
此命令用于识别与microSD卡对应的设备节点,假设此处为/dev/disk2。
2. 卸载SD卡
在写入图像之前,已卸载SD卡以防止文件系统损坏:
3. 编写 Buildroot 镜像
使用 dd 工具将生成的 Buildroot 镜像刷入 SD 卡:
其中:
•if=sdcard.img 指定输入的图像文件。
•of=/dev/rdisk2 指定了原始SD卡设备。
•bs=1m 使用 1 MB 的块大小以提高写入性能。
•使用原始设备(rdisk2)代替缓冲设备(disk2),以实现更快的数据传输速度。
4. 同步待写入数据
图像写入完成后,所有缓冲数据将通过以下方式刷新到SD卡:
这确保了在移除卡片之前,图像已完全写入。
5. 安全弹出SD卡
已安全弹出SD卡:
启动 Pi Zero
由于自定义的 Buildroot 镜像是一个没有图形界面的最小化 Linux 系统,因此无法通过显示器和桌面环境与树莓派 Zero W 交互。相反,在开发和调试期间,访问系统的主方法是通过串行控制台。
树莓派Zero W提供了一个UART(通用异步收发器)接口,可在启动过程中直接与操作系统通信。该接口特别适用于查看内核消息、监控启动进度以及访问命令行 shell。
启用启动分区
根文件系统目前没有显示已挂载的 /boot 分区。/boot 分区是一个小型的 FAT32 分区,其中包含在 Linux 内核接管之前启动系统所需的所有内容。对于此使用场景,我需要修改 config.txt 文件以启用 I2C 外设(具体说明见下文)。
验证分区
通常,p1 是 FAT 启动分区,p2 是 Linux 根文件系统。
使用以下方法手动挂载引导分区(p2):
然后检查启动目录,
设置WiFi模块
Pi Zero W 的 Wi-Fi 栈(即 brcmfmac)作为 Linux 内核模块提供,因此需要手动加载才能访问 Wi-Fi。
验证后,wlan0 应该会弹出提示,表明 WiFi 驱动已成功初始化,且 Linux 已检测到板载 Wi-Fi 芯片。
接下来,应将 wlan0 设置为 active 以启用 WiFi。默认情况下,其状态为 DOWN。
WiFi开启后,即可用于扫描并连接新的网络。我通过创建一个wpa_supplicant配置文件手动设置了WiFi。在目录/etc/中创建一个名为wpa_supplicant.conf的文件。该文件必须包含以下内容:
使用以下方式连接:
使用以下方式检查状态:
请注意,这不会启用互联网访问,因为其IP地址未被分配。要获取其IP地址,请启用DHCP以设置IP地址:
使用简单的 ping 命令测试网络(例如:ping 8.8.8.8)。
设置系统日期和时间
最小化的 Buildroot Linux 镜像不包含桌面 Linux 发行版中常见的许多后台服务。因此,系统在连接到网络后不会自动与互联网时间服务器同步时钟。
当 Raspberry Pi Zero W 首次启动时,系统时钟通常会被初始化为默认的日期和时间。由于该板子没有带电池备份的实时时钟(RTC)模块,因此断电后无法保留正确的时间。因此,在执行依赖精确时间戳的任务(如日志记录、文件管理或安全网络通信)之前,必须手动设置日期和时间。
当前日期和时间可通过以下命令进行配置:
自动时间同步
一旦无线网络配置完成且树莓派成功连接到互联网,就可以自动设置日期和时间。在启动时运行一个Python脚本,可连接网络时间协议(NTP)服务器并获取当前时间。
在此项目中,时间同步功能已集成到自定义启动管理器中。在建立Wi-Fi连接并获取IP地址后,脚本会与NTP服务器通信,并自动更新系统时钟。
设置 I2C 驱动程序
标准的buildroot配置最初并未加载任何I2C内核驱动。检查设备树(ls /proc/device-tree/soc/)后,发现i2c接口最初被禁用。
要启用 I2C 外设,首先在 Raspberry Pi 的固件配置中启用 I2C。将以下内容添加到 /boot/config.txt 文件中。
它告诉树莓派的固件开启专用的I2C引脚,并配置设备树以识别I2C外设。
完成文件修改后,重启设备并确认已连接I2C外设。
启动脚本
自定义的启动脚本是基于 Buildroot 的 Linux 系统的主要启动应用程序,通过 S99boot_manager 初始化脚本在系统启动时自动执行。其目的是初始化嵌入式平台,并为系统运行做好准备。启动过程中,该脚本首先初始化 SSD1306 OLED 显示屏,并显示启动状态信息,向用户提供视觉反馈。随后加载所需的 Wi-Fi 内核模块:cfg80211、brcmutil 和 brcmfmac,启动 wpa_supplicant 以连接已配置的无线网络,并通过 DHCP 获取 IP 地址。一旦网络连接建立,脚本将系统时间与 NTP 服务器同步,以确保日期和时间信息的准确性。在此整个过程中,OLED 显示屏会实时更新连接状态和网络信息,包括分配到的 IP 地址。
执行以下操作以运行启动脚本:1. 创建一个 init 脚本:/etc/init.d/S99bootmanager。该启动脚本必须包含 Python 启动脚本:
2. 将启动脚本编译为可执行文件
这使得 Busybox 在下次启动时能够自动执行它。
缩短启动时间
在对基于 Buildroot 的 Linux 系统进行初始测试时,启动过程在网络初始化阶段出现了延迟,并显示如下信息:
延迟的原因是 Buildroot 的网络服务(S40network)通过 /etc/network/interfaces 文件在启动时被配置为自动启用 wlan0 接口。然而,Wi-Fi 驱动程序(brcmfmac)及其相关的网络组件是由自定义的 boot_manager.py 脚本稍后加载的(因为这些组件作为模块而非内核驱动程序提供)。因此,网络服务需要等待 wlan0 接口可用,从而延长了整体启动时间。
以下的 /etc/network/interfaces 配置导致系统等待无线接口:
由于Wi-Fi初始化和网络配置已由自定义的boot_manager.py脚本处理,因此自动网络配置变得多余。通过注释掉这些行,系统启动时的引导过程不再需要等待wlan0。
因此,延迟被消除,整体启动时间得以缩短,并且在完成必要的系统初始化步骤后,启动管理器应用程序可立即启动。
释放更多内存
在Raspberry Pi Zero W上运行基于Buildroot的最小Linux发行版时,我原本以为该板的512MB内存中大部分都能被Linux系统使用。然而,初步测试显示,操作系统仅能看到约364MB的内存。这促使我调查了Raspberry Pi固件如何分配内存,以及GPU内存划分如何影响可用的系统RAM。
启动新构建的 Buildroot 镜像后,系统报告:
内核启动日志显示:
这表明 Linux 只收到了:
从固件中释放物理内存。
由于树莓派Zero W配备了512MB内存,Linux系统启动前大约有128MB内存似乎被占用了。由于树莓派在ARM CPU和VideoCore GPU之间共享系统内存,我的目标是减少分配给GPU的内存,因为我没有使用桌面版Linux系统,也没有任何与摄像头相关的应用程序。
解决方案
Buildroot生成的config.txt最初包含:gpu_mem_512=100 MB。将其改为16 MB后,Linux仍然只识别到约384 MB的内存。
为验证固件是否正确遵循了GPU内存设置,测试了多个数值。
gpu内存=256
结果:
这确认了固件正在读取并应用配置。
gpu_mem=64
结果:
gpu_mem=32
结果:
gpu_mem=16
结果:
意外的是,将GPU内存降低到32 MB以下时,可用的RAM会急剧下降。这表明固件强制执行了最低支持的GPU内存分配,并在使用不支持的值时切换为不同的内存分配方式。
通过调整 gpu_mem 设置,成功将 Linux 可见内存提升至约 459 MB。此举在保持系统稳定运行的同时,回收了近 100 MB 的 RAM。
速度对比
在本项目中,我将比较构建根文件系统(buildroot)的最小Linux版本与标准的32位树莓派OS Lite,因为后者并非桌面版。我会对比它们的启动时间、内存使用情况以及平均CPU负载。我将把树莓派OS Lite安装在一块32GB的SanDisk Ultra Class-10 SD卡上(速度更快),而构建根版本则安装在一块16GB的Class-4 SD卡上。
1. 启动时间对比
本实验的目的是比较基于自定义Buildroot的Linux发行版与运行在Raspberry Pi Zero W上的Raspberry Pi OS Lite之间的启动性能。
评估了两个指标:
•内核启动时间
•启动管理器启动时间
内核启动时间表示Linux内核初始化并启动init进程所需的时间。启动管理器开始时间表示从开机到用户应用程序(boot_manager.py)开始执行所经过的时间。
在 Buildroot Linux 中,引导管理器被配置为一个 SysV 初始化脚本:
并使用以下方式获取内核启动时间:
启动管理器的启动时间是通过以下方式测量的:
在 Raspberry Pi OS Lite 上,启动管理器被配置为一个 systemd 服务:
内核启动时间通过以下方式获取:
引导程序启动时间是使用相同的Python脚本测量的。
结果
•Buildroot Linux 内核启动时间:4.60 秒
•树莓派 Lite 操作系统 启动时间:11.967 秒
•Buildroot Linux 启动管理器启动时间:6.77 秒
•树莓派 Lite 操作系统启动管理器启动时间:113.07 秒
由于架构精简,Buildroot镜像的启动性能显著更快。Buildroot仅包含应用程序所需的服务和软件包,从而减少了初始化的工作量。
树莓派 Lite 操作系统镜像包含许多附加服务,例如:
•systemd
•网络管理器
•cloud-init
•D-Bus 服务
•主机名和网络配置服务
使用 systemd-analyze 进行分析后显示:
和
表明大部分延迟源于用户空间服务的初始化,而非内核启动。
Buildroot 系统在内核初始化后约 2.17 秒即可执行应用程序,而 Raspberry Pi OS Lite 则需要超过 100 秒才能启动应用。综上所述,Buildroot 能实现更快的启动速度,因为它仅包含应用程序所需的服务;而 Raspberry Pi OS Lite 则初始化了一个更大的用户空间环境,包括 systemd、NetworkManager 和 cloud-init。
2. 内存使用情况
系统完成启动并建立网络连接后,评估了内存使用情况。
基于 Buildroot 的系统在启动后仅占用 25 MB 内存,而 Raspberry Pi OS Lite 占用 107 MB,内存使用量减少了约 76.6%。
此外,Buildroot 为应用程序执行预留了 426 MB 的内存,而 Raspberry Pi OS Lite 只有 319 MB。
内存占用的减少主要得益于极简的Buildroot配置,该配置排除了NetworkManager、cloud-init、D-Bus以及其他在Raspberry Pi OS Lite中常见的用户空间组件。
结果表明,Buildroot 提供了显著更轻的运行时环境,更适合资源受限的嵌入式系统,这些系统需要较低的内存消耗。
3. CPU负载对比
在启动两个操作系统并使其保持空闲后,使用 Linux 的 uptime 命令评估系统负载。
在 Pi OS lite 上:
在 Buildroot 操作系统上:
启动后的最初几分钟内,Raspberry Pi OS Lite 由于运行了 NetworkManager、蓝牙以及与 systemd 相关的后台服务,导致系统负载较高。相比之下,Buildroot 镜像仅运行应用程序所需的服务,因此整体系统活动较低。
大约10分钟后,两个系统均进入大致空闲状态。然而,在Buildroot上,5分钟和15分钟的负载平均值仍较低,表明后台处理减少,运行环境更轻。这些结果表明,自定义的Buildroot镜像在提供嵌入式应用程序所需功能的同时,保持了更低的系统开销。
4. 根文件系统大小比较
在 Pi OS lite 上:
在 Buildroot Linux 上:
Buildroot镜像仅占用Raspberry Pi OS Lite所需存储空间的一小部分,因为它只包含应用程序所需的软件包。
5. 运行服务
在 Pi OS Lite 上:
在 Buildroot Linux 上:
Buildroot 启动时服务集显著缩小,从而降低了内存使用量和启动时间。
应用:自定义仪表盘
为了为自定义的 Buildroot Linux 系统提供交互式用户界面,开发并显示了一个基于菜单的仪表板应用程序,该应用通过 I2C 接口连接到 Raspberry Pi Zero W 的 OLED 显示屏上。由于 Buildroot 镜像设计为轻量级嵌入式 Linux 系统,不包含图形桌面环境,因此基于 OLED 的仪表板为用户提供了简单高效的设备交互方式。
功能
仪表板采用分层菜单结构,使用户能够轻松切换至不同的系统功能和实用工具。通过SSH终端会话远程控制菜单导航,无需专用键盘、鼠标或显示器即可实现交互操作。
该应用程序集成了多项实用功能,包括:
系统信息
•显示设备IP地址
•当前日期和时间
•网络状态信息
天气信息
•获取并显示当前天气状况
•OLED显示屏上显示的天气图标
•温度和环境信息
Wi-Fi 连接
•网络连接状态显示
无线连接成功验证
•交互式菜单导航
•可滚动的菜单界面
•通过SSH使用键盘输入选择菜单选项
•OLED显示屏上的动态画面更新
结论
该项目成功展示了使用 Buildroot 为 Raspberry Pi Zero W 开发自定义嵌入式 Linux 发行版。主要目标是创建一个轻量级的 Linux 系统,能够支持 Wi-Fi 连接、SSH 和 SCP 访问、I2C 通信、Python 应用程序以及基于 OLED 的用户界面等基本嵌入式功能,同时保持较小的内存占用和快速的启动时间。
本文编译自hackster.io





