第二章 嵌入式Linux系统搭建:交叉编译环境配置、系统镜像烧录、根文件系统定制、设备树修改基础
说实话,很多做AI算法的朋友,一听到“嵌入式Linux系统搭建”就头大。觉得这是BSP工程师的活,跟自己没关系。
但我得说句实话——在车载AI座舱和环视项目里,不懂系统搭建,你连算法怎么跑起来的都不知道。我见过太多算法工程师,模型在PC上跑得飞起,一上板子就黑屏,然后一脸懵地来找我:“是不是你们系统有问题?”
嗯,很多时候确实是系统有问题。但如果你自己能搞定交叉编译、烧录、改设备树,这些问题根本不用求人。
2.1 交叉编译环境配置——为什么不能在板子上直接编译?
你可能会问:我的开发板性能也不差啊,为什么非要在PC上编译,再拷过去?
原因很简单:嵌入式设备的资源是用来跑应用的,不是用来编译代码的。一个完整的Linux内核编译,动辄几十分钟甚至一两个小时。你在板子上编译,CPU跑满,内存吃光,其他事全干不了。
我早期做项目时,图省事直接在ARM板子上编译一个OpenCV,结果编译了三个多小时,中途散热不好还死机了……从那以后,我再也没在板子上编译过任何东西。
2.1.1 工具链的选择
交叉编译的核心就是工具链。说白了,就是一套在x86 PC上生成ARM可执行文件的编译器、链接器、库文件集合。
| 工具链类型 | 典型名称 | 适用场景 |
|---|---|---|
| GNU通用工具链 | arm-linux-gnueabihf- | 大多数ARM Linux系统 |
| Linaro优化工具链 | aarch64-linux-gnu- | ARMv8 64位平台(如树莓派3B+、RK3399) |
| 芯片厂商定制工具链 | arm-rockchip-linux-gnueabihf- | 瑞芯微、全志等SoC专用 |
我个人习惯用Linaro的预编译工具链。省事,社区活跃,遇到问题也好搜。
关键点:工具链的前缀决定了你编译出来的程序能在什么架构上跑。比如 arm-linux-gnueabihf-gcc 编译出来的程序,只能在ARMv7+硬浮点的CPU上运行。选错了,程序跑不起来是小事,跑起来出诡异bug才要命。
2.1.2 环境变量配置
配置交叉编译环境,其实就是告诉系统:编译器在哪、头文件在哪、库文件在哪。
# 解压工具链
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
sudo mv gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf /opt/
# 配置环境变量(建议写入 ~/.bashrc)
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
# 验证是否配置成功
arm-linux-gnueabihf-gcc --version
小技巧:我习惯写一个 setenv.sh 脚本,每个项目单独配置环境变量。这样不同项目用不同工具链时,不会互相干扰。你想想看,同时维护三四个项目,每个项目用的工具链版本都不一样,全靠一个 ~/.bashrc 管理,迟早要出问题。
2.2 系统镜像烧录——把系统装进板子
环境配好了,接下来就是把系统烧进板子。这一步看起来简单,但坑特别多。
2.2.1 烧录方式对比
| 方式 | 工具 | 速度 | 适用场景 |
|---|---|---|---|
| SD卡烧录 | dd、balenaEtcher、Win32DiskImager | 中等 | 开发调试阶段,方便更换系统 |
| eMMC烧录 | 厂商工具(如Rockchip的RKDevTool) | 快 | 量产或最终定型 |
| 网络烧录 | TFTP + NFS | 慢 | 内核调试,频繁修改内核 |
| JTAG烧录 | OpenOCD、JLink | 慢 | 裸机或uboot调试 |
我个人最常用SD卡烧录。原因很简单:拔卡、插卡、换系统,三分钟搞定。eMMC烧录虽然快,但一旦烧死,救砖很麻烦。
2.2.2 SD卡烧录实战
# 查看SD卡设备号(注意别选错!)
lsblk
# 假设SD卡是 /dev/sdb,卸载所有分区
sudo umount /dev/sdb*
# 烧录镜像(注意:of=后面是设备,不是分区)
sudo dd if=ubuntu-18.04-armhf.img of=/dev/sdb bs=4M status=progress
# 同步并弹出
sync
sudo eject /dev/sdb
警告:我曾经有一次没仔细看 lsblk 的输出,把 of=/dev/sda 写成了 of=/dev/sdb……结果把我工作电脑的系统盘给覆盖了。嗯,那天下午我什么都没干,光重装系统了。所以,烧录前一定再三确认设备号。
2.3 根文件系统定制——你的系统你做主
系统能启动了,但你会发现——怎么什么都没有?没有 apt-get,没有 gcc,甚至连 ls 都只有最基本的几个命令。
这就是根文件系统(RootFS)的作用。它决定了你的系统里有什么、能干什么。
2.3.1 根文件系统的组成
一个典型的嵌入式根文件系统包含:
- bin/、sbin/:基本命令(BusyBox提供)
- lib/:动态库(glibc或uClibc)
- etc/:配置文件(inittab、fstab、网络配置)
- dev/:设备节点(通常由udev或mdev管理)
- proc/、sys/:虚拟文件系统挂载点
- usr/:用户程序和数据
2.3.2 使用Buildroot定制
Buildroot是我最常用的根文件系统构建工具。它能把交叉编译工具链、内核、根文件系统、第三方库全部打包好。
# 下载Buildroot
git clone https://git.busybox.net/buildroot
cd buildroot
# 配置(选择目标平台和软件包)
make menuconfig
# 关键配置项:
# Target options -> Target Architecture = ARM (little endian)
# Target options -> Target Binary Format = ELF
# Target options -> ARM variant = cortex-a7
# Toolchain -> Toolchain type = External toolchain
# System configuration -> Root filesystem overlay directories
# 开始编译(第一次编译较慢,约30分钟)
make
# 编译完成后,output/images/ 下会生成:
# rootfs.tar、rootfs.cpio、zImage、*.dtb
经验之谈:做AI座舱项目时,我建议你在Buildroot里提前选上这些包:python3、opencv、tensorflow-lite、gstreamer1。虽然编译时间会长一些,但总比后面缺库了再手动交叉编译要省心得多。
2.4 设备树修改基础——让内核认识你的硬件
设备树(Device Tree)是嵌入式Linux里最让人头疼的东西之一。但说白了,它就是一份硬件描述文件,告诉内核:CPU有哪些外设、内存多大、GPIO怎么接、I2C设备地址是多少。
2.4.1 设备树文件结构
一个典型的设备树文件(.dts)长这样:
/dts-v1/;
#include "rk3288.dtsi"
/ {
model = "Rockchip RK3288 Firefly Board";
compatible = "rockchip,rk3288-firefly", "rockchip,rk3288";
memory@0 {
device_type = "memory";
reg = <0x0 0x80000000>; // 2GB内存
};
&i2c1 {
status = "okay";
clock-frequency = <400000>;
ov5640: camera@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
pinctrl-names = "default";
pinctrl-0 = <&cif_pdn>;
reset-gpios = <&gpio7 RK_PA0 GPIO_ACTIVE_LOW>;
};
};
};
2.4.2 常见修改场景
| 场景 | 修改内容 | 示例 |
|---|---|---|
| 增加摄像头 | 添加I2C设备节点,配置GPIO | 添加 ov5640 节点 |
| 修改显示分辨率 | 修改 display-timings |
调整 hactive、vactive |
| 启用SPI接口 | 设置 status = "okay" |
&spi1 { status = "okay"; }; |
| 调整内存大小 | 修改 reg 属性 |
reg = <0x0 0x40000000>; (1GB) |
2.4.3 编译设备树
# 编译.dts为.dtb
dtc -I dts -O dtb -o rk3288-firefly.dtb rk3288-firefly.dts
# 反编译.dtb为.dts(查看别人的设备树)
dtc -I dtb -O dts -o rk3288-firefly.dts rk3288-firefly.dtb
注意:设备树里一个常见的坑是 GPIO号对应关系。不同SoC的GPIO编号方式不一样。比如RK3288的GPIO7_A0,在设备树里可能是 <&gpio7 RK_PA0 GPIO_ACTIVE_LOW>,但在驱动里可能是另一个数字。我曾经因为GPIO号搞错,查了两天为什么摄像头初始化不成功……最后发现是reset引脚没拉对。
2.5 本章知识体系
下面这张图,帮你把本章的核心逻辑串起来:
这四步走完,你的嵌入式Linux系统就基本成型了。后面做AI算法部署,不管是环视拼接还是座舱内的手势识别,都建立在这个基础之上。
嗯,系统搭建这块确实琐碎,但它是整个车载AI项目的基石。我见过太多项目,算法模型调得再好,最后因为系统不稳定、驱动没配好、文件系统缺库,导致整个项目延期。所以,别嫌麻烦,把这章的内容亲手过一遍。等你真正在板子上跑起第一个AI模型时,那种成就感,值得。