1. Linux 驱动开发环境搭建
在开始编写字符设备驱动之前,必须搭建一套完整的交叉编译与内核模块开发环境。本章节将依次讲解交叉编译工具链的安装、内核源码树的配置以及模块编译 Makefile 的编写规范,为后续的中断处理与 ioctl 接口实现打下坚实基础。
环境概览:本课程基于 Ubuntu 22.04 LTS 主机,目标平台为 ARM Cortex-A7(树莓派 2B+),内核版本 Linux 5.10 LTS。所有操作均在此环境下验证。
1.1 交叉编译工具链安装
交叉编译工具链允许我们在 x86 主机上编译出 ARM 架构可执行的二进制文件。推荐使用 Linaro 提供的预编译工具链,稳定且社区支持广泛。
1.1.1 下载与解压
# 创建工作目录
mkdir -p ~/arm_toolchain && cd ~/arm_toolchain
# 下载 Linaro GCC 7.5.0 ARM 硬浮点工具链
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/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
# 查看工具链内容
ls gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/
1.1.2 环境变量配置
将工具链路径加入 PATH,并设置交叉编译前缀变量,方便后续 Makefile 引用。
# 编辑 ~/.bashrc 或 ~/.zshrc,追加以下内容
export PATH=$PATH:~/arm_toolchain/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
# 使配置生效
source ~/.bashrc
# 验证安装
arm-linux-gnueabihf-gcc --version
验证要点:执行
arm-linux-gnueabihf-gcc --version 应显示版本号 7.5.0。若提示“未找到命令”,请检查路径是否正确或重新加载 shell 配置。
1.1.3 工具链组件说明
| 组件名称 | 功能描述 | 典型用法 |
|---|---|---|
arm-linux-gnueabihf-gcc |
C 编译器,将源码编译为 ARM 目标文件 | $(CC) -c file.c |
arm-linux-gnueabihf-ld |
链接器,合并目标文件生成可执行文件或模块 | $(LD) -r -o module.o a.o b.o |
arm-linux-gnueabihf-objcopy |
目标文件格式转换(如 ELF → 二进制) | objcopy -O binary vmlinux vmlinux.bin |
arm-linux-gnueabihf-objdump |
反汇编与符号表查看 | objdump -D module.ko |
arm-linux-gnueabihf-strip |
去除调试符号,减小文件体积 | strip --strip-debug module.ko |
1.2 内核源码树配置
驱动模块的编译必须依赖内核源码树(Kernel Source Tree)提供的头文件、Makefile 规则和编译脚本。我们使用官方 LTS 内核并配置为 ARM 架构。
1.2.1 获取内核源码
# 下载 Linux 5.10 LTS 内核
cd ~
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.210.tar.xz
tar -xvf linux-5.10.210.tar.xz
cd linux-5.10.210
1.2.2 配置内核
使用默认的 ARM 多平台配置(multi_v7_defconfig),该配置支持大多数 ARMv7 开发板。
# 设置架构与交叉编译器
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 加载默认配置
make multi_v7_defconfig
# 可选:使用 menuconfig 进行微调(如开启调试符号)
make menuconfig
常见错误:若
make menuconfig 提示缺少 ncurses 库,请执行 sudo apt install libncurses5-dev。若跳过此步,后续模块编译可能因缺少配置头文件而失败。
1.2.3 编译内核(可选但推荐)
编译一次内核可生成必要的 Module.symvers 和 include/generated/ 下的自动头文件,确保模块编译时符号解析正确。
# 编译内核(使用 4 核加速,根据主机 CPU 调整)
make -j4 zImage
# 编译设备树
make -j4 dtbs
# 编译模块(可选,用于验证)
make -j4 modules
编译产物:
arch/arm/boot/zImage 为内核镜像;arch/arm/boot/dts/ 下生成 .dtb 设备树文件。模块编译依赖 Module.symvers,该文件位于源码树根目录。
1.2.4 内核源码树结构速览
1.3 模块编译 Makefile 编写
内核模块的编译使用内核提供的 kbuild 系统。我们只需编写一个简洁的 Makefile,通过 make -C 指向内核源码树即可完成交叉编译。
1.3.1 基础 Makefile 模板
# Makefile —— 字符设备驱动模块编译
# 目标:编译 mychardev.ko
# 模块名称(不带 .ko 后缀)
obj-m := mychardev.o
# 若模块由多个源文件组成,使用如下格式:
# mychardev-objs := file1.o file2.o
# 内核源码树路径(根据实际位置修改)
KDIR := ~/linux-5.10.210
# 当前目录
PWD := $(shell pwd)
# 默认目标:编译模块
all:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
# 清理目标
clean:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean
# 安装模块到临时目录(可选)
install:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install INSTALL_MOD_PATH=./modules_install
1.3.2 Makefile 关键变量解析
| 变量/语法 | 含义 | 示例值 |
|---|---|---|
obj-m |
指定要编译为模块的目标文件名(不含 .ko) | obj-m := mychardev.o |
KDIR |
内核源码树绝对路径 | ~/linux-5.10.210 |
M=$(PWD) |
告诉 kbuild 在指定目录查找源码并输出模块 | M=/home/user/driver |
ARCH |
目标架构,必须与内核配置一致 | arm |
CROSS_COMPILE |
交叉编译工具链前缀 | arm-linux-gnueabihf- |
modules |
kbuild 目标,编译所有 obj-m 指定的模块 |
— |
modules_install |
将编译好的 .ko 安装到指定目录 | — |
1.3.3 编译与验证
# 进入驱动源码目录
cd ~/driver_project
# 编译模块
make
# 预期输出示例:
# make -C ~/linux-5.10.210 M=/home/user/driver_project ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
# CC [M] mychardev.o
# MODPOST mychardev.mod.c
# CC [M] mychardev.mod.o
# LD [M] mychardev.ko
# 查看生成的模块信息
arm-linux-gnueabihf-objdump -h mychardev.ko | head -20
arm-linux-gnueabihf-modinfo mychardev.ko
常见问题排查:
• 若提示
• 若提示
• 若编译后
• 若提示
*** No rule to make target 'modules',请检查 KDIR 路径是否正确,且内核已执行过 make 或至少 make modules_prepare。• 若提示
*** missing MODULE_LICENSE(),请在驱动源码中添加 MODULE_LICENSE("GPL");。• 若编译后
.ko 文件大小为 0,通常是因为源码中存在编译错误,请查看终端输出的错误信息。
1.3.4 多文件模块 Makefile 示例
当驱动由多个源文件组成时,使用 <模块名>-objs 语法指定目标文件列表。
# Makefile —— 多文件模块示例
obj-m := mychardev.o
mychardev-objs := main.o ioctl.o interrupt.o
KDIR := ~/linux-5.10.210
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean
1.4 环境验证与测试
完成上述步骤后,编写一个最简单的“空模块”来验证整个工具链是否正常工作。
1.4.1 最小驱动源码
// test_dummy.c
#include <linux/module.h>
#include <linux/kernel.h>
static int __init dummy_init(void)
{
pr_info("Dummy module loaded\n");
return 0;
}
static void __exit dummy_exit(void)
{
pr_info("Dummy module unloaded\n");
}
module_init(dummy_init);
module_exit(dummy_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Expert");
MODULE_DESCRIPTION("A minimal dummy module for environment verification");
1.4.2 编译与检查
# 编译
make
# 检查模块架构
file test_dummy.ko
# 输出应包含:ELF 32-bit LSB relocatable, ARM, version 1 (ARM)
# 查看模块符号
arm-linux-gnueabihf-nm test_dummy.ko | grep -E "init|exit"
验证通过标志:
file 命令显示 ARM 架构,nm 能正确列出 init_module 和 cleanup_module 符号,且无编译错误。至此,交叉编译环境、内核源码树和 Makefile 全部就绪,可以进入下一章的字符设备驱动框架编写。
1.5 本章小结
- 交叉编译工具链:使用 Linaro GCC 7.5 ARM 硬浮点版本,配置
PATH、CROSS_COMPILE和ARCH环境变量。 - 内核源码树:下载 Linux 5.10 LTS,执行
multi_v7_defconfig并至少编译一次内核以生成依赖文件。 - Makefile 编写:利用 kbuild 系统,通过
obj-m指定模块,KDIR指向内核源码树,ARCH和CROSS_COMPILE确保交叉编译。 - 验证方法:编译最小驱动并用
file、nm确认架构与符号正确。