2. 开发环境搭建:交叉编译工具链安装、TFTP/NFS服务器配置、目标板启动流程

说实话,嵌入式开发环境搭建这件事,看着简单,但坑是真不少。我见过太多新手卡在这一步,工具链版本不对、服务器没配好、板子起不来……一折腾就是半天。这一章,我把这些年踩过的坑和总结的经验都摊开来讲。

2.1 交叉编译工具链:为什么需要它?

你想想看,你的开发机是x86架构,目标板可能是ARM、RISC-V或者MIPS。x86上编译出来的程序,ARM板子能直接跑吗?当然不能。这时候就需要交叉编译工具链——它能在你的x86机器上生成目标架构的可执行文件。

说白了,交叉编译工具链就是一套“翻译官”。它把你的源代码翻译成目标板能理解的机器码。

2.1.1 工具链的选择

我个人习惯用Linaro提供的预编译工具链,稳定且社区活跃。当然,你也可以自己用crosstool-NG从头构建,但那太费时间了,除非你有特殊需求。

目标架构 推荐工具链 下载地址
ARM (32-bit) gcc-arm-linux-gnueabihf Linaro Releases
AArch64 (64-bit) gcc-aarch64-linux-gnu Linaro Releases
RISC-V (64-bit) riscv64-unknown-linux-gnu SiFive / RISC-V GNU
注意: 工具链版本必须与你的内核版本匹配。我曾经因为用了太新的gcc编译旧版内核,结果链接阶段报了一堆莫名其妙的错误。后来换成Linaro 7.5.0,问题就解决了。

2.1.2 安装步骤

以ARM 32-bit为例,安装其实就三步:

  1. 下载工具链压缩包
  2. 解压到指定目录(我一般放 /opt/toolchains/)
  3. 将bin目录加入PATH环境变量
# 下载
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/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 -C /opt/toolchains/

# 配置环境变量(写入 ~/.bashrc)
export PATH=/opt/toolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH

# 验证
arm-linux-gnueabihf-gcc --version
小技巧: 我习惯在项目根目录下放一个 setenv.sh 脚本,每次打开终端先 source 一下,这样不同项目可以切换不同的工具链,互不干扰。

2.2 TFTP/NFS服务器配置:让板子“活”起来

目标板刚焊好,Flash里啥也没有。怎么把内核和文件系统传进去?这时候就需要TFTP和NFS了。

TFTP用来传内核镜像,NFS用来挂载根文件系统。两者配合,开发效率能提升一大截。

2.2.1 TFTP服务器配置

TFTP是个极简的文件传输协议,没有认证,没有加密,但胜在轻量。U-Boot里内置了TFTP客户端,可以直接从服务器拉取内核。

# 安装TFTP服务器(Ubuntu/Debian)
sudo apt-get install tftpd-hpa

# 配置文件 /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"          # 文件存放目录
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"

# 重启服务
sudo systemctl restart tftpd-hpa

# 测试:在 /srv/tftp 下放一个文件,然后在另一台机器上用 tftp 命令下载
echo "Hello TFTP" | sudo tee /srv/tftp/test.txt
tftp 192.168.1.100 -c get test.txt
关键点: TFTP目录的权限一定要配好。我遇到过好几次,文件明明在目录里,但板子就是下载失败——最后发现是tftp用户没有读权限。

2.2.2 NFS服务器配置

NFS用来共享根文件系统。开发阶段,你可以在主机上修改文件,板子立刻就能看到变化,不用反复烧写Flash。

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

# 编辑 /etc/exports,添加共享目录
/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)

# 创建根文件系统目录(假设你已经有一个构建好的rootfs)
sudo mkdir -p /srv/nfs/rootfs
sudo cp -a /path/to/your/rootfs/* /srv/nfs/rootfs/

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

# 验证
showmount -e localhost
注意: no_root_squash 这个选项很重要。如果不加,板子上的root用户会被映射成nobody,很多操作会权限不足。我刚开始做项目时就因为这个折腾了一下午。

2.3 目标板启动流程:从上电到Shell

板子上电后,到底发生了什么?理解这个流程,对后续调试至关重要。

我画了一张流程图,把整个启动过程串起来:

目标板启动流程图 目标板启动流程 上电复位 BootROM SPL / MLO U-Boot Linux 内核 根文件系统 各阶段说明 • BootROM:固化在芯片内部 • SPL:初始化DDR、时钟 • U-Boot:加载内核到内存 启动介质 • NAND Flash • eMMC / SD卡 • SPI Flash

2.3.1 各阶段详解

  • BootROM: 芯片出厂固化的代码,不可修改。它负责初始化最基本的硬件(比如时钟、片内SRAM),然后从预设的启动介质加载SPL。
  • SPL/MLO: 二级引导程序。它初始化DDR控制器,把U-Boot从Flash加载到DDR中。SPL体积很小,一般只有几十KB。
  • U-Boot: 功能完整的引导加载程序。它提供命令行交互,可以从TFTP、NFS、Flash等介质加载内核和设备树。
  • Linux内核: 解压并初始化硬件,挂载根文件系统。
  • 根文件系统: 提供用户空间程序,最终启动init进程,呈现Shell或图形界面。
调试技巧: 如果板子起不来,先看串口输出。BootROM阶段一般没输出,SPL阶段会打印一些信息,U-Boot阶段输出最详细。我通常会在U-Boot里用 printenv 查看环境变量,确认启动参数是否正确。

2.3.2 典型U-Boot启动参数

这里给出一组我常用的启动参数,配合TFTP和NFS使用:

setenv ipaddr 192.168.1.10
setenv serverip 192.168.1.100
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1

setenv bootcmd 'tftp 0x82000000 zImage; tftp 0x83000000 imx6ull.dtb; bootz 0x82000000 - 0x83000000'
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,vers=3 rw ip=192.168.1.10'

saveenv
boot
注意: NFS版本要匹配。有些旧版内核只支持NFSv3,而新版NFS服务器默认可能只开v4。我遇到过板子挂载NFS时一直卡住,最后发现是服务器没开v3支持。

2.4 环境验证:确保一切就绪

环境搭好了,怎么确认没问题?我一般按这个顺序检查:

  1. 工具链: 编译一个简单的Hello World,用 file 命令确认生成的是目标架构的ELF文件。
  2. TFTP: 在板子的U-Boot里用 tftp 0x82000000 test.txt 下载文件,看是否成功。
  3. NFS: 在主机上挂载NFS目录,确认读写正常。然后在板子上用 mount 命令挂载。
  4. 完整启动: 从TFTP加载内核,用NFS挂载根文件系统,看能否进入Shell。
我的习惯: 我会写一个自动化测试脚本,一键验证所有环节。这样每次换板子或者换环境,跑一遍脚本就能确认环境没问题,省心省力。

嗯,环境搭建这部分就讲到这里。工具链、TFTP、NFS、启动流程,这四个东西搞明白了,后续的内核裁剪和调试才能顺利进行。别急,慢慢来,每个环节都亲手做一遍,印象才深刻。