1. Linux 内核驱动开发环境搭建
嵌入式 Linux 驱动开发的第一步是构建一个完整、可复现的编译环境。与普通应用程序不同,内核模块必须与目标内核的源码树和编译配置严格对齐。本章将依次完成交叉编译工具链配置、内核源码树构建以及模块编译基础三个核心环节。
1.1 交叉编译工具链配置
交叉编译工具链运行在宿主机(x86_64 PC)上,生成目标板(ARM64 / ARM32)的可执行代码。工具链的选择直接影响内核模块的 ABI 兼容性。
| 组件 | 说明 | 典型包名(ARM64) |
|---|---|---|
| gcc | C 编译器,生成目标架构指令 | gcc-aarch64-linux-gnu |
| binutils | 汇编器、链接器、objdump 等 | binutils-aarch64-linux-gnu |
| glibc / uclibc | C 运行时库(内核模块不直接链接,但影响头文件路径) | libc6-dev-arm64-cross |
| kernel headers | 目标内核的 asm 和 linux 头文件 |
linux-libc-dev-arm64-cross |
推荐做法:使用 Linaro 或 ARM 官方预编译工具链,避免自行从源码构建。安装后确认
aarch64-linux-gnu-gcc --version 输出正常。
环境变量设置
将工具链路径加入 PATH,并导出交叉编译前缀:
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
export PATH=/opt/toolchains/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
常见错误:忘记设置
ARCH 或 CROSS_COMPILE 会导致内核构建系统使用宿主机 gcc,编译出的模块无法在目标板加载。
1.2 内核源码树构建
内核模块编译需要完整的内核源码树,且必须包含已配置的 .config 和编译生成的 Module.symvers。推荐使用与目标板完全相同的内核版本。
获取源码
# 从 kernel.org 下载
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.28.tar.xz
tar -xf linux-6.1.28.tar.xz
cd linux-6.1.28
配置内核
使用目标板厂商提供的 defconfig,或从目标板运行中的 /proc/config.gz 提取:
# 使用 defconfig(以 ARM64 为例)
make ARCH=arm64 defconfig
# 或从目标板复制
zcat /proc/config.gz > .config
make ARCH=arm64 olddefconfig
编译内核与模块导出
至少需要编译一次完整内核,以生成 vmlinux、System.map 和 Module.symvers:
make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_prepare
关键文件清单:
./.config— 内核配置选项./Module.symvers— 导出的符号 CRC 校验值./include/generated/autoconf.h— C 头文件形式的配置宏./arch/arm64/include/— 架构相关头文件
1.3 模块编译基础
内核模块使用 kbuild 系统编译,通过 Makefile 描述源文件和编译选项。以下是一个最小模块示例。
模块源码:hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void)
{
pr_info("Hello, Embedded Linux Driver!\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Goodbye, Embedded Linux Driver!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Expert");
MODULE_DESCRIPTION("A minimal kernel module example");
Makefile
obj-m := hello.o
KDIR := /home/user/linux-6.1.28
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm64 \
CROSS_COMPILE=aarch64-linux-gnu- modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm64 \
CROSS_COMPILE=aarch64-linux-gnu- clean
编译与部署
make
# 生成 hello.ko
# 复制到目标板
scp hello.ko root@192.168.1.100:/root/
# 在目标板加载/卸载
insmod hello.ko
rmmod hello.ko
dmesg | tail -5
避坑提示:目标板内核版本与源码树版本必须完全一致,否则
insmod 会报 Invalid module format 或 Unknown symbol 错误。
1.4 环境搭建流程总览
1.5 常见问题与排查
| 现象 | 根因 | 解决方案 |
|---|---|---|
cc1: error: unknown target CPU |
工具链与 ARCH 不匹配 |
检查 CROSS_COMPILE 前缀是否正确 |
make: *** No rule to make target 'modules_prepare' |
内核源码未完整解压或路径错误 | 确认 KDIR 指向已配置的内源码根目录 |
insmod: ERROR: could not insert module |
内核版本或 .config 不一致 |
在目标板运行 uname -r 对比源码版本 |
modprobe: FATAL: Module not found |
模块路径未在 /lib/modules/ 下 |
使用 insmod /full/path/hello.ko 或执行 depmod |
调试技巧:在 Makefile 中添加
V=1 可输出完整编译命令,便于定位头文件路径或链接错误:make V=1。