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为例,安装其实就三步:
- 下载工具链压缩包
- 解压到指定目录(我一般放
/opt/toolchains/) - 将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
板子上电后,到底发生了什么?理解这个流程,对后续调试至关重要。
我画了一张流程图,把整个启动过程串起来:
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 环境验证:确保一切就绪
环境搭好了,怎么确认没问题?我一般按这个顺序检查:
- 工具链: 编译一个简单的Hello World,用
file命令确认生成的是目标架构的ELF文件。 - TFTP: 在板子的U-Boot里用
tftp 0x82000000 test.txt下载文件,看是否成功。 - NFS: 在主机上挂载NFS目录,确认读写正常。然后在板子上用
mount命令挂载。 - 完整启动: 从TFTP加载内核,用NFS挂载根文件系统,看能否进入Shell。
我的习惯: 我会写一个自动化测试脚本,一键验证所有环节。这样每次换板子或者换环境,跑一遍脚本就能确认环境没问题,省心省力。
嗯,环境搭建这部分就讲到这里。工具链、TFTP、NFS、启动流程,这四个东西搞明白了,后续的内核裁剪和调试才能顺利进行。别急,慢慢来,每个环节都亲手做一遍,印象才深刻。