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 结构体中的回调函数,并将其注册到内核。
字符设备驱动在 Linux 系统中的位置与数据流 用户空间 (User Space) 应用程序 (App) — 通过标准文件 I/O 操作设备 系统调用层 (sys_open / sys_read / sys_write) 内核空间 (Kernel Space) VFS (虚拟文件系统) → 字符设备驱动 → file_operations 回调 硬件层 (Hardware) GPIO / UART / I2C / SPI 外设寄存器 数据流方向:用户 App → 系统调用 → VFS → 字符设备驱动 → 硬件寄存器

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 内核官方文档中明确保留的设备号。
设备号结构:主设备号 + 次设备号 32 位设备号 (dev_t) 高 12 位 = 主设备号 (Major)   |   低 20 位 = 次设备号 (Minor) 主设备号 (Major) 12 位 (0 ~ 4095) 次设备号 (Minor) 20 位 (0 ~ 1048575) 内核宏:MAJOR(dev_t) 提取主设备号,MINOR(dev_t) 提取次设备号 MKDEV(major, minor) 将主次设备号组合为 dev_t 类型

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 位)组成,主设备号标识驱动类型,次设备号标识设备实例。
  • 推荐使用动态分配方式获取设备号,避免冲突。