Linux PCIe驱动开发调试与部署 - 第4节
4.1 交叉编译工具链的安装与配置
嵌入式 PCIe 驱动开发与 x86 原生开发最大的区别在于交叉编译。目标板(ARM64 / RISC-V / MIPS)无法直接运行 x86 编译器,必须在宿主机上安装交叉工具链,生成目标架构的可执行代码。
4.1.1 工具链选择与版本匹配
| 目标架构 | 推荐工具链前缀 | 典型包名 | 内核版本匹配要求 |
|---|---|---|---|
| ARM64 (aarch64) | aarch64-linux-gnu- |
gcc-aarch64-linux-gnu |
gcc ≥ 10.x,binutils ≥ 2.35 |
| ARM (32-bit) | arm-linux-gnueabihf- |
gcc-arm-linux-gnueabihf |
gcc ≥ 9.x,glibc ≥ 2.28 |
| RISC-V (64-bit) | riscv64-linux-gnu- |
gcc-riscv64-linux-gnu |
gcc ≥ 12.x,支持 Zicsr 扩展 |
经验法则:交叉工具链的 gcc 主版本号不应高于目标内核编译时使用的 gcc 版本超过 2 个主版本,否则可能触发内核构建脚本中的版本检查警告。
4.1.2 安装与验证步骤
- 安装工具链(以 Ubuntu 22.04 / ARM64 为例):
sudo apt update
sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
- 验证编译器可用性:
aarch64-linux-gnu-gcc --version
# 输出应显示版本号,且目标架构为 aarch64
- 测试交叉编译:
echo 'int main(){ return 0; }' | aarch64-linux-gnu-gcc -x c - -o /tmp/test_arm64
file /tmp/test_arm64
# 输出: ELF 64-bit LSB executable, ARM aarch64, ...
常见陷阱:不要使用发行版自带的
gcc-multilib 来交叉编译内核模块。Multilib 方案仅适用于用户态程序,内核模块必须使用纯交叉工具链。
4.2 内核源码树配置与构建
PCIe 驱动模块必须与目标内核的源码树严格对齐。源码树包含内核头文件、Kconfig 定义、Kbuild 规则以及编译好的 Module.symvers 符号表。
4.2.1 获取并解压内核源码
# 从 kernel.org 或板卡厂商 BSP 获取
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.8.tar.xz
tar -xf linux-6.6.8.tar.xz
cd linux-6.6.8
4.2.2 配置内核(三种方式)
| 配置方式 | 命令 | 适用场景 |
|---|---|---|
| 使用目标板 defconfig | make ARCH=arm64 defconfig |
板卡厂商提供标准配置 |
| 基于现有 .config 修改 | make ARCH=arm64 menuconfig |
需要裁剪或添加特定驱动 |
| 使用 vendor 预编译配置 | cp /path/to/vendor_config .config |
BSP 开发包自带配置 |
关键配置项:PCIe 驱动开发必须确保以下内核选项开启:
CONFIG_PCI=y、CONFIG_PCI_MSI=y、CONFIG_MODULES=y、CONFIG_MODVERSIONS=y
4.2.3 编译内核与生成符号表
# 设置交叉编译环境变量
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
# 编译内核(仅编译必要部分,节省时间)
make -j$(nproc) Image dtbs
# 编译全部模块
make -j$(nproc) modules
# 生成 Module.symvers(驱动模块编译时必须)
make modules_prepare
编译完成后,Module.symvers 文件会出现在内核源码根目录,其中记录了所有导出的内核符号及其 CRC 校验值。
加速技巧:如果只开发驱动模块而不修改内核本身,可以只执行
make modules_prepare 和 make scripts,无需完整编译内核镜像,可节省 70% 以上的时间。
4.3 Kbuild / Makefile 编写实战
Linux 内核使用 Kbuild 系统来管理模块编译。开发者只需编写一个简洁的 Makefile,Kbuild 会自动处理交叉编译、符号依赖和模块安装。
4.3.1 最小驱动 Makefile 模板
# Makefile — PCIe 驱动模块编译
# 目标架构: ARM64, 交叉工具链: aarch64-linux-gnu-
# 模块名称
obj-m := pcie_sample.o
# 源文件(如果模块由多个 .c 文件组成)
pcie_sample-objs := main.o dma.o irq.o
# 外部内核源码树路径(必须指向已配置的内核)
KDIR ?= /home/user/linux-6.6.8
# 当前目录
PWD := $(shell pwd)
# 默认目标:编译模块
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
# 清理
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# 安装模块到临时目录
install:
$(MAKE) -C $(KDIR) M=$(PWD) modules_install INSTALL_MOD_PATH=/tmp/modules
4.3.2 交叉编译环境变量传递
Kbuild 会自动读取环境变量 ARCH 和 CROSS_COMPILE。也可以在 make 命令行中显式传递:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4
如果内核源码树已经通过 make ARCH=arm64 defconfig 配置过,Kbuild 会从 .config 中读取架构信息,但 CROSS_COMPILE 必须手动指定。
4.3.3 Kbuild 常用变量速查表
| 变量名 | 含义 | 示例值 |
|---|---|---|
obj-m |
需要编译为模块的目标文件列表 | pcie_drv.o |
obj-y |
需要编译并链接进内核的目标文件 | pcie_core.o |
ccflags-y |
传递给 C 编译器的额外标志 | -DDEBUG -O2 |
ldflags-y |
传递给链接器的额外标志 | --no-warn-rwx-segments |
KBUILD_EXTRA_SYMBOLS |
外部模块的符号表路径 | /path/to/other/Module.symvers |
4.3.4 多文件模块与依赖管理
当 PCIe 驱动包含多个源文件时,使用 <模块名>-objs 语法:
obj-m := pcie_complex.o
pcie_complex-objs := pcie_init.o pcie_probe.o pcie_dma.o pcie_msi.o
Kbuild 会自动推导头文件依赖(.d 文件),修改头文件后只需重新执行 make,无需手动清理。
注意:不要使用
obj-m += pcie_init.o 这种写法。Kbuild 要求模块目标必须以 .o 结尾,且必须通过 -objs 或 -y 列出源文件。
4.4 编译与部署流程
4.4.1 完整编译命令
# 设置环境(可写入 ~/.bashrc 避免重复输入)
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
export KDIR=/home/user/linux-6.6.8
# 进入驱动源码目录
cd ~/pcie_driver/
# 编译模块
make -j4
# 检查编译产物
ls -lh *.ko
# 输出: pcie_sample.ko (ELF 64-bit LSB relocatable, ARM aarch64)
4.4.2 部署到目标板
# 通过 scp 传输到目标板
scp pcie_sample.ko root@192.168.1.100:/root/
# 在目标板上加载模块
ssh root@192.168.1.100 "insmod /root/pcie_sample.ko"
# 验证加载
ssh root@192.168.1.100 "lsmod | grep pcie_sample"
4.4.3 常见编译错误与解决
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Cannot use CONFIG_CC_VERSION_TEXT |
内核源码未配置 | 执行 make ARCH=arm64 defconfig |
No rule to make target 'modules_prepare' |
内核未执行 prepare 阶段 | 执行 make ARCH=arm64 modules_prepare |
Unknown symbol pci_enable_device (err 0) |
Module.symvers 缺失或不匹配 | 重新编译内核并生成符号表 |
relocation truncated to fit |
内核模块地址空间不足 | 检查 CONFIG_ARM64_MODULE_PLTS=y |
4.5 环境搭建整体流程
下图展示了从宿主机到目标板的完整工具链与编译流程:
4.6 环境验证清单
完成环境搭建后,使用以下清单逐项确认:
- 工具链验证:
aarch64-linux-gnu-gcc -v输出正确目标架构 - 内核配置验证:
grep CONFIG_PCI .config返回y - 符号表验证:
head -5 Module.symvers显示有效符号条目 - 编译验证:
make -j4无错误,生成.ko文件 - 目标板加载验证:
insmod成功,dmesg无异常
推荐工作流:将上述验证步骤写入
check_env.sh 脚本,每次切换内核版本或工具链后自动执行,避免因环境不一致导致的调试陷阱。