2. 嵌入式Linux驱动开发环境搭建:交叉编译工具链、TFTP/NFS服务配置、内核源码树构建

做嵌入式驱动开发,跟搞纯Linux应用开发完全是两码事。你想想看,你的代码最终要跑在ARM核上,但你的编辑器、编译器却跑在x86的PC上。这就引出了第一个核心概念——交叉编译。

我个人习惯把环境搭建分成三个独立但又互相依赖的模块:工具链(怎么编译)、服务(怎么下载和调试)、源码树(编译成什么)。下面我一个个拆开讲。

2.1 交叉编译工具链:让x86 PC为ARM芯片编译代码

说白了,交叉编译工具链就是一套能在PC上运行、但生成ARM可执行文件的编译器、链接器和库。RK3568是64位ARMv8架构,所以我们得用aarch64-linux-gnu-前缀的工具链。

2.1.1 获取工具链的两种方式

方式 命令/来源 适用场景
apt直接安装 sudo apt install gcc-aarch64-linux-gnu 快速验证、学习环境
Rockchip官方SDK 从prebuilts/gcc/linux-x86/aarch64/目录获取 项目开发、与内核版本严格对齐

我在项目中遇到过一个问题:用apt装的gcc版本太新,编译老版本内核时直接报了一堆语法错误。后来我改用Rockchip SDK里自带的gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu,一次通过。嗯,这里要注意——工具链版本必须与内核版本匹配,不是越新越好。

2.1.2 验证工具链是否可用

装完之后,别急着写代码。先跑个最简单的测试:

# 查看编译器版本
aarch64-linux-gnu-gcc --version

# 编译一个空文件,测试链接器
echo 'int main(){return 0;}' > test.c
aarch64-linux-gnu-gcc -o test test.c
file test
# 输出应该包含 "ELF 64-bit LSB executable, ARM aarch64"
我的小技巧: 把工具链路径加到 ~/.bashrc 里,设置 export CROSS_COMPILE=aarch64-linux-gnu-。这样后面编译内核、uboot时就不用每次都敲一长串前缀了。

2.2 TFTP/NFS服务配置:让开发板与PC无缝对接

环境搭好了,代码也编译了。怎么把它弄到开发板上跑?

早期我搞单片机开发时,都是插拔SD卡、用读卡器拷文件。但做Linux驱动开发,一天可能要烧写几十次内核,插拔卡太慢了。这时候就需要TFTP和NFS这两个网络服务。

2.2.1 TFTP:快速下载内核镜像

TFTP用于在uboot阶段把内核镜像从PC下载到开发板的内存里。配置很简单:

# 安装TFTP服务器
sudo apt install tftpd-hpa

# 修改配置文件 /etc/default/tftpd-hpa
TFTP_DIRECTORY="/srv/tftp"    # 文件存放目录
TFTP_OPTIONS="--secure --create"

# 重启服务
sudo systemctl restart tftpd-hpa

# 把内核镜像放进去
sudo cp arch/arm64/boot/Image /srv/tftp/

然后在开发板的uboot命令行里:

setenv serverip 192.168.1.100   # PC的IP
setenv ipaddr 192.168.1.10     # 开发板的IP
tftp 0x02000000 Image          # 下载到内存地址0x02000000
booti 0x02000000 - 0x01f00000  # 启动内核
我曾经踩过的坑: TFTP默认端口是69,很多公司内网防火墙会拦截这个端口。如果你发现 tftp 命令一直超时,先 ping 一下确认网络通不通,再检查防火墙规则。我曾经花了一下午才发现是防火墙的问题。

2.2.2 NFS:挂载根文件系统

NFS的作用更大——它让开发板通过网络挂载PC上的一个目录作为根文件系统。这样你修改了驱动代码,在PC上重新编译,开发板重启后就能直接用新驱动,完全不用烧写。

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

# 编辑 /etc/exports,添加共享目录
/home/user/rk3568/rootfs *(rw,sync,no_subtree_check,no_root_squash)

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

# 在uboot中设置内核启动参数
setenv bootargs 'root=/dev/nfs nfsroot=192.168.1.100:/home/user/rk3568/rootfs rw ip=192.168.1.10:192.168.1.100::255.255.255.0::eth0:off'

这里有个关键点:no_root_squash 参数一定要加。不加的话,开发板上的root用户会被映射成nobody,很多驱动设备节点创建时会报权限错误。

2.3 内核源码树构建:理解编译体系

环境有了,服务配好了,接下来要面对的就是内核源码树。我第一次看内核目录结构时,说实话有点懵——几千个文件夹,几万个文件,从哪下手?

其实你不需要全部搞懂。做驱动开发,你只需要关注几个核心目录:

目录 内容 驱动开发者关注度
arch/arm64/ ARM64体系结构相关代码,包括启动、中断、内存管理 ⭐⭐⭐
drivers/ 所有设备驱动,按子系统分目录(gpio、i2c、spi、drm等) ⭐⭐⭐⭐⭐
include/linux/ 内核头文件,驱动开发常用的API声明都在这里 ⭐⭐⭐⭐
Documentation/devicetree/ 设备树绑定文档,写dts时必须参考 ⭐⭐⭐⭐
arch/arm64/boot/dts/rockchip/ RK3568的设备树源文件 ⭐⭐⭐⭐⭐

2.3.1 配置和编译内核

拿到内核源码后,第一步是配置。RK3568官方SDK通常会提供一个默认配置:

# 加载RK3568的默认配置
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_linux_defconfig

# 如果要做个性化配置,用menuconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

# 编译内核
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image dtbs

编译完成后,在 arch/arm64/boot/ 下会生成 Image(内核镜像)和 dts/rockchip/ 下的 .dtb 文件(设备树二进制)。这两个文件就是你要通过TFTP下载到开发板的东西。

核心思路: 驱动开发环境搭建的本质,就是建立一条「PC编译 → 网络传输 → 开发板运行」的流水线。工具链负责编译,TFTP/NFS负责传输,内核源码树定义了编译的目标。这三者缺一不可。

2.4 本章知识体系总览

下面这张图把整个环境搭建的流程和关系画出来了。你可以把它当作一张地图,每次遇到环境问题,先看看是哪个环节出了岔子。

嵌入式Linux驱动开发环境搭建 - 知识体系 PC开发机 (x86) 交叉编译工具链 aarch64-linux-gnu-gcc 内核源码树 drivers/ arch/arm64/ TFTP服务器 /srv/tftp/Image NFS服务器 /etc/exports 共享 网络传输层 TFTP: 下载内核镜像 NFS: 挂载根文件系统 以太网 (UDP/TCP) RK3568开发板 (ARM64) U-Boot引导 tftp 0x02000000 Image Linux内核 booti 启动 设备驱动模块 insmod / modprobe 根文件系统 (NFS) /dev /lib /etc 核心流水线:PC编译 → 网络传输 → 开发板运行

这张图把整个环境拆成了三个区域。左边是PC开发机上的四个核心组件,中间是网络传输层,右边是开发板上的运行链路。箭头方向就是数据流动的方向。你每次遇到环境问题,先定位是哪个区域出了问题——是编译不过(左边),还是传不过去(中间),还是跑不起来(右边)。

我的建议: 刚开始搭建环境时,不要追求一步到位。先搞定工具链,编译一个hello world跑通;再配TFTP,把内核下载下去;最后搞NFS。每完成一步就验证一步,这样出了问题能快速定位。我曾经见过有人一口气配完所有服务,结果启动时黑屏,排查了三天才发现是NFS的权限配置漏了一个参数。