第三章 嵌入式Linux系统搭建:交叉编译环境、内核配置、设备树编译、根文件系统制作

说实话,很多刚入行的朋友觉得嵌入式Linux开发最难的是写驱动。但我个人经验告诉我——系统搭建才是真正的第一道坎。你驱动写得再好,环境没搭对,内核起不来,一切都是白搭。

这一章,我们就来啃这块硬骨头。我会把交叉编译环境、内核配置、设备树编译、根文件系统制作这四件事,掰开揉碎了讲清楚。

3.1 交叉编译环境搭建

先问个问题:为什么非要用交叉编译?

你想想看,你的开发电脑是x86架构,而目标板是ARM(比如我们用的STM32MP157或者i.MX6ULL)。x86的编译器编译出来的程序,ARM芯片根本跑不了。所以我们需要一套工具链——在x86上运行,但生成ARM可执行文件。这就是交叉编译。

核心概念:交叉编译工具链 = 编译器 + 链接器 + 库文件 + 头文件,全部针对目标架构。

我个人习惯用Linaro提供的预编译工具链,省时省力。以ARM Cortex-A7为例:

# 下载工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/

# 设置环境变量(建议写入 ~/.bashrc)
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

我的小技巧:环境变量一定要写在 ~/.bashrc 里,别每次手动 export。我曾经有个项目,因为忘记 export 导致编译出来的内核跑不起来,排查了整整一下午。

验证工具链是否安装成功:

arm-linux-gnueabihf-gcc --version

如果能看到版本信息,恭喜你,第一步走通了。

3.2 内核配置与编译

内核配置,说白了就是告诉内核:你要支持哪些硬件、哪些功能、哪些驱动。我刚开始做的时候,总觉得配置项太多,眼花缭乱。后来总结出一个方法——从默认配置开始,按需裁剪

3.2.1 获取内核源码

从官方仓库拉取,或者用芯片厂商提供的BSP包。以Linux 5.10 LTS为例:

git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git -b v5.10
cd linux

3.2.2 配置内核

三种配置方式,我分别说说:

配置方式 命令 适用场景
默认配置 make defconfig 快速起步,通用平台
菜单配置 make menuconfig 手动选择驱动、功能(最常用)
芯片厂商配置 make xxx_defconfig 使用厂商提供的默认配置

我个人最常用的是 make menuconfig。它基于ncurses的图形界面,操作直观。比如我们要添加一个摄像头驱动:

make menuconfig
# 进入 Device Drivers → Multimedia support → Media USB Adapters
# 选中 USB Video Class (UVC) 驱动

注意:配置完成后,配置会保存在 .config 文件中。千万别手抖删了它!我曾经有一次不小心 make distclean,配置全丢了,只能从头再来。

3.2.3 编译内核

配置好了,开始编译:

make -j4 zImage
make -j4 modules
make -j4 dtbs

-j4 表示用4个线程并行编译,能快不少。编译完成后,你会得到:

  • arch/arm/boot/zImage —— 内核镜像
  • arch/arm/boot/dts/ —— 设备树文件(.dtb)
  • 各个内核模块(.ko文件)

3.3 设备树编译

设备树(Device Tree)是什么?说白了,它就是一份硬件描述文件。告诉内核:你的板子上有哪些外设、它们挂在哪个总线上、中断号是多少、寄存器地址在哪。

嗯,这里要注意——设备树不是驱动,它是驱动的“说明书”

3.3.1 设备树源文件(.dts)

一个典型的设备树节点长这样:

/ {
    model = "My Smart Camera Board";
    compatible = "mycompany,smart-camera";

    chosen {
        stdout-path = &uart1;
    };

    memory {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 512MB内存
    };

    &i2c1 {
        status = "okay";
        clock-frequency = <100000>;

        ov5640: camera@3c {
            compatible = "ovti,ov5640";
            reg = <0x3c>;
            clocks = <&clks IMX6UL_CLK_CSI>;
            reset-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
            powerdown-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
        };
    };
};

