3. 开发环境搭建:Ubuntu虚拟机、交叉编译链与TFTP/NFS服务

做嵌入式Linux开发,第一步不是写代码,而是先把“窝”搭好。我见过不少新手,代码写得挺溜,结果卡在环境配置上好几天,最后发现是工具链版本不对。这一章,咱们就把开发环境彻底理清楚。

说白了,我们的目标就三个:一台能跑Linux的虚拟机、一套能把代码编译成ARM架构可执行文件的工具链、以及能让开发板和宿主机无障碍传文件的服务。这三样齐了,后面的事情才谈得上。

本章核心脉络: 宿主机(Ubuntu虚拟机)→ 交叉编译工具链(arm-linux-gnueabihf)→ 网络服务(TFTP/NFS)→ 目标板(ARM网关)
开发环境整体架构 宿主机 Ubuntu 20.04 LTS 交叉编译工具链 arm-linux-gnueabihf- 网络服务 TFTP + NFS 目标板 ARM Cortex-A7 网关 代码编写 & 编译 (x86_64 架构) 文件传输 & 根文件系统挂载 (以太网) 生成 ARM 可执行文件 (.elf / .bin)

3.1 Ubuntu虚拟机安装——选对版本,少走弯路

虚拟机软件我推荐 VMware Workstation Pro 或者 VirtualBox,两者我都用过。个人习惯用 VMware,网络配置更直观一些。但 VirtualBox 免费,学生党用着也没压力。

版本选择: 别追新。Ubuntu 22.04 虽然新,但有些老牌工具链的依赖包还没完全适配。我建议用 Ubuntu 20.04 LTS,稳定、资料多、遇到问题随便一搜就有答案。

我的经验: 虚拟机硬盘给 80GB 以上,后面编译内核、Buildroot 动不动就吃掉十几 GB。内存至少 4GB,8GB 更舒服。CPU 核心数给 2 个以上,不然编译时你会怀疑人生。

安装步骤其实很简单,我快速过一下重点:

  1. 下载镜像: 去 Ubuntu 官网下载 20.04.6 LTS 桌面版 ISO。
  2. 新建虚拟机: 选择“典型”安装,稍后安装操作系统,选 Linux → Ubuntu 64 位。
  3. 网络类型: 选“桥接模式”。这样虚拟机跟开发板就在同一个网段,TFTP/NFS 才能通。
  4. 安装系统: 挂载 ISO,启动虚拟机,一路默认就行。分区我建议手动分——/ 给 30GB,/home 给剩下的,swap 给 4GB。
注意: 安装时如果选了“NAT 模式”,虚拟机跟宿主机虽然能上网,但跟开发板不在同一个子网,后面 TFTP 和 NFS 会连不上。我曾经在这上面浪费过一下午,切记。

3.2 交叉编译工具链配置——arm-linux-gnueabihf

交叉编译,说白了就是在 x86 的电脑上编译出 ARM 能跑的程序。你想想看,开发板那点算力,编译个 Linux 内核得等到猴年马月?所以这事必须在宿主机上干。

工具链的选择,我推荐 Linaro 提供的 arm-linux-gnueabihf- 版本。为什么是它?因为它是 ARM 官方推荐的,更新及时,而且支持硬浮点(hf),性能比软浮点好不少。

3.2.1 下载与安装

# 下载 Linaro 工具链(以 gcc-linaro-7.5.0 为例)
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

# 解压到 /opt 目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/

# 重命名(方便管理)
sudo mv /opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf /opt/gcc-arm-linux-gnueabihf

3.2.2 配置环境变量

我个人习惯把工具链路径加到 ~/.bashrc 里,这样每次打开终端自动生效:

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

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

# 使配置生效
source ~/.bashrc

3.2.3 验证安装

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

# 编译一个测试程序
echo 'int main(){ return 0; }' > test.c
arm-linux-gnueabihf-gcc -o test test.c

# 查看生成的文件类型
file test
# 输出应为:ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)
关键检查点: 如果 file test 显示的是 x86-64,说明你用的是宿主机自带的 gcc,不是交叉编译器。检查一下 PATH 顺序,或者直接用绝对路径调用。

