2. 开发环境搭建:安装Linux虚拟机、配置交叉编译工具链、搭建TFTP/NFS服务
说实话,驱动开发这件事,七分靠环境,三分靠代码。我见过太多新手把时间耗在环境问题上,真正写驱动的时间反而没多少。这一章,咱们就把吃饭的家当一次性备齐。
你想想看,驱动是要跑在目标板上的,但代码却要在PC上写、编译、调试。这就涉及三个核心问题:在哪写、怎么编、如何传。下面我一个个拆开讲。
本章核心目标:搭建一套完整的驱动开发环境,实现「PC上写代码 → 交叉编译 → 通过网络加载到开发板」的闭环流程。
2.1 安装Linux虚拟机
我个人习惯用 Ubuntu 20.04 LTS 作为开发宿主机。为什么选它?因为大部分芯片厂商的 SDK 和文档都基于这个版本验证过,踩坑最少。
如果你用的是 Windows,别直接在 Windows 上装工具链,那会把自己逼疯。老老实实装个虚拟机,推荐 VMware Workstation 或 VirtualBox,都行。
2.1.1 虚拟机配置建议
| 配置项 | 建议值 | 说明 |
|---|---|---|
| CPU 核心数 | 4 核 | 编译内核时多核并行能省大量时间 |
| 内存 | 8 GB | 低于 4GB 编译大型项目会卡死 |
| 磁盘 | 80 GB(动态分配) | 内核源码 + 工具链 + 编译产物很占空间 |
| 网络 | 桥接模式 | 让虚拟机与开发板在同一网段 |
我的经验:磁盘千万别选「固定大小」,选「动态分配」。我第一次装虚拟机时给了 50GB 固定大小,结果后来装了个 SDK 就满了,又得重来。动态分配的话,用多少占多少,灵活得多。
2.1.2 安装步骤简述
- 下载 Ubuntu 20.04 LTS 镜像(官网或国内镜像站)
- 创建虚拟机,挂载 ISO 启动
- 安装过程中选择「最小安装」,省掉 LibreOffice 等无关软件
- 安装完成后,执行
sudo apt update && sudo apt upgrade -y - 安装必要工具:
sudo apt install build-essential git vim net-tools -y
嗯,这里要注意:装完系统后第一件事是配好网络。用 ip addr 查看 IP,确保能 ping 通外网。如果 ping 不通,八成是桥接网卡没选对。
2.2 配置交叉编译工具链
交叉编译是什么?说白了,就是在 PC(x86 架构)上编译出能在 ARM 开发板上运行的二进制文件。你想想看,开发板的 CPU 跑不动编译器,但 PC 可以。所以我们需要一套「跨架构」的编译工具。
2.2.1 获取工具链
以 ARM Cortex-A 系列为例,我常用 Linaro 提供的 gcc-arm-linux-gnueabihf 工具链。下载方式如下:
# 下载 ARM 32位工具链
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
# 解压到 /opt 目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 添加环境变量
echo 'export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin' >> ~/.bashrc
source ~/.bashrc
我曾经踩过的坑:工具链版本一定要跟芯片厂商的 Linux 内核版本匹配。比如你的内核是 4.19,用 gcc-9 编译大概率会报错。我有一回图省事用了最新版工具链,结果编译出来的内核模块 insmod 直接报「invalid module format」,折腾了两天才发现是工具链版本不兼容。
2.2.2 验证工具链
配置完成后,执行以下命令验证:
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc -v 2>&1 | grep Target
如果输出类似 Target: arm-linux-gnueabihf,说明工具链工作正常。我习惯再写个简单的 Hello World 测试一下:
# 编写测试文件
echo '#include <stdio.h>
int main() { printf("Hello, Driver World!\n"); return 0; }' > test.c
# 交叉编译
arm-linux-gnueabihf-gcc test.c -o test_arm
# 查看文件信息
file test_arm
# 输出应为:ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)
看到 ARM 字样就对了。这说明你编译出来的文件是 ARM 架构的,不是 x86 的。
2.2.3 工具链路径与 Makefile 配合
在实际驱动开发中,我们不会每次都手动敲交叉编译器路径。通常的做法是在内核源码的顶层 Makefile 中指定:
# 在 Makefile 开头添加
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
这样,编译内核或模块时,系统会自动调用交叉编译器。我个人习惯把这两行写到 ~/.bashrc 里,省得每个项目都配一遍:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
2.3 搭建TFTP/NFS服务
代码编译好了,怎么传到开发板上?两个主流方案:TFTP 传内核和设备树,NFS 挂载根文件系统。两者配合使用,开发效率能翻倍。
2.3.1 TFTP 服务搭建
TFTP 用于在 U-Boot 启动阶段快速下载内核镜像(zImage)和设备树(dtb)。它简单、轻量,但不安全——所以只适合开发环境。
# 安装 TFTP 服务端
sudo apt install tftpd-hpa -y
# 配置 TFTP 根目录
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
# 修改配置文件 /etc/default/tftpd-hpa
sudo vim /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
sudo systemctl status tftpd-hpa
# 测试:在 /srv/tftp 下放一个文件,然后在开发板 U-Boot 中执行:
# tftp 0x42000000 zImage
我的习惯:我会在 /srv/tftp 下按项目建子目录,比如 /srv/tftp/project_a/,这样多个项目不会互相干扰。另外,记得关闭防火墙或开放 69/UDP 端口,否则 TFTP 传文件会超时。
2.3.2 NFS 服务搭建
NFS 用于让开发板通过网络挂载 PC 上的目录作为根文件系统。这样你修改了驱动或应用程序,直接在 PC 上编译,开发板重启就能用新文件,不用反复烧写 Flash。
# 安装 NFS 服务端
sudo apt install nfs-kernel-server -y
# 创建共享目录
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)
参数说明:
| 参数 | 含义 |
|---|---|
rw |
允许读写 |
sync |
同步写入,保证数据一致性 |
no_subtree_check |
关闭子树检查,提升性能 |
no_root_squash |
允许开发板以 root 权限访问 |
重启 NFS 服务:
sudo systemctl restart nfs-kernel-server
sudo exportfs -v # 查看共享状态
我曾经踩过的坑:no_root_squash 这个参数一定要加。不加的话,开发板上的 root 用户会被映射成 nobody,导致你连 /dev 目录都写不了,驱动模块根本 insmod 不进去。我那次排查了整整一个下午,最后发现是权限问题,气得不行。
2.3.3 开发板启动参数配置
在 U-Boot 中设置以下环境变量,即可实现网络启动:
setenv serverip 192.168.1.100 # PC 的 IP
setenv ipaddr 192.168.1.50 # 开发板的 IP
setenv netmask 255.255.255.0
# TFTP 下载内核和设备树
setenv bootcmd 'tftp 0x42000000 zImage; tftp 0x43000000 board.dtb; bootz 0x42000000 - 0x43000000'
# NFS 挂载根文件系统
setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs rw ip=192.168.1.50'
saveenv
boot
这样,开发板上电后会自动从 TFTP 下载内核,然后通过 NFS 挂载根文件系统。整个过程不到 10 秒,比烧写 Flash 快太多了。
2.4 本章知识体系总览
下面这张图把整个开发环境的核心流程串起来了,你可以对照着检查自己哪一步还没配好:
2.5 环境验证清单
所有服务搭建完成后,我建议你按下面的清单逐项验证。每过一项打个勾,确保环境万无一失:
| 序号 | 验证项 | 验证方法 | 预期结果 |
|---|---|---|---|
| 1 | 虚拟机网络 | ping 8.8.8.8 |
能通外网 |
| 2 | 交叉编译器 | arm-linux-gnueabihf-gcc --version |
显示版本号 |
| 3 | 交叉编译测试 | 编译 test.c 并执行 file test_arm |
显示 ARM 架构 |
| 4 | TFTP 服务 | 在开发板 U-Boot 中执行 tftp 0x42000000 zImage |
下载成功 |
| 5 | NFS 服务 | 开发板挂载 mount -t nfs 192.168.1.100:/srv/nfs/rootfs /mnt |
挂载成功,可读写 |
| 6 | 完整启动 | 开发板上电,观察串口输出 | 进入系统 Shell |
我的建议:把这六项验证做成一个脚本,每次重装环境后跑一遍。我自己的 check_env.sh 脚本已经用了三年,省了不少排查时间。