1. 字符设备驱动概述
1.1.1 字符设备的概念
字符设备(Character Device)是 Linux 驱动模型中最基础、最常用的设备类型。它以字节流的形式进行数据传输,数据按顺序、逐个字节地读写,不支持随机访问。典型的字符设备包括:串口、键盘、鼠标、触摸屏、LED、GPIO、I2C/SPI 从设备等。
核心特征:字符设备的数据访问如同读写文件——打开、读取、写入、关闭,操作接口与普通文件高度一致。用户空间通过
open()、read()、write()、close() 等系统调用与字符设备交互。
1.1.2 字符设备 vs 块设备 vs 网络设备
Linux 内核将设备划分为三大类型,理解它们的区别是驱动开发的基础。
| 对比维度 | 字符设备 | 块设备 | 网络设备 |
|---|---|---|---|
| 数据访问方式 | 字节流,顺序读写 | 块(固定大小,如 512B/4KB),随机访问 | 数据包(Packet),异步收发 |
| 设备节点 | /dev/ttyS0、/dev/input/event0 |
/dev/sda、/dev/mmcblk0 |
无设备节点,通过 socket 接口访问 |
| 文件操作接口 | open/read/write/ioctl/close |
open/read/write/mmap + 块层缓存 |
socket/bind/send/recv |
| 内核实现 | struct file_operations |
struct block_device_operations + 块层框架 |
struct net_device_ops + 网络协议栈 |
| 典型硬件 | UART、GPIO、I2C、SPI、ADC | eMMC、NAND Flash、SD 卡、硬盘 | 以太网 PHY、WiFi、CAN |
| 是否经过内核缓存 | 通常不经过页缓存(直接 I/O) | 经过页缓存(Page Cache) | 经过网络协议栈缓存 |
开发视角:嵌入式 Linux 驱动开发中,字符设备驱动占比最高(约 70% 以上)。掌握字符设备驱动是入门内核驱动开发的第一步,也是理解更复杂驱动框架(如块设备、网络设备、USB、PCIe)的基础。
1.1.3 驱动在内核中的角色
设备驱动是操作系统与硬件之间的桥梁。在内核架构中,驱动承担以下关键职责:
- 硬件抽象:将硬件寄存器、中断、DMA 等底层细节封装为统一的操作接口。
- 系统调用分发:将用户空间的
read/write/ioctl等请求转化为硬件操作序列。 - 中断处理:响应硬件中断,完成数据接收、状态通知、唤醒等待进程等任务。
- 资源管理:管理硬件寄存器地址、中断号、DMA 通道、时钟等系统资源。
- 电源管理:配合内核 PM 框架实现设备的休眠、唤醒、频率调节。
1.1.4 驱动开发流程总览
一个完整的字符设备驱动开发遵循以下标准化流程,本课程将逐一深入每个环节:
- 设备号申请:使用
alloc_chrdev_region()或register_chrdev_region()获取主/次设备号。 - cdev 初始化与注册:调用
cdev_init()绑定file_operations,通过cdev_add()将设备加入内核。 - 硬件初始化:映射寄存器地址(
ioremap)、注册中断(request_irq)、初始化 GPIO/时钟。 - 实现 file_operations:编写
.open、.read、.write、.ioctl、.release等回调函数。 - 设备节点创建:手动
mknod或通过 udev/mdev 自动创建设备节点。 - 用户空间测试:编写应用程序调用驱动接口,验证功能正确性。
- 驱动卸载与清理:调用
cdev_del()、unregister_chrdev_region()、释放硬件资源。
常见误区:初学者容易跳过“硬件初始化”直接编写 file_operations,导致驱动加载成功但无法操作硬件。正确的顺序是:先确保硬件寄存器映射和中断注册成功,再实现数据读写接口。
1.1.5 本章小结
- 字符设备以字节流方式工作,是嵌入式 Linux 中最常见的设备类型。
- 与块设备(随机访问、有缓存)和网络设备(数据包、无设备节点)有本质区别。
- 驱动在内核中承担硬件抽象、系统调用分发、中断处理、资源管理四大核心角色。
- 开发流程分为7 个标准步骤,从设备号申请到卸载清理形成完整闭环。