1. Linux 设备驱动概述
设备驱动是操作系统与硬件设备之间的“翻译官”。在 Linux 系统中,驱动以内核模块的形式存在,负责管理设备的初始化、数据传输、中断处理以及电源管理等核心任务。本章将建立驱动开发的基本认知框架,包括驱动的作用、内核空间与用户空间的划分,以及交叉编译环境的搭建方法。
1.1 驱动的作用与分类
Linux 设备驱动的主要职责可以概括为以下四点:
- 设备初始化与配置:在系统启动或模块加载时,完成寄存器设置、中断注册、内存映射等硬件初始化工作。
- 数据收发:实现 read/write/ioctl 等文件操作接口,在用户态与硬件之间搬运数据。
- 中断处理:响应硬件中断,在中断上下文中完成紧急处理,并将耗时任务推送到下半部(bottom half)。
- 电源管理与热插拔:配合内核 PM 框架实现设备的休眠/唤醒,以及设备的热移除/热插入。
根据设备类型和通信方式,Linux 驱动通常分为三大类:
| 驱动类型 | 典型设备 | 核心框架 | 用户接口 |
|---|---|---|---|
| 字符设备 | 串口、GPIO、I2C 传感器 | cdev / file_operations | /dev/xxx 节点 |
| 块设备 | eMMC、SD 卡、NVMe SSD | block_device / gendisk | /dev/sda 等 |
| 网络设备 | 以太网 PHY、WiFi 模块 | net_device / sk_buff | socket 接口 |
课程聚焦:本课程以字符设备为主线,结合 platform 框架讲解驱动与设备树的匹配机制。字符设备结构简单、调试方便,是入门 Linux 驱动开发的最佳起点。
1.2 内核空间与用户空间
Linux 系统将虚拟内存划分为两个特权级区域:用户空间(User Space)和内核空间(Kernel Space)。驱动代码运行在内核空间,拥有最高权限;应用程序运行在用户空间,受 MMU 保护,无法直接访问硬件。
关键区别:用户空间通过系统调用陷入内核,驱动在内核空间完成硬件操作后,将结果拷贝回用户缓冲区。这一过程涉及地址空间切换和数据拷贝,是驱动性能优化的核心关注点。
1.3 驱动开发环境搭建
嵌入式 Linux 驱动开发通常采用交叉编译模式:在性能强大的宿主机(x86 PC)上编写和编译代码,生成目标架构(ARM / RISC-V / MIPS)的可执行文件,再下载到目标板运行。
1.3.1 交叉编译工具链
工具链包含编译器、链接器、库和调试器。以 ARM Cortex-A7 为例,常用工具链如下:
| 组件 | 示例名称 | 说明 |
|---|---|---|
| C 编译器 | arm-linux-gnueabihf-gcc | 生成 ARM 硬浮点指令 |
| 链接器 | arm-linux-gnueabihf-ld | 链接目标文件与库 |
| 反汇编器 | arm-linux-gnueabihf-objdump | 查看二进制指令与符号表 |
| 调试器 | arm-linux-gnueabihf-gdb | 远程调试目标板内核 |
避坑提示:工具链的 glibc 版本必须与目标板根文件系统匹配,否则编译出的驱动模块可能因符号版本不兼容而加载失败。建议使用板卡厂商提供的 SDK 工具链,或从 Linaro / Bootlin 下载预编译版本。
1.3.2 内核源码树
驱动模块的编译依赖内核源码树(Kernel Source Tree),包括头文件、Kconfig 和 Makefile 结构。搭建步骤:
- 下载内核源码:从 kernel.org 或板卡厂商仓库获取,版本需与目标板运行的内核一致。
- 配置内核:使用
make ARCH=arm menuconfig加载默认配置或厂商 defconfig。 - 编译内核:执行
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4生成 zImage 和 dtb。 - 编译模块:进入驱动源码目录,使用内核树提供的 Makefile 编译外部模块。
典型的外部模块 Makefile 模板:
# Makefile — 外部驱动模块
obj-m := hello_driver.o
KERNEL_DIR ?= /home/user/linux-5.10
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 CROSS_COMPILE=$(CROSS_COMPILE) clean
1.3.3 环境验证
搭建完成后,执行以下命令验证环境是否就绪:
# 检查工具链
arm-linux-gnueabihf-gcc --version
# 检查内核树是否已编译
ls /home/user/linux-5.10/include/linux/device.h
# 编译一个空模块测试
make -C /home/user/linux-5.10 M=$(pwd) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
推荐实践:在虚拟机或 Docker 容器中搭建统一的开发环境,避免因宿主机库版本冲突导致编译失败。使用
export ARCH=arm 和 export CROSS_COMPILE=arm-linux-gnueabihf- 可减少每次编译时的参数输入。
1.4 本章小结
- 驱动是内核与硬件之间的桥梁,字符设备驱动是入门首选。
- 用户空间与内核空间通过系统调用隔离,驱动运行在内核态,拥有硬件访问权限。
- 交叉编译环境包括工具链和内核源码树,两者版本必须与目标板严格对齐。
- 使用外部模块 Makefile 模板可以快速编译独立驱动,无需每次重新编译整个内核。