3. 开发环境搭建:安装交叉编译工具链、配置TFTP/NFS服务、搭建最小化实验环境

说实话,嵌入式开发环境搭建这件事,看着简单,但坑是真不少。我见过太多新手,甚至有些老手,卡在环境配置上好几天。说白了,这步要是没弄好,后面写再多代码都是白搭。

这一章,我就带你一步步把环境搭起来。咱们的目标很明确:装好交叉编译工具链、配好TFTP和NFS服务、跑通最小化实验环境。这三样东西,就是你后面所有实验的基石。

本章核心目标:在Ubuntu 20.04/22.04宿主机上,搭建一个完整的嵌入式开发环境,能编译、能下载、能调试。

3.1 交叉编译工具链的安装

先说说什么是交叉编译。你想想看,你的开发机是x86架构,但目标板是ARM架构。x86上编译出来的程序,ARM板子能直接跑吗?当然不能。所以我们需要一个交叉编译工具链——在x86上运行,但生成ARM机器码的编译器。

我个人习惯用Linaro提供的工具链,稳定、社区活跃、更新及时。当然,你也可以用ARM官方的gcc-arm-none-eabi,或者自己用crosstool-NG编译一个。但说实话,对于学习Busybox来说,Linaro的预编译版本就足够了。

3.1.1 下载与解压

我建议你统一把工具链放在 /opt/toolchains 目录下,方便管理。别问我为什么,我在项目里吃过乱放的亏——后来换电脑、换项目,找工具链找得想哭。

# 创建工具链目录
sudo mkdir -p /opt/toolchains
cd /opt/toolchains

# 下载Linaro ARM交叉编译工具链(以gcc-arm-10.2-2020.11为例)
sudo 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

# 解压
sudo 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 arm-linux-gnueabihf-7.5

小提示:如果你用的是ARM64架构的板子(比如树莓派3B+、RK3399),记得下载aarch64版本的工具链。别搞混了,我见过有人拿arm-linux-gnueabihf去编译aarch64的内核,结果折腾了一整天。

3.1.2 配置环境变量

解压完只是第一步,你得让系统能找到这些工具。配置环境变量,我推荐写在 ~/.bashrc 里,而不是 /etc/profile。为什么?因为每个用户可能有不同的工具链需求,全局配置容易打架。

# 编辑 ~/.bashrc
vim ~/.bashrc

# 在文件末尾添加
export PATH=$PATH:/opt/toolchains/arm-linux-gnueabihf-7.5/bin
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

# 使配置生效
source ~/.bashrc

配置完,验证一下:

# 检查编译器版本
arm-linux-gnueabihf-gcc --version

# 如果看到类似下面的输出,说明安装成功
# arm-linux-gnueabihf-gcc (Linaro GCC 7.5-2019.12) 7.5.0

注意:如果你同时装了多个版本的GCC,记得检查PATH顺序。系统会使用第一个找到的版本。我曾经因为PATH顺序问题,用错了编译器版本,编译出来的内核在板子上跑起来就崩,查了三天才找到原因。

3.2 TFTP服务配置

TFTP(Trivial File Transfer Protocol)说白了就是一个超简单的文件传输协议。在嵌入式开发中,我们常用它来通过网络下载内核镜像和设备树文件到开发板。为什么不用FTP或SCP?因为TFTP足够轻量,而且U-Boot原生支持。

3.2.1 安装与配置

在Ubuntu上安装TFTP服务很简单:

# 安装tftp-hpa(客户端)和tftpd-hpa(服务端)
sudo apt-get update
sudo apt-get install tftp-hpa tftpd-hpa -y

安装完,需要配置一下:

# 编辑配置文件
sudo vim /etc/default/tftpd-hpa

# 修改为以下内容
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"          # TFTP根目录
TFTP_ADDRESS="0.0.0.0:69"          # 监听所有IP的69端口
TFTP_OPTIONS="-l -c -s"            # -l: 日志, -c: 允许创建文件, -s: 安全模式

创建TFTP根目录并设置权限:

sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
sudo chown tftp:tftp /srv/tftp

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

3.2.2 测试TFTP服务

配置完,一定要测试一下。我习惯用本地回环地址测试:

# 在TFTP目录下创建一个测试文件
echo "Hello TFTP" | sudo tee /srv/tftp/test.txt

# 用tftp客户端测试
tftp localhost
tftp> get test.txt
tftp> quit

# 检查是否下载成功
cat test.txt

如果能看到 "Hello TFTP",说明服务正常。

避坑指南:防火墙可能会拦截TFTP的69端口。如果你发现客户端连不上,先检查防火墙:sudo ufw status。如果开启了,记得放行:sudo ufw allow 69/udp。

3.3 NFS服务配置

NFS(Network File System)比TFTP强大得多。它允许开发板直接挂载宿主机上的目录,就像访问本地硬盘一样。这对于调试阶段特别有用——你可以在宿主机上编译程序,开发板立刻就能运行,不用反复烧写。

3.3.1 安装与配置

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

配置共享目录:

# 创建NFS共享目录
sudo mkdir -p /srv/nfs/rootfs

# 编辑exports文件
sudo vim /etc/exports

# 添加以下内容(假设开发板IP为192.168.1.0/24网段)
/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)

