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 命令一直超时,先 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下载到开发板的东西。
2.4 本章知识体系总览
下面这张图把整个环境搭建的流程和关系画出来了。你可以把它当作一张地图,每次遇到环境问题,先看看是哪个环节出了岔子。
这张图把整个环境拆成了三个区域。左边是PC开发机上的四个核心组件,中间是网络传输层,右边是开发板上的运行链路。箭头方向就是数据流动的方向。你每次遇到环境问题,先定位是哪个区域出了问题——是编译不过(左边),还是传不过去(中间),还是跑不起来(右边)。