1. 字符设备驱动概述
1.1 Linux 设备驱动分类
在 Linux 操作系统中,设备驱动是内核与硬件设备之间的桥梁。根据设备的数据交换方式和访问特性,Linux 将设备驱动划分为以下三大类:
| 驱动类型 | 数据交换方式 | 典型设备 | 设备文件标识 |
|---|---|---|---|
| 字符设备 | 按字节流顺序访问,无缓冲区 | 串口、GPIO、I2C、SPI、LED、按键 | c (character) |
| 块设备 | 按数据块随机访问,有缓冲区 | 硬盘、eMMC、SD卡、U盘 | b (block) |
| 网络设备 | 通过套接字接口收发数据包 | 以太网卡、WiFi模块、CAN总线 | 无设备文件,通过 socket 访问 |
核心要点: 嵌入式开发中,90% 以上的外设驱动都属于字符设备驱动。掌握字符设备驱动开发是嵌入式 Linux 驱动工程师的基本功。
1.2 字符设备特点
字符设备(Character Device)是 Linux 中最基础、最常用的设备类型。其核心特点如下:
- 顺序字节流: 数据按字节顺序读写,不支持随机访问(不能像块设备那样
lseek到任意位置)。 - 无缓存机制: 用户态读写操作直接传递给内核驱动,不经过内核页缓存(page cache)。
- 操作接口简洁: 通过
open()、close()、read()、write()、ioctl()等标准系统调用进行交互。 - 实时性较好: 由于没有复杂的缓存层,数据路径短,适合对延迟敏感的外设。
- 设备文件: 在
/dev目录下以文件形式呈现,用户程序通过文件 I/O 操作设备。
开发提示: 字符设备驱动开发的核心工作就是实现
struct file_operations 结构体中的回调函数,并将其注册到内核。
1.3 主设备号与次设备号
Linux 内核使用 设备号 来唯一标识一个设备驱动实例。设备号是一个 32 位无符号整数,由两部分组成:
- 主设备号 (Major Number): 高 12 位,用于标识设备驱动类型。内核通过主设备号找到对应的驱动程序。
- 次设备号 (Minor Number): 低 20 位,用于标识同一驱动下的不同设备实例。驱动内部通过次设备号区分具体硬件。
关键理解: 主设备号决定“谁来处理”,次设备号决定“处理哪个设备”。例如,两个串口设备
/dev/ttyS0 和 /dev/ttyS1 共享同一个主设备号(4),但次设备号分别为 64 和 65。
1.4 设备号分配方式
| 分配方式 | 函数/宏 | 适用场景 | 特点 |
|---|---|---|---|
| 静态分配 | register_chrdev_region() |
已知主设备号(如 0~255 保留号) | 需提前申请,避免冲突 |
| 动态分配 | alloc_chrdev_region() |
自定义驱动,不关心具体主设备号 | 内核自动分配,推荐使用 |
| 查看已分配 | cat /proc/devices |
调试和排查冲突 | 列出当前系统所有已注册设备号 |
避坑指南: 在嵌入式开发中,强烈建议使用动态分配方式 (
alloc_chrdev_region),避免与内核主线或其他驱动的主设备号冲突。静态分配仅适用于 Linux 内核官方文档中明确保留的设备号。
1.5 设备号查看与管理
在嵌入式开发调试过程中,经常需要查看当前系统的设备号分配情况。以下是常用命令:
# 查看已注册的设备驱动及其主设备号
cat /proc/devices
# 查看 /dev 目录下设备文件的设备号(主:次)
ls -l /dev/ | grep '^c'
# 使用 stat 命令查看单个设备文件的设备号
stat -c "主设备号: %t 次设备号: %T" /dev/ttyS0
实战建议: 在编写驱动时,建议在
init 函数中打印分配到的设备号,方便后续创建设备文件节点。例如:printk("注册设备: major=%d, minor=%d\n", MAJOR(dev), MINOR(dev));
1.6 本章小结
- Linux 设备驱动分为字符设备、块设备、网络设备三大类,嵌入式开发以字符设备为主。
- 字符设备以字节流方式访问,无缓存,通过
file_operations结构体实现标准文件操作。 - 设备号由主设备号(12 位)和次设备号(20 位)组成,主设备号标识驱动类型,次设备号标识设备实例。
- 推荐使用动态分配方式获取设备号,避免冲突。