2. 开发环境搭建:交叉编译工具链安装、内核源码树配置、最小系统构建与NFS/TFTP启动

嵌入式Linux驱动开发与桌面开发最大的区别在于交叉编译与目标板启动。本章将带你从零搭建一套可用的开发环境,涵盖工具链安装、内核源码树配置、最小根文件系统构建,以及通过NFS/TFTP实现网络启动。完成本章后,你将拥有一个可编译、可调试、可快速迭代的驱动开发实验平台。

环境概览: 主机(x86_64 Ubuntu 22.04) + 目标板(ARM Cortex-A7/A9,如STM32MP157或i.MX6ULL)。所有操作均在主机侧完成,目标板仅作为运行载体。

2.1 交叉编译工具链安装

交叉编译工具链是嵌入式开发的基石。它运行在x86主机上,生成ARM架构的可执行文件。推荐使用Linaro GCC或芯片厂商提供的预编译工具链。

2.1.1 下载与解压

以ARM Cortex-A7为例,下载gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf版本:

# 创建工具链目录
mkdir -p ~/toolchain && cd ~/toolchain

# 下载(若网络受限,可提前下载后拷贝)
wget https://releases.linaro.org/components/toolchain/binaries/9.2-2019.12/arm-linux-gnueabihf/gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf.tar.xz

# 解压
tar -xvf gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf.tar.xz

2.1.2 环境变量配置

将工具链的bin目录加入PATH,并设置交叉编译前缀:

# 编辑 ~/.bashrc,追加以下内容
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:~/toolchain/gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf/bin

# 使配置生效
source ~/.bashrc

# 验证安装
arm-linux-gnueabihf-gcc --version
验证输出示例: arm-linux-gnueabihf-gcc (GCC) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]

2.1.3 工具链组件说明

组件 用途 示例命令
编译器 将C/C++源码编译为ARM目标文件 arm-linux-gnueabihf-gcc
汇编器 将汇编代码转为机器码 arm-linux-gnueabihf-as
链接器 将多个目标文件链接为可执行文件 arm-linux-gnueabihf-ld
调试器 远程调试目标板上的程序 arm-linux-gnueabihf-gdb
二进制工具 查看/操作ELF、反汇编等 arm-linux-gnueabihf-objdump

2.2 内核源码树配置

驱动开发需要内核源码树提供头文件、Kconfig结构和编译框架。我们使用与目标板匹配的Linux内核版本(如Linux 5.10或6.1 LTS)。

2.2.1 获取内核源码

# 从内核官网或芯片厂商仓库克隆
git clone --depth=1 -b linux-5.10.y https://github.com/beagleboard/linux.git ~/kernel/linux-5.10
cd ~/kernel/linux-5.10

2.2.2 配置内核

使用芯片厂商提供的默认配置(defconfig)作为起点:

# 以STM32MP157为例
make ARCH=arm stm32mp1_defconfig

# 若需自定义,使用menuconfig图形界面
make ARCH=arm menuconfig
注意: 驱动开发阶段,建议开启以下选项:
CONFIG_MODULES=y —— 支持动态加载驱动模块
CONFIG_DEBUG_FS=y —— 调试文件系统
CONFIG_DYNAMIC_DEBUG=y —— 动态调试支持

2.2.3 编译内核与设备树

# 设置环境变量
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 编译内核镜像(zImage)
make -j$(nproc) zImage

# 编译设备树文件(.dtb)
make -j$(nproc) dtbs

# 编译内核模块
make -j$(nproc) modules

编译产物位于:

  • arch/arm/boot/zImage —— 内核镜像
  • arch/arm/boot/dts/stm32mp157*.dtb —— 设备树二进制
  • drivers/ 下各目录中的 .ko 文件 —— 内核模块
内核源码树配置与编译流程 获取内核源码 make defconfig menuconfig 微调 make zImage make dtbs make modules zImage + .dtb + .ko → 部署到目标板

2.3 最小系统构建

一个可启动的嵌入式Linux系统包含三个核心组件:Bootloader(如U-Boot)、内核镜像(zImage)、根文件系统(rootfs)。本节聚焦于构建最小根文件系统。

2.3.1 使用BusyBox构建最小rootfs

BusyBox是一个集成了数百个常用Unix命令的单一可执行文件,非常适合嵌入式环境。

# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1

# 配置为静态编译(避免依赖动态库)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# 进入 Settings → Build static binary (no shared libs) → 选中

# 编译并安装到指定目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=~/rootfs install

