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"
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 本章知识体系
下面这张图概括了开发环境搭建的整体流程和各个组件之间的关系:
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只检查语法,不生成文件。
环境搭建好了,后面写驱动、调试、调优才能顺利进行。别嫌这一步麻烦,基础打牢了,后面事半功倍。