2. Linux驱动开发环境搭建

嵌入式 Linux 驱动开发与普通桌面开发最大的区别在于交叉编译。目标板(ARM、RISC-V 等)资源有限,无法运行完整的编译工具链,因此需要在性能强大的宿主机(x86_64)上编译出目标板可执行的二进制模块。本章将系统讲解交叉编译工具链的安装、内核源码树的配置以及模块 Makefile 的编写规范。

学习目标:完成本章后,你应能独立搭建一套可用的嵌入式驱动开发环境,并成功编译第一个“Hello World”内核模块。

2.1 交叉编译工具链

交叉编译工具链由 binutils(汇编器、链接器)、gcc(编译器)、glibc/uclibc(C 库)以及 linux kernel headers 组成。工具链的命名规则通常为 arch-vendor-os-abi,例如 arm-linux-gnueabihf-。

2.1.1 工具链获取方式

方式 说明 适用场景
芯片厂商 SDK NXP Yocto、TI Processor SDK、ST SDK 等 量产项目,与 BSP 版本严格绑定
Linaro 预编译工具链 gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 快速原型验证,社区广泛使用
Buildroot / Yocto 自编译 从源码构建完整工具链 深度定制,需要控制 C 库版本
发行版包管理器 apt install gcc-arm-linux-gnueabihf 简单测试,版本可能较旧

2.1.2 安装与验证

以 Linaro arm-linux-gnueabihf 为例,下载后解压并添加至 PATH:

# 解压
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/

# 添加环境变量(写入 ~/.bashrc)
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 验证
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-objdump --version
关键环境变量:ARCH 指定目标架构,CROSS_COMPILE 指定交叉编译工具链前缀。内核构建系统通过这两个变量自动选择正确的编译器。
交叉编译工具链结构 宿主机 x86_64 交叉工具链 目标板 ARM gcc binutils glibc kernel headers 工具链必须与目标内核版本匹配,否则模块加载时可能出现“Invalid module format”错误

2.2 内核源码树配置

驱动模块本质上是内核的一部分,编译时需要引用内核源码树中的头文件和 Makefile 规则。因此,必须准备一份与目标板运行内核版本一致的、已配置好的内核源码树。

2.2.1 获取内核源码

  • 从芯片厂商获取:最可靠,包含所有板级补丁和驱动。
  • 从 kernel.org 主线获取:适用于通用平台学习。
  • 从开发板社区获取:如 Raspberry Pi Linux、BeagleBoard Linux。

2.2.2 配置与编译内核

以 ARM 架构为例,配置内核的典型步骤:

# 清理
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

# 使用默认配置(以 vexpress 开发板为例)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig

# 菜单配置(可选,用于裁剪或添加驱动)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 编译内核与设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs -j4
注意:如果只编译外部模块,不需要完整编译内核,但必须执行 make modules_prepare 来生成 Module.symvers 和必要的目标文件。

2.2.3 内核源码树目录结构

目录 作用
arch/ 架构相关代码,如 arch/arm、arch/arm64
drivers/ 设备驱动子目录,如 char、net、i2c、spi
include/ 内核头文件,包括 uapi(用户空间 API)
scripts/ 构建脚本,如 modpost、kconfig 工具
Module.symvers 导出的符号版本信息,外部模块依赖此文件
内核源码树关键目录 Linux Kernel Source arch/ drivers/ include/ scripts/ Module.symvers 编译外部模块时,内核构建系统通过 KDIR 变量指向已配置的内核源码树 系统会自动使用 arch/、include/ 和 Module.symvers 完成符号解析

2.3 模块编译基础(Makefile)

Linux 内核模块的编译依赖内核构建系统(Kbuild)。开发者只需编写一个简洁的 Makefile,内核构建系统会自动处理交叉编译、依赖跟踪和模块链接。

2.3.1 最简单的模块 Makefile

# KDIR:指向已配置的内核源码树路径
KDIR ?= /home/user/linux-5.10

# 当前目录
PWD := $(shell pwd)

# 目标模块名称(不带 .ko 后缀)
obj-m := hello.o

# 如果模块由多个源文件组成,使用模块名-y
# hello-y := file1.o file2.o

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

2.3.2 带交叉编译的 Makefile

当需要交叉编译时,在 make 命令行传入 ARCH 和 CROSS_COMPILE:

# 编译命令
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

也可以在 Makefile 中直接指定:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
KDIR ?= /home/user/linux-5.10

obj-m := hello.o

all:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean

2.3.3 Makefile 关键变量说明

变量 含义 示例
obj-m 需要编译成模块的目标文件列表 obj-m := mydrv.o
obj-y 需要编译进内核的目标文件列表 obj-y := mydrv.o
ccflags-y 附加的编译标志 ccflags-y := -DDEBUG -I$(src)/include
ldflags-y 附加的链接标志 ldflags-y := --strip-debug
KBUILD_EXTRA_SYMBOLS 额外符号文件路径,用于模块间符号依赖 export KBUILD_EXTRA_SYMBOLS=/path/Module.symvers

2.3.4 编译与加载测试

# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

# 查看生成的模块
file hello.ko
# 输出: ELF 32-bit LSB relocatable, ARM, version 1

# 将模块拷贝到目标板,加载
insmod hello.ko

# 查看模块信息
modinfo hello.ko

# 卸载
rmmod hello
常见错误:
  • Invalid module format — 内核版本或配置不匹配,重新编译内核或模块。
  • Unknown symbol — 模块依赖的符号未导出,检查 Module.symvers 或使用 KBUILD_EXTRA_SYMBOLS。
  • Permission denied — 目标板文件系统权限问题,使用 chmod +x 或 root 用户。
模块编译流程 编写 .c 源文件 编写 Makefile make 编译 生成 .ko 文件 内核构建系统:-C $(KDIR) M=$(PWD) modules 解析 Kconfig → 生成 .o → 链接为 .ko → 校验符号版本 hello.ko hello.mod.c Module.symvers .o 目标文件

2.4 本章小结

  • 交叉编译工具链是嵌入式驱动开发的基石,ARCH 和 CROSS_COMPILE 是核心环境变量。
  • 内核源码树必须与目标板运行内核版本一致,且至少执行过 make modules_prepare。
  • 模块 Makefile 通过 obj-m 指定目标,借助 -C $(KDIR) M=$(PWD) 调用内核构建系统。
  • 编译产物 .ko 文件是 ELF 格式的可重定位目标文件,通过 insmod 加载到内核空间。
课后练习:编写一个最简单的 hello.c 模块,使用本章的 Makefile 模板,在 QEMU vexpress 或真实 ARM 开发板上完成交叉编译、加载和卸载测试。