看到那个 compatible 属性了吗?驱动就是靠它来匹配设备的。驱动里写 of_match_table,设备树里写 compatible,两者对上号,驱动才能绑定到设备。

3.3.2 编译设备树

设备树编译器(dtc)会把 .dts 编译成 .dtb:

# 单独编译某个设备树
make imx6ull-smart-camera.dtb

# 或者用dtc工具手动编译
dtc -I dts -O dtb -o myboard.dtb myboard.dts

避坑指南:我曾经在设备树里写错了GPIO号,结果摄像头初始化时一直报错。排查了三天,最后发现是 reset-gpios 的引脚号写反了。所以,设备树里的每一个数字,都要和原理图一一对应

3.4 根文件系统制作

内核启动后,需要挂载根文件系统(rootfs)。它包含了所有用户空间的程序、库文件、配置文件。没有它,内核就是个空壳。

制作根文件系统,我推荐两种方式:

3.4.1 使用Buildroot(推荐新手)

Buildroot是一个自动化构建工具,能一键生成完整的根文件系统:

# 下载Buildroot
git clone https://git.buildroot.net/buildroot
cd buildroot

# 配置(选择目标架构、软件包等)
make menuconfig

# 编译
make

编译完成后,在 output/images/ 下会生成 rootfs.tarrootfs.ext4。直接烧录到SD卡或eMMC即可。

3.4.2 手动制作(适合深度定制)

如果你需要完全控制文件系统的内容,可以手动搭建:

# 创建根文件系统目录结构
mkdir -p rootfs/{bin,dev,etc,lib,mnt,proc,sbin,sys,usr/{bin,lib,sbin},var}

# 复制Busybox(提供基本的shell命令)
cp /path/to/busybox rootfs/bin/
# 或者直接安装Busybox到rootfs
make -C /path/to/busybox CONFIG_PREFIX=/path/to/rootfs install

# 复制必要的库文件(来自工具链)
cp -r /opt/gcc-linaro-7.5.0/arm-linux-gnueabihf/libc/lib/* rootfs/lib/

# 创建设备节点
sudo mknod rootfs/dev/console c 5 1
sudo mknod rootfs/dev/null c 1 3

# 创建init脚本
cat > rootfs/init << EOF
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Hello from Smart Camera!"
exec /bin/sh
EOF
chmod +x rootfs/init

关键点:根文件系统必须包含 /init/sbin/init,否则内核启动后会 panic,因为找不到第一个用户进程。

3.4.3 打包成镜像

最后,把根文件系统打包成可烧录的格式:

# 制作ext4格式的镜像
dd if=/dev/zero of=rootfs.ext4 bs=1M count=64
mkfs.ext4 rootfs.ext4
sudo mount -o loop rootfs.ext4 /mnt
sudo cp -r rootfs/* /mnt/
sudo umount /mnt

或者直接打包成tar.gz,方便在开发阶段使用NFS挂载:

cd rootfs
tar czf ../rootfs.tar.gz .

3.5 启动验证

所有东西都准备好了,怎么验证?我一般用QEMU模拟器先跑一遍,省得反复烧录SD卡:

qemu-system-arm -M virt -kernel zImage -dtb myboard.dtb -drive file=rootfs.ext4,format=raw -append "root=/dev/vda console=ttyAMA0" -nographic

如果能看到内核启动日志,最后出现 Welcome to Smart Camera! 之类的提示,说明系统搭建成功了。

最后提醒一句:系统搭建这件事,一次成功是运气,反复调试才是常态。别怕出错,每个错误都是你积累经验的机会。我当年第一次搭系统,整整折腾了一周才把内核跑起来。现在回想起来,那段经历反而让我对Linux启动流程理解得特别透彻。

下一章,我们会正式开始写驱动——从最简单的GPIO控制开始,一步步点亮你相机外设的第一盏灯。