参数说明:

参数 含义
rw 允许读写
sync 同步写入,保证数据一致性
no_subtree_check 禁用子树检查,提升性能
no_root_squash 允许开发板上的root用户拥有宿主机root权限

重要:no_root_squash 这个参数要谨慎使用。它意味着开发板上的root用户对共享目录有完全控制权。在调试阶段没问题,但生产环境千万别这么配。我见过有人忘了改这个,结果开发板被入侵后,宿主机也跟着遭殃。

配置完,重启NFS服务:

sudo systemctl restart nfs-kernel-server
sudo systemctl enable nfs-kernel-server

# 验证共享是否生效
showmount -e localhost

3.4 搭建最小化实验环境

好了,工具链和服务都配好了。现在我们来搭建一个最小化的实验环境。这个环境包括:一个能启动的Linux内核、一个Busybox构建的根文件系统、以及通过TFTP/NFS启动的配置。

3.4.1 整体架构

先看看整个实验环境的架构,我画了一张图帮你理解:

最小化实验环境架构图 宿主机 (Ubuntu x86) 交叉编译工具链 TFTP服务 (/srv/tftp) NFS服务 (/srv/nfs/rootfs) 内核源码 + Busybox源码 开发板 (ARM) U-Boot引导程序 Linux内核 (zImage) 设备树文件 (.dtb) 根文件系统 (NFS挂载) TFTP下载内核/DTB NFS挂载根文件系统 工作流程 1. 开发板上电 → U-Boot启动 → 通过TFTP下载内核和DTB 2. 内核启动 → 通过NFS挂载根文件系统 → 进入Busybox shell 以太网连接 (192.168.1.x)

3.4.2 准备内核和根文件系统

先编译一个最小化的内核。这里我以Linux 5.10 LTS版本为例:

# 下载内核源码
cd ~
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar -xvf linux-5.10.100.tar.xz
cd linux-5.10.100

# 配置内核(使用默认的vexpress配置,适合QEMU模拟)
make vexpress_defconfig

# 编译内核(-j4表示用4个线程编译,根据你的CPU核心数调整)
make -j4 zImage dtbs

# 编译完成后,复制到TFTP目录
cp arch/arm/boot/zImage /srv/tftp/
cp arch/arm/boot/dts/vexpress-v2p-ca9.dtb /srv/tftp/

接下来,用Busybox构建根文件系统。这部分我会在后面的章节详细讲,这里先搭个最小框架:

# 下载Busybox
cd ~
wget https://busybox.net/downloads/busybox-1.33.2.tar.bz2
tar -xvf busybox-1.33.2.tar.bz2
cd busybox-1.33.2

# 配置Busybox
make defconfig
make menuconfig  # 这里可以按需裁剪,后面章节会细讲

# 编译并安装到NFS目录
make -j4
make CONFIG_PREFIX=/srv/nfs/rootfs install

3.4.3 配置U-Boot启动参数

最后,在U-Boot中设置启动参数。假设你的开发板IP为192.168.1.100,宿主机IP为192.168.1.10:

# 在U-Boot命令行中设置
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv netmask 255.255.255.0

# 设置TFTP下载内核和设备树
setenv bootcmd 'tftp 0x60000000 zImage; tftp 0x65000000 vexpress-v2p-ca9.dtb; bootz 0x60000000 - 0x65000000'

# 设置内核启动参数(NFS根文件系统)
setenv bootargs 'console=ttyAMA0,115200 root=/dev/nfs nfsroot=192.168.1.10:/srv/nfs/rootfs rw ip=192.168.1.100'

# 保存并启动
saveenv
boot

调试技巧:如果启动失败,别慌。先检查网络连通性:在U-Boot里用 ping 192.168.1.10 测试。如果ping不通,八成是网线或IP配置问题。我遇到过最奇葩的一次,是交换机端口被配置了VLAN隔离,折腾了两小时才发现。

3.5 验证环境

环境搭好了,怎么验证?我的做法是:写一个简单的Hello World程序,交叉编译后放到NFS目录,在开发板上运行。

# 在宿主机上
cat > hello.c << EOF
#include <stdio.h>

int main()
{
    printf("Hello from ARM!\n");
    printf("Busybox environment is ready!\n");
    return 0;
}
EOF

# 交叉编译
arm-linux-gnueabihf-gcc -o hello hello.c

# 复制到NFS目录
cp hello /srv/nfs/rootfs/

然后在开发板的串口终端上:

# 在开发板上
cd /
./hello

# 如果看到输出
# Hello from ARM!
# Busybox environment is ready!

看到这个输出,说明你的整个开发环境已经跑通了。交叉编译、TFTP下载、NFS挂载、Busybox根文件系统,所有环节都正常工作。

环境验证清单:

  • ✅ 交叉编译工具链能正常编译ARM程序
  • ✅ TFTP服务能下载内核和设备树
  • ✅ NFS服务能挂载根文件系统
  • ✅ 开发板能通过U-Boot引导内核
  • ✅ Busybox shell能正常执行程序

嗯,到这里,你的嵌入式开发环境就搭建完成了。后面所有的实验,都会基于这个环境进行。记住,环境搭建是基础中的基础,花点时间把它搞扎实,后面会省很多事。