2.3.2 完善根文件系统目录结构

cd ~/rootfs
mkdir -p proc sys dev tmp etc/init.d lib/modules

# 创建必要的设备节点
sudo mknod -m 666 dev/console c 5 1
sudo mknod -m 666 dev/null c 1 3

# 创建初始化脚本
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
echo "Welcome to Embedded Linux!"
EOF
chmod +x etc/init.d/rcS

# 创建inittab(BusyBox init使用)
cat > etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
EOF
最小rootfs大小: 使用静态编译的BusyBox,整个rootfs仅约2~3MB,非常适合NFS启动或烧录到SPI Flash。

2.4 NFS/TFTP启动配置

在驱动开发阶段,频繁烧写Flash效率极低。使用NFS挂载根文件系统 + TFTP下载内核,可实现“主机编译 → 目标板秒级启动”的快速迭代流程。

2.4.1 主机端配置NFS服务器

# 安装NFS服务器
sudo apt install nfs-kernel-server

# 编辑 /etc/exports,添加共享目录
echo "/home/$(whoami)/rootfs *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports

# 重启NFS服务
sudo systemctl restart nfs-kernel-server

# 验证挂载
showmount -e localhost

2.4.2 主机端配置TFTP服务器

# 安装TFTP服务器
sudo apt install tftpd-hpa

# 配置TFTP根目录(存放zImage和dtb)
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
sudo cp ~/kernel/linux-5.10/arch/arm/boot/zImage /srv/tftp/
sudo cp ~/kernel/linux-5.10/arch/arm/boot/dts/stm32mp157*.dtb /srv/tftp/

# 编辑 /etc/default/tftpd-hpa,确保以下配置
# TFTP_DIRECTORY="/srv/tftp"
# TFTP_OPTIONS="--secure --create"

# 重启TFTP服务
sudo systemctl restart tftpd-hpa

2.4.3 U-Boot启动参数设置

在目标板的U-Boot命令行中设置以下环境变量:

# 设置网络参数(根据实际网络修改)
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv netmask 255.255.255.0

# 设置内核启动参数(关键!)
setenv bootargs 'console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.10:/home/yourname/rootfs,proto=tcp rw ip=192.168.1.100:192.168.1.10:192.168.1.1:255.255.255.0::eth0:off'

# 从TFTP下载内核和设备树并启动
setenv bootcmd 'tftp 0xc2000000 zImage; tftp 0xc4000000 stm32mp157.dtb; bootz 0xc2000000 - 0xc4000000'

# 保存并启动
saveenv
boot
NFS/TFTP网络启动架构 开发主机(x86 Ubuntu) TFTP服务器 (zImage + dtb) NFS服务器 (rootfs) 交叉编译工具链 目标板(ARM Cortex-A7) U-Boot Bootloader Linux Kernel (zImage) 根文件系统 (NFS挂载) TFTP下载 NFS挂载 以太网

2.5 环境验证与常见问题

2.5.1 验证清单

验证项 命令/操作 预期结果
工具链可用 arm-linux-gnueabihf-gcc -v 显示版本信息,无错误
内核可编译 make zImage 生成 arch/arm/boot/zImage
NFS共享正常 showmount -e 显示 rootfs 路径
TFTP可下载 tftp localhost -c get zImage 成功传输文件
目标板网络连通 U-Boot下 ping 192.168.1.10 收到应答

2.5.2 常见问题与解决

问题1:NFS挂载失败,提示 "VFS: Unable to mount root fs"
原因:内核未开启NFS客户端支持。
解决:在menuconfig中启用 CONFIG_NFS_FS=y 和 CONFIG_IP_PNP=y,重新编译内核。
问题2:TFTP下载超时
原因:防火墙未放行TFTP端口(69/UDP)。
解决:sudo ufw allow 69/udp 或临时关闭防火墙测试。
问题3:BusyBox启动后提示 "can't run /etc/init.d/rcS: Permission denied"
原因:rcS脚本没有可执行权限。
解决:chmod +x ~/rootfs/etc/init.d/rcS,并确认NFS共享的no_root_squash选项已设置。

2.6 本章小结

至此,你已经完成了嵌入式Linux驱动开发环境的完整搭建:

  • 交叉编译工具链 —— 在x86主机上编译ARM代码
  • 内核源码树 —— 配置、编译内核与设备树
  • 最小根文件系统 —— 使用BusyBox构建轻量rootfs
  • 网络启动 —— NFS挂载rootfs + TFTP下载内核,实现秒级启动迭代