3.3 TFTP服务搭建——给开发板传文件

TFTP 这东西,说白了就是一个极简版的 FTP,没有认证、没有目录列表,但胜在轻量。U-Boot 里默认就支持 TFTP 下载,所以我们用它来给开发板传内核镜像和设备树。

3.3.1 安装与配置

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

# 创建 TFTP 根目录
sudo mkdir -p /tftpboot
sudo chmod 777 /tftpboot

# 修改配置文件
sudo vim /etc/default/tftpd-hpa

配置文件内容如下:

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
注意: -c 选项允许上传文件,调试时很有用。但生产环境建议去掉,只保留 -l -s。

3.3.2 启动与测试

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

# 本地测试
echo "Hello TFTP" > /tftpboot/test.txt
tftp localhost
tftp> get test.txt
tftp> quit
cat test.txt  # 应该看到 "Hello TFTP"

3.4 NFS服务搭建——共享根文件系统

NFS 比 TFTP 高级多了。它能让开发板直接把宿主机上的一个目录挂载成自己的根文件系统。调试驱动时,你改完代码、编译完,开发板重启就能用上新程序,不用反复烧写 Flash。

3.4.1 安装与配置

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

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

# 编辑导出配置文件
sudo vim /etc/exports

在 /etc/exports 中添加:

/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
重点: no_root_squash 这个选项很关键。不加的话,开发板上的 root 用户会被映射成 nobody,很多操作会权限不足。我曾经调试一个驱动,死活加载不了,最后发现是 NFS 权限问题。

3.4.2 启动与验证

# 导出共享目录
sudo exportfs -a

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

# 查看导出列表
showmount -e localhost

# 本地挂载测试
sudo mount -t nfs localhost:/srv/nfs/rootfs /mnt
ls /mnt  # 应该能看到内容
sudo umount /mnt

3.5 开发板端配置——U-Boot 启动参数

服务端搭好了,开发板也得配合。在 U-Boot 命令行里设置好环境变量,才能让 TFTP 和 NFS 跑起来。

环境变量 说明 示例值
serverip 宿主机的 IP 地址 192.168.1.100
ipaddr 开发板的 IP 地址 192.168.1.200
netmask 子网掩码 255.255.255.0
gateway 网关 192.168.1.1
bootcmd 自动启动命令 见下方示例

一个典型的 NFS 启动参数设置:

setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.200
setenv netmask 255.255.255.0
setenv gateway 192.168.1.1

# 从 TFTP 下载内核和设备树
setenv bootcmd 'tftp 0x42000000 zImage; tftp 0x43000000 imx6ull.dtb; bootz 0x42000000 - 0x43000000'

# NFS 根文件系统
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,proto=tcp rw ip=192.168.1.200'

saveenv
boot
调试小技巧: 第一次启动时,建议在 U-Boot 里手动敲命令,一行一行执行。看到哪一步卡住了,问题就定位在哪。等全部调通了,再写到 bootcmd 里让它自动跑。

3.6 常见问题与避坑指南

  • TFTP 超时: 检查防火墙。Ubuntu 默认开了 ufw,关掉或者放行 UDP 69 端口。sudo ufw disable 或者 sudo ufw allow 69/udp。
  • NFS 挂载失败: 检查 /etc/exports 语法,每行结尾不能有空格。用 exportfs -v 查看实际导出的路径和权限。
  • 交叉编译器报错 "No such file or directory": 大概率是 64 位系统缺少 32 位库。安装 sudo apt-get install lib32z1 lib32ncurses5 试试。
  • 开发板 ping 不通宿主机: 检查网线、交换机,确认桥接模式。在虚拟机里 ifconfig 看看 IP 是不是跟开发板在同一网段。
我的习惯: 每次搭建完环境,我都会写一个简单的测试脚本,把编译、传输、挂载全跑一遍。这样下次重装系统或者换电脑时,十分钟就能恢复环境,不用从头再踩一遍坑。

好了,环境搭到这步,你的开发“窝”就算建好了。后面写驱动、编译内核、调试应用,全都要依赖这套环境。别嫌麻烦,一次配好,后面省心。