3、嵌入式Linux系统移植:U-Boot编译与烧录、Linux内核裁剪与设备树配置、根文件系统构建
系统移植,说白了就是给硬件平台「穿上」Linux 的衣服。很多做 AI 应用的工程师觉得这步太底层,直接买开发板用现成的镜像就好。但做边缘 AI 产品不一样——你要裁剪内核、定制驱动、优化启动时间,甚至要适配非标准的传感器接口。我自己带项目时吃过这个亏:第一版原型直接用厂商 BSP,结果量产时发现 Flash 空间不够、启动慢了 2 秒被客户投诉。从那以后,我坚持自己走一遍移植流程。
这一章,我们聚焦三个核心环节:U-Boot(引导加载程序)、Linux 内核(含设备树)、根文件系统。我会结合一个典型的 ARM Cortex-A72 平台(比如 RK3588 或 i.MX8M Plus)来讲解。
3.1 U-Boot 编译与烧录
U-Boot 是硬件上电后第一个跑的程序。它的任务很简单:初始化 DDR、时钟、存储控制器,然后加载内核到内存并跳转。但简单不代表容易——我见过太多人卡在 U-Boot 阶段,串口只打印一个「CC」就死掉了。
3.1.1 获取与配置
从 官方仓库 克隆代码,或者直接用芯片厂商提供的版本。我个人习惯用厂商版本,因为里面已经包含了板级配置和 DDR 初始化代码。
# 以 RK3588 为例
git clone https://github.com/rockchip-linux/u-boot.git
cd u-boot
make rk3588_defconfig
make menuconfig
在 menuconfig 里,你需要关注几个关键项:
- Boot media:选择从 eMMC、SD 卡还是 SPI NOR Flash 启动。
- Network support:如果要用 TFTP 下载内核,必须开启。
- Environment storage:环境变量保存在哪里?我一般放 eMMC 的特定分区。
3.1.2 编译与烧录
编译命令很简单,但要注意交叉编译工具链的路径:
export CROSS_COMPILE=aarch64-linux-gnu-
make -j$(nproc)
编译产物通常是 u-boot.bin 或 idbloader.img(取决于平台)。烧录方式分两种:
| 烧录方式 | 适用场景 | 命令示例 |
|---|---|---|
| SD 卡 | 开发调试阶段 | dd if=u-boot.bin of=/dev/sdb seek=64 |
| eMMC | 量产烧录 | 使用厂商工具(如 rkdeveloptool) |
| SPI Flash | 小容量启动 | flashcp u-boot.bin /dev/mtd0 |
3.2 Linux 内核裁剪与设备树配置
内核是系统的核心,但默认配置往往包含大量你不需要的驱动。对于边缘 AI 设备,我们要做减法——去掉蓝牙、Wi-Fi(如果不用)、去掉多余的文件系统支持、去掉调试符号。
3.2.1 内核裁剪
从厂商提供的 defconfig 开始,逐步裁剪:
make ARCH=arm64 rockchip_linux_defconfig
make menuconfig
我通常会重点检查这几项:
- General setup → Kernel compression mode:选 LZ4 或 LZO,解压更快。
- Device Drivers:只保留你硬件上有的外设。比如你的摄像头是 MIPI CSI,就别选 USB Camera。
- File systems:如果只用 SquashFS 或 ext4,去掉其他。
- Networking support:边缘 AI 设备通常不需要完整的网络栈,可以裁剪掉 IPv6、802.11 等。
裁剪后,内核镜像大小可以从 15MB 降到 5MB 左右。启动时间也能缩短 1-2 秒。
3.2.2 设备树配置
设备树(Device Tree)描述了硬件拓扑结构。没有它,内核不知道你的 GPIO 接了什么、I2C 地址是多少、DDR 大小是多少。
设备树源文件(.dts)通常放在 arch/arm64/boot/dts/rockchip/ 下。以 RK3588 为例:
/dts-v1/;
#include "rk3588.dtsi"
/ {
model = "Edge AI Board v1.0";
compatible = "rockchip,rk3588";
chosen {
stdout-path = &uart2;
};
memory@a0000000 {
device_type = "memory";
reg = <0x0 0xa0000000 0x0 0x80000000>; // 2GB
};
};
&i2c2 {
status = "okay";
imx219: camera@10 {
compatible = "sony,imx219";
reg = <0x10>;
clocks = <&cru CLK_CAMERA>;
// ... 其他属性
};
};
编译设备树:
make ARCH=arm64 dtbs
生成 rk3588-edge-ai-board.dtb,和内核镜像一起打包。
cat /sys/kernel/debug/gpio 才发现引脚复用冲突。建议每次改完 dts 后,先检查 pinmux 是否正确。
3.3 根文件系统构建
根文件系统是 Linux 的用户空间基础。它包含 init 程序、库文件、配置文件、工具链等。对于嵌入式设备,我们通常用 Buildroot 或 Yocto 来构建。
3.3.1 Buildroot:轻量级选择
Buildroot 适合对大小敏感的项目。它用 Kconfig 配置,和内核风格一致,上手快。
git clone https://git.buildroot.net/buildroot
cd buildroot
make qemu_aarch64_virt_defconfig # 或自己从头配
make menuconfig
关键配置项:
- Target options:选择架构(AArch64)和 ABI。
- Toolchain:可以用外部工具链,也可以让 Buildroot 自己编译。
- System configuration:设置主机名、root 密码、启动脚本。
- Target packages:只选你需要的。比如 AI 推理需要 OpenCV、TensorFlow Lite,但不需要 X11 或 Qt。
编译完成后,在 output/images/ 下得到 rootfs.ext4、rootfs.cpio 等镜像。
3.3.2 Yocto:企业级方案
Yocto 更复杂,但定制性更强。它适合需要大量自定义软件包、多平台支持的项目。
基本流程:
git clone git://git.yoctoproject.org/poky
cd poky
source oe-init-build-env
bitbake core-image-minimal
Yocto 的 layer 机制很强大。你可以为 AI 推理引擎单独写一个 layer:
meta-edge-ai/
├── conf/
│ └── layer.conf
├── recipes-support/
│ └── tflite/
│ ├── tflite_2.14.0.bb
│ └── files/
│ └── tflite.rules
然后在 local.conf 中添加:
IMAGE_INSTALL_append = " tflite opencv python3"
| 特性 | Buildroot | Yocto |
|---|---|---|
| 学习曲线 | 低(1-2 天) | 高(1-2 周) |
| 镜像大小 | 小(可到 4MB) | 较大(通常 50MB+) |
| 包管理 | 无(静态编译) | 有(RPM/DEB) |
| 适用场景 | 资源受限设备 | 复杂产品、多平台 |
3.4 整合与启动验证
三个组件都准备好后,把它们打包成可启动镜像。以 SD 卡为例:
# 分区:第一个分区放内核和设备树,第二个分区放根文件系统
sudo fdisk /dev/sdb
# 创建两个分区:64MB 的 FAT32 + 剩余空间的 ext4
# 拷贝内核和设备树
sudo mount /dev/sdb1 /mnt/boot
sudo cp Image /mnt/boot/
sudo cp rk3588-edge-ai-board.dtb /mnt/boot/
sudo umount /mnt/boot
# 拷贝根文件系统
sudo mount /dev/sdb2 /mnt/rootfs
sudo tar -xvf rootfs.tar -C /mnt/rootfs/
sudo umount /mnt/rootfs
插卡上电,串口输出应该能看到 U-Boot 启动、加载内核、挂载根文件系统,最后出现登录提示符。
- U-Boot 能识别 DDR 容量和存储设备
- 内核能挂载根文件系统(无 panic)
- 设备树中的外设(I2C、SPI、GPIO)能正常工作
- AI 推理库(如 TensorFlow Lite)能加载模型
嗯,到这里系统移植的核心流程就走通了。你可能会遇到各种奇怪的问题——DDR 时序不对、设备树中断号写错、文件系统缺少动态链接库。别慌,每个问题都是学习的机会。我当年第一次移植成功时,看着串口打印出 Welcome to Buildroot,那种成就感比写十年代码都强。