第二章 嵌入式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系统搭建核心流程 1. 交叉编译环境配置 2. 系统镜像烧录 3. 根文件系统定制 4. 设备树修改 ✅ 可运行的嵌入式Linux系统 关键工具 • Linaro工具链 • dd / balenaEtcher • Buildroot / Yocto • dtc (Device Tree Compiler) 常见坑点 • 工具链架构不匹配 • 烧录时选错设备号 • 设备树GPIO号搞错

这四步走完,你的嵌入式Linux系统就基本成型了。后面做AI算法部署,不管是环视拼接还是座舱内的手势识别,都建立在这个基础之上。

嗯,系统搭建这块确实琐碎,但它是整个车载AI项目的基石。我见过太多项目,算法模型调得再好,最后因为系统不稳定、驱动没配好、文件系统缺库,导致整个项目延期。所以,别嫌麻烦,把这章的内容亲手过一遍。等你真正在板子上跑起第一个AI模型时,那种成就感,值得。