1. Linux驱动开发环境搭建
嵌入式 Linux 驱动开发与普通 PC 开发最大的区别在于交叉编译。目标板(ARM、RISC-V 等)资源有限,无法运行完整的编译工具链,因此需要在性能强劲的宿主机(x86_64)上编译出目标架构的二进制模块,再部署到目标板运行。本章将完整搭建这一基础环境。
1.1 交叉编译工具链安装
交叉编译工具链(Cross-Compilation Toolchain)包含针对目标架构的编译器、链接器、库和头文件。常见获取方式有两种:
| 方式 | 适用场景 | 典型命令/包名 |
|---|---|---|
| 发行版预装 | 快速原型、通用 ARM 平台 | gcc-arm-linux-gnueabihf |
| 芯片厂商 SDK | 特定 SoC(如 i.MX、STM32MP1) | Yocto / Buildroot 输出工具链 |
| Linaro 预编译包 | ARMv7-A / ARMv8-A 通用开发 | gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf |
以 Ubuntu 宿主机 + ARM Cortex-A7 目标板为例,安装 Linaro 工具链:
# 下载并解压到 /opt
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
sudo tar -xf gcc-linaro-*.tar.xz -C /opt
# 添加至 PATH
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
# 验证安装
arm-linux-gnueabihf-gcc --version
提示:工具链版本必须与目标板内核版本匹配。使用过高版本的 GCC 编译旧内核模块可能因内联函数 ABI 变化导致加载失败。
1.2 内核源码树配置
驱动模块必须依赖完整的内核源码树(Kernel Source Tree)进行编译。源码树需要预先配置,生成 .config 和 Module.symvers 等关键文件。
配置流程分为三步:
- 获取内核源码:从 kernel.org 或芯片厂商 BSP 获取,版本必须与目标板运行内核一致。
- 生成默认配置:使用目标板厂商提供的
defconfig,或从目标板/proc/config.gz导出。 - 编译内核与模块:先编译完整内核,再编译模块,确保
Module.symvers生成。
# 以 i.MX6ULL EVK 为例
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make imx_v7_defconfig
make -j4 zImage modules
make modules_install INSTALL_MOD_PATH=./modules_install
注意:如果只编译模块而不先编译内核,
Module.symvers 文件缺失,将导致模块中引用的内核符号(如 input_register_device)无法解析,insmod 时报 Unknown symbol 错误。
1.3 模块编译与加载基础
驱动模块的编译依赖内核源码树提供的 Kbuild 系统。开发者只需编写 Makefile,指定模块名和源文件,Kbuild 会自动处理交叉编译参数。
一个最简单的驱动模块 Makefile 模板:
# Makefile — 外部模块编译
obj-m := hello_drv.o
hello_drv-objs := hello.o
KERNEL_DIR ?= /home/user/linux-imx
CROSS_COMPILE ?= arm-linux-gnueabihf-
all:
make -C $(KERNEL_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) ARCH=arm clean
编译后生成 hello_drv.ko,部署到目标板后使用以下命令管理:
| 操作 | 命令 | 说明 |
|---|---|---|
| 加载模块 | insmod hello_drv.ko |
直接插入,不处理依赖 |
| 卸载模块 | rmmod hello_drv |
按名称卸载 |
| 查看已加载模块 | lsmod |
显示模块列表及引用计数 |
| 查看模块信息 | modinfo hello_drv.ko |
显示作者、描述、依赖、参数 |
| 自动加载依赖 | modprobe hello_drv |
需先执行 depmod |
关键概念:模块加载时内核会调用
module_init() 注册的回调函数;卸载时调用 module_exit()。触摸屏驱动正是在 init 中申请 I2C/SPI 设备、注册 input 设备。
1.4 环境搭建全流程 SVG 图
下图展示了从宿主机到目标板的完整环境搭建链路,以及模块编译、部署、加载的流程关系:
1.5 常见问题与避坑
- 模块加载报错
Invalid module format:内核版本或vermagic不一致。用modinfo对比模块与内核的vermagic字符串。 Unknown symbol错误:模块引用了未导出的内核符号,或Module.symvers未正确生成。重新编译内核并确认EXPORT_SYMBOL。- insmod 后无任何输出:检查
printk日志级别,使用dmesg -n 8提升控制台显示级别。 - 模块卸载时系统崩溃:
exit函数中释放了仍在使用的资源,或未正确注销 input 设备。
开发效率建议:在宿主机搭建 NFS 根文件系统,目标板通过网络挂载,编译后的 .ko 文件直接同步到目标板,无需反复烧写镜像,大幅缩短调试周期。