1、Linux驱动开发环境搭建:交叉编译工具链配置、内核源码树构建、NFS与TFTP调试环境搭建
说实话,很多初学者一上来就急着写代码,结果编译都过不了,或者烧进去跑不起来。我当年也踩过这个坑——花了两周调一个驱动,最后发现是工具链版本不对。所以,环境搭建这事,真不能马虎。
这一章,咱们就把开发环境从头捋一遍。说白了,就是三件事:交叉编译工具链、内核源码树、网络调试环境。搞定了这些,后面写驱动才能顺风顺水。
1.1 交叉编译工具链配置
为什么需要交叉编译?你想想看,你的开发机是x86架构,但目标板是ARM(或者RISC-V、MIPS)。x86上编译出来的程序,ARM跑不了。所以我们需要一套在x86上运行、但能生成ARM机器码的编译器——这就是交叉编译工具链。
1.1.1 获取工具链
我个人习惯用Linaro提供的工具链,稳定且社区支持好。以ARM Cortex-A系列为例:
# 下载ARM GCC工具链
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
# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 添加环境变量
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
~/.bashrc 或 /etc/profile 里,避免每次开终端都要 export 一遍。
1.1.2 验证工具链
配置完以后,先别急着用。验证一下能不能正常工作:
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc -v 2>&1 | grep Target
输出里应该能看到 Target: arm-linux-gnueabihf 字样。嗯,这里要注意:如果显示的是 x86_64 或者 i686,说明你用的还是本地编译器,路径没配对。
arm-linux-gnueabi(软浮点)工具链,但内核配置了硬浮点。两种ABI不兼容,跑起来必崩。所以,工具链的ABI一定要跟内核匹配。
1.1.3 工具链的组成
一套完整的交叉工具链包含这些组件:
| 组件 | 说明 | 常见命令 |
|---|---|---|
| 编译器 | 将C/C++源码编译为汇编 | arm-linux-gnueabihf-gcc |
| 汇编器 | 将汇编转为机器码 | arm-linux-gnueabihf-as |
| 链接器 | 将目标文件链接为可执行文件 | arm-linux-gnueabihf-ld |
| 库 | glibc/uClibc等C运行库 | libc.so.6 |
| 调试器 | 远程调试目标板程序 | arm-linux-gnueabihf-gdb |
1.2 内核源码树构建
驱动开发离不开内核源码。你写的驱动模块,需要依赖内核头文件和编译框架。说白了,你得先有一套完整的内核源码树,才能编译出.ko文件。
1.2.1 获取内核源码
我建议直接从kernel.org下载主线内核,或者从芯片厂商(如NXP、TI、Rockchip)的官方仓库拉取。以Linux 5.10 LTS为例:
# 下载内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
# 解压
tar -xvf linux-5.10.100.tar.xz -C /home/developer/kernel/
# 进入源码目录
cd /home/developer/kernel/linux-5.10.100
1.2.2 配置内核
内核配置有两种方式:
- 使用厂商提供的默认配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xxx_defconfig - 手动配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
我个人习惯先用厂商的defconfig,再根据需要微调。比如:
# 以TI AM335x为例
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- omap2plus_defconfig
# 然后微调
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
1.2.3 编译内核与模块
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage -j4
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# 编译内核模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j4
# 安装模块到目标目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/home/developer/rootfs modules_install
你想想看,如果每次改一行驱动代码都要重新编译整个内核,那效率得多低?所以,我们通常只编译内核一次,后面只编译模块。
1.3 NFS与TFTP调试环境搭建
驱动开发最痛苦的是什么?每次改完代码都要烧写、重启、插拔SD卡。太慢了。我建议用网络文件系统(NFS)和TFTP来加速调试。
1.3.1 TFTP服务器配置
TFTP用来传输内核镜像和设备树。Bootloader(如U-Boot)通过TFTP从开发机下载这些文件到板子的内存中运行。
# 安装TFTP服务器(Ubuntu/Debian)
sudo apt-get install tftpd-hpa
# 配置TFTP目录
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
# 编辑配置文件 /etc/default/tftpd-hpa
# 内容如下:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure"
# 重启服务
sudo systemctl restart tftpd-hpa
然后把编译好的zImage和dtb文件拷贝到 /srv/tftp 目录下:
cp arch/arm/boot/zImage /srv/tftp/
cp arch/arm/boot/dts/am335x-boneblack.dtb /srv/tftp/
1.3.2 NFS服务器配置
NFS用来挂载根文件系统。板子启动后,通过NFS访问开发机上的rootfs,这样你修改驱动模块后,板子立刻就能用上新的.ko文件。
# 安装NFS服务器
sudo apt-get install nfs-kernel-server
# 编辑 /etc/exports,添加共享目录
# 内容如下:
/home/developer/rootfs *(rw,sync,no_subtree_check,no_root_squash)
# 重启NFS服务
sudo systemctl restart nfs-kernel-server
no_root_squash 这个选项很重要。如果不加,板子上的root用户会被映射为nobody,导致很多操作权限不足。我曾经因为这个配置,折腾了一下午才发现问题。
1.3.3 U-Boot启动参数设置
在板子的U-Boot命令行中,设置从TFTP和NFS启动:
# 设置IP地址(根据你的网络环境调整)
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
# 从TFTP下载内核和设备树
tftp 0x82000000 zImage
tftp 0x88000000 am335x-boneblack.dtb
# 设置内核启动参数,通过NFS挂载根文件系统
setenv bootargs console=ttyO0,115200 root=/dev/nfs nfsroot=192.168.1.10:/home/developer/rootfs rw ip=192.168.1.100:192.168.1.10:192.168.1.1:255.255.255.0::eth0:off
# 启动内核
bootz 0x82000000 - 0x88000000
嗯,这里要注意:IP地址、网关、NFS路径这些参数,一定要跟你实际网络环境一致。否则板子起不来。
1.3.4 调试流程
环境搭好以后,调试流程就变成了这样:
- 在开发机上修改驱动源码
- 交叉编译生成.ko文件
- 拷贝到NFS共享目录下的对应位置
- 在板子上用
insmod或modprobe加载模块 - 用
dmesg查看内核日志 - 发现问题,回到第1步
整个过程不需要烧写、不需要重启。效率提升不是一点半点。
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C /home/developer/kernel/linux-5.10.100 M=$(pwd) modules
cp *.ko /home/developer/rootfs/home/root/drivers/
echo "Done! Run 'modprobe -r mydriver && modprobe mydriver' on target."
小结
这一章的内容,说白了就是三件事:
- 交叉编译工具链——让你在x86上编译出ARM能跑的程序
- 内核源码树——驱动模块的编译依赖和框架
- NFS+TFTP——让调试不再需要反复烧写
环境搭好了,后面写驱动才能心无旁骛。下一章,咱们开始真正接触驱动架构——字符设备驱动的基础框架。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。