2. 开发环境搭建:交叉编译工具链安装、TFTP/NFS服务器配置、QEMU模拟环境搭建
说实话,嵌入式Linux开发的第一步,往往就把不少人卡住了。不是代码多难写,而是环境搭得让人头疼。我刚开始做这行时,光配一个交叉编译工具链就折腾了两天——版本不对、路径写错、库文件缺失,各种问题轮着来。后来我养成了一个习惯:先把环境理清楚,再动手干活。这一章,我就带你把这些基础环境一一搞定。
本章核心目标:搭建一套完整的嵌入式Linux开发环境,包括交叉编译工具链、TFTP/NFS服务器以及QEMU模拟器。这套环境将贯穿整个课程,后续所有实验都基于此。
2.1 交叉编译工具链:为什么需要它?
你想想看,你的开发机是x86架构,而目标板可能是ARM、RISC-V或MIPS。x86上编译出来的程序,ARM板子根本跑不了。这时候就需要交叉编译工具链——它运行在x86上,但生成的目标代码是ARM的。
说白了,交叉编译工具链就是一套“跨架构的编译器+链接器+库”的组合。我习惯把它安装在 /opt/toolchains/ 目录下,方便管理。
2.1.1 获取工具链
常用的获取方式有三种,我列个表给你对比一下:
| 方式 | 来源 | 适用场景 | 我的建议 |
|---|---|---|---|
| 芯片厂商提供 | NXP、TI、ST等官方SDK | 量产项目、硬件固定 | 最稳定,优先使用 |
| Linaro预编译包 | linaro.org | ARM架构通用开发 | 学习阶段首选 |
| Buildroot/Yocto生成 | 自己编译 | 定制化需求、裁剪优化 | 进阶玩法,后面章节会讲 |
我个人推荐学习阶段用Linaro的预编译包。以ARM 32位为例,下载并安装:
# 下载ARM 32位工具链(以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
# 解压到指定目录
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
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 验证安装
arm-linux-gnueabihf-gcc --version
小技巧:我习惯在 ~/.bashrc 里写一个函数,方便切换不同工具链。比如 setarm()、setaarch64(),一键切换环境变量,省得每次手动改。
2.1.2 验证工具链是否可用
写一个简单的Hello World,交叉编译一下:
# 编写测试代码
echo '#include <stdio.h>
int main() {
printf("Hello Embedded Linux!\n");
return 0;
}' > hello.c
# 交叉编译
arm-linux-gnueabihf-gcc -o hello hello.c
# 查看生成的文件类型
file hello
# 输出:hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3
看到 ARM 字样,说明工具链工作正常。嗯,这里要注意:如果 file 命令显示的是 x86-64,那说明你用的是本地gcc,不是交叉编译器——检查一下PATH顺序。
我曾经踩过的坑:有一次我同时装了多个工具链版本,结果PATH里旧版本在前,编译出来的程序在新内核上跑不起来。后来我养成了习惯:每次编译前先 which ${CROSS_COMPILE}gcc 确认一下用的是哪个。
2.2 TFTP服务器:给板子传文件
嵌入式开发中,板子通常没有显示器、键盘,你怎么把内核镜像、设备树文件传过去?TFTP(Trivial File Transfer Protocol)就是干这个的。它简单、轻量,适合在局域网内传输小文件。
2.2.1 安装与配置TFTP服务器
以Ubuntu 20.04为例:
# 安装tftp-hpa服务器和客户端
sudo apt-get update
sudo apt-get install tftp-hpa tftpd-hpa
# 创建TFTP根目录
sudo mkdir -p /tftpboot
sudo chmod 777 /tftpboot
# 修改配置文件 /etc/default/tftpd-hpa
sudo vim /etc/default/tftpd-hpa
配置文件内容如下:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
参数说明:
-l:以独立服务模式运行-c:允许客户端上传文件(调试时很有用)-s:将根目录切换到TFTP_DIRECTORY
# 重启服务
sudo systemctl restart tftpd-hpa
sudo systemctl enable tftpd-hpa
# 测试:在/tftpboot下放一个文件,从本机获取
echo "test" > /tftpboot/test.txt
tftp localhost -c get test.txt
避坑指南:防火墙可能会拦截TFTP的69端口。如果板子连不上,先 sudo ufw status 检查一下,或者直接 sudo ufw disable 临时关掉防火墙测试。
2.3 NFS服务器:让板子共享开发机文件
TFTP适合传单个文件,但调试阶段你经常需要频繁修改程序、更新库文件。每次都用TFTP传,效率太低了。NFS(Network File System)可以让你把开发机上的目录直接挂载到板子上,板子上的程序就像在本地运行一样。
2.3.1 安装与配置NFS服务器
# 安装NFS内核服务器
sudo apt-get install nfs-kernel-server
# 创建共享目录
sudo mkdir -p /srv/nfs/rootfs
sudo chmod 777 /srv/nfs/rootfs
# 编辑导出配置文件 /etc/exports
sudo vim /etc/exports
添加以下内容:
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
参数含义:
*:允许所有客户端访问(生产环境建议限定IP)rw:读写权限sync:同步写入,保证数据一致性no_root_squash:允许客户端root用户保持root权限(调试时很有用)
# 重启NFS服务
sudo systemctl restart nfs-kernel-server
# 验证共享是否生效
showmount -e localhost
输出应该显示:/srv/nfs/rootfs *
2.3.2 在QEMU或板子上挂载NFS
假设开发机IP是192.168.1.100,在板子的U-Boot或内核启动参数中设置:
root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs rw ip=dhcp
板子启动后,根文件系统就会直接从开发机的 /srv/nfs/rootfs 目录加载。你修改这个目录下的任何文件,板子都能立即看到——这就是我为什么说NFS是调试利器。
我曾经踩过的坑:有一次我忘了加 no_root_squash,结果板子上用root用户创建的文件,在开发机上变成了nobody用户,权限乱成一团。调试阶段,no_root_squash 一定要加上。
2.4 QEMU模拟环境:没有板子也能玩
不是每个人都有开发板。就算有,反复烧写、重启也很浪费时间。QEMU可以模拟一个完整的ARM/ RISC-V开发板,让你在PC上直接运行内核和根文件系统。我个人觉得,QEMU是学习阶段性价比最高的工具。
2.4.1 安装QEMU
# 安装QEMU及相关工具
sudo apt-get install qemu-system-arm qemu-user-static
# 验证安装
qemu-system-arm --version
2.4.2 用QEMU启动一个最小系统
这里我用一个ARM的vexpress-a9开发板为例。先准备内核镜像和根文件系统:
# 下载预编译的内核和设备树(也可以用你自己编译的)
wget https://.../zImage
wget https://.../vexpress-v2p-ca9.dtb
# 下载根文件系统(比如用Buildroot生成的rootfs.ext4)
# 启动QEMU
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-drive file=rootfs.ext4,format=raw,if=sd \
-append "console=ttyAMA0 root=/dev/mmcblk0 rw" \
-nographic
启动后,你会看到一个完整的Linux命令行界面。嗯,这就是你的“虚拟开发板”。
我的习惯:我会写一个启动脚本 run_qemu.sh,把上面的命令保存起来。每次调试时只需要 ./run_qemu.sh,不用每次都敲一长串参数。
2.4.3 QEMU网络配置:让虚拟板子上网
默认情况下,QEMU的虚拟板子无法访问外网。我们需要配置网络:
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-drive file=rootfs.ext4,format=raw,if=sd \
-append "console=ttyAMA0 root=/dev/mmcblk0 rw ip=dhcp" \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-device,netdev=net0 \
-nographic
这里 hostfwd=tcp::2222-:22 把开发机的2222端口映射到虚拟板子的22端口(SSH)。之后你就可以用 ssh -p 2222 root@localhost 登录虚拟板子了。
2.5 本章知识体系总览
下面这张图把本章的核心内容串起来了,你可以对照着检查自己是否都理解了:
2.6 环境验证清单
搭建完成后,我建议你对照这个清单逐项检查,确保每个环节都正常:
| 检查项 | 验证方法 | 预期结果 |
|---|---|---|
| 交叉编译工具链 | arm-linux-gnueabihf-gcc --version |
显示版本号,无报错 |
| 交叉编译测试 | 编译hello.c并用 file 查看 |
显示ARM架构 |
| TFTP服务 | tftp localhost -c get test.txt |
成功下载文件 |
| NFS服务 | showmount -e localhost |
显示共享目录 |
| QEMU启动 | 运行启动脚本 | 进入Linux命令行 |
| QEMU网络 | 在QEMU内 ping 8.8.8.8 |
网络可达 |