2. 开发环境搭建:交叉编译工具链配置、TFTP/NFS服务搭建、内核源码树构建、设备树编译器安装

说实话,很多初学者一上来就急着写驱动代码,结果编译都过不了。为什么?环境没搭好。我见过太多人在 x86 上编译 ARM 代码,然后一脸懵地问我为什么跑不起来。嗯,咱们先把地基打牢。

嵌入式开发环境搭建,说白了就是让 PC 能生成目标板能跑的代码,并且能方便地把代码传过去调试。这一章,我带你一步步搞定这四件事。

2.1 交叉编译工具链配置

交叉编译,就是在你的 x86 电脑上编译出 ARM 架构的二进制文件。你想想看,嵌入式设备性能有限,不可能在上面直接编译。

2.1.1 工具链的选择

我个人习惯用 Linaro 提供的 GCC 工具链,稳定且社区活跃。当然,你也可以用芯片厂商提供的 SDK 里自带的工具链。

工具链类型 适用场景 获取方式
Linaro GCC ARM Cortex-A 系列通用开发 官网下载或 apt 安装
ARM GCC (arm-none-eabi) 裸机或 RTOS 开发 ARM 官网下载
芯片厂商 SDK 特定芯片优化,含 BSP 厂商官网或开发板配套光盘

2.1.2 安装与配置

以 Linaro 工具链为例,我通常这样操作:

# 下载工具链(以 aarch64 为例)
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz

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

# 配置环境变量
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
小技巧: 把环境变量写到 ~/.bashrc 里,这样每次打开终端就不用重新 export 了。我习惯在文件末尾加一句 source ~/my_env.sh,把所有交叉编译相关的变量集中管理。

2.1.3 验证工具链

配置完一定要验证一下,别等到编译内核时才发现问题。

aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-gcc -v 2>&1 | grep "Target:"

如果输出显示 Target: aarch64-linux-gnu,说明工具链工作正常。我曾经有一次解压路径写错了,结果调了半天才发现 PATH 指向了空目录。嗯,这种低级错误,犯过一次就不会再犯了。

2.2 TFTP/NFS 服务搭建

开发阶段,你不可能每次修改代码都重新烧写整个镜像。TFTP 和 NFS 就是用来解决这个问题的。TFTP 传内核和设备树,NFS 挂载根文件系统。

2.2.1 TFTP 服务

TFTP 简单,就是用来从开发板下载文件到内存。我一般用 tftp-hpa 这个包。

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

# 配置 TFTP 目录
sudo mkdir -p /tftpboot
sudo chmod 777 /tftpboot

# 修改配置文件 /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
注意: 防火墙要放行 UDP 69 端口。我曾经在客户现场调试,死活连不上 TFTP,最后发现是防火墙没关。从那以后,我每次搭建环境第一件事就是检查防火墙。

2.2.2 NFS 服务

NFS 用来共享根文件系统。开发板启动后,直接通过网络挂载 PC 上的目录作为根文件系统。这样你修改了驱动或应用,重启开发板就能生效,不用反复烧写。

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

# 配置 /etc/exports
/opt/rootfs *(rw,sync,no_subtree_check,no_root_squash)

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

这里有个关键点:no_root_squash 一定要加。不加的话,开发板上的 root 用户会被映射成 nobody,很多操作会权限不足。我刚开始做驱动时就被这个坑过,调试一个 GPIO 驱动,明明代码没问题,就是写不进寄存器,查了半天才发现是 NFS 权限问题。

2.3 内核源码树构建

写驱动必须要有内核源码树。不是让你把整个内核源码都编译一遍,而是需要它的头文件和编译框架。

2.3.1 获取内核源码

我建议从内核官网或芯片厂商的仓库拉取。以 Linux 5.10 LTS 为例:

git clone --depth 1 -b linux-5.10.y git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd linux-stable

--depth 1 只拉取最新版本,省时间也省空间。如果你需要查看历史提交,去掉这个参数就行。

2.3.2 配置与编译内核

配置内核有两种方式:

  • 使用厂商默认配置: make ARCH=arm64 defconfig 或 make ARCH=arm64 xxx_defconfig
  • 手动配置: make ARCH=arm64 menuconfig

我个人习惯先用厂商的 defconfig,然后根据需要微调。比如:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4
关键点: 编译内核时,CROSS_COMPILE 和 ARCH 必须和你安装的工具链一致。否则编译出来的内核在目标板上跑不起来。

2.3.3 构建内核模块

驱动开发中,你通常只编译自己的模块,而不是整个内核。但模块编译依赖内核源码树:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_prepare

这个命令会生成模块编译所需的 Module.symvers 和头文件依赖。之后你写驱动时,只需要在 Makefile 里指定内核源码路径即可。

2.4 设备树编译器安装

设备树(Device Tree)是描述硬件信息的文件。内核启动时通过它来知道板子上有哪些外设。设备树编译器(DTC)就是把 .dts 源文件编译成 .dtb 二进制文件的工具。

2.4.1 安装 DTC

DTC 通常包含在内核源码中,路径是 scripts/dtc/。你也可以单独安装:

# 方法一:从内核源码编译
cd linux-stable/scripts/dtc
make

# 方法二:apt 安装(版本可能较旧)
sudo apt-get install device-tree-compiler

我推荐用内核源码自带的 DTC,版本和内核匹配,不会出现兼容性问题。

2.4.2 编译设备树

编译设备树的命令很简单:

dtc -I dts -O dtb -o myboard.dtb myboard.dts

反编译也常用:

dtc -I dtb -O dts -o myboard.dts myboard.dtb
调试技巧: 反编译出来的 .dts 文件可以用来对比你修改前后的差异。我经常用 diff 命令来检查设备树改动是否正确。

2.5 本章知识体系

下面这张图概括了开发环境搭建的整体流程和各个组件之间的关系:

嵌入式开发环境搭建知识体系 主机(PC) 交叉工具链 内核源码树 TFTP 服务 NFS 服务 目标板(ARM) U-Boot 内核 设备树 根文件系统 TFTP 传输 NFS 挂载 核心流程 主机编译内核和设备树 → TFTP 传输到目标板 → 目标板启动内核 主机通过 NFS 共享根文件系统 → 目标板挂载后运行驱动和应用 关键工具清单 交叉编译器:aarch64-linux-gnu-gcc / arm-linux-gnueabihf-gcc 设备树编译器:dtc(Device Tree Compiler) 网络服务:tftpd-hpa / nfs-kernel-server

2.6 避坑指南

最后,分享几个我踩过的坑:

  • 工具链版本不匹配: 内核 5.x 用 GCC 7.x 没问题,但内核 6.x 可能需要 GCC 10.x 以上。编译报错时先检查工具链版本。
  • NFS 权限问题: 记得加 no_root_squash,否则 root 用户权限不够。
  • TFTP 文件找不到: 检查文件权限和路径。TFTP 默认只读,要加 -c 选项才能创建文件。
  • 设备树编译错误: 语法错误最常见。用 dtc -I dts -O dtb -o /dev/null myboard.dts 只检查语法,不生成文件。

环境搭建好了,后面写驱动、调试、调优才能顺利进行。别嫌这一步麻烦,基础打牢了,后面事半功倍。