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 安装步骤简述

  1. 下载 Ubuntu 20.04 LTS 镜像(官网或国内镜像站)
  2. 创建虚拟机,挂载 ISO 启动
  3. 安装过程中选择「最小安装」,省掉 LibreOffice 等无关软件
  4. 安装完成后,执行 sudo apt update && sudo apt upgrade -y
  5. 安装必要工具: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 本章知识体系总览

下面这张图把整个开发环境的核心流程串起来了,你可以对照着检查自己哪一步还没配好:

驱动开发环境搭建 — 核心流程 PC 虚拟机 Ubuntu 20.04 LTS 交叉编译工具链 arm-linux-gnueabihf-gcc TFTP 服务 传内核 + 设备树 NFS 服务 挂载根文件系统 ARM 开发板 U-Boot → 内核 → 文件系统 编译 内核镜像 驱动模块 下载内核 挂载文件系统 💡 开发效率关键 • 编译一次只需 30 秒 • 网络启动只需 10 秒 • 无需反复烧写 Flash

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 脚本已经用了三年,省了不少排查时间。