《Linux设备驱动程序》第三版(LDD3)
Linux Device Drivers, 3rd Edition — Linux 驱动开发领域的圣经级著作,O’Reilly 出版,基于 Linux 2.6 内核。 三位作者均为 Linux 内核的核心贡献者:Jonathan Corbet(LWN.net 主编)、Alessandro Rubini(Linux 驱动专家)、Greg Kroah-Hartman(Linux 驱动子系统维护者)。
核心定位
- 驱动程序 = 机制(mechanism),而非策略(policy):驱动只提供硬件能力,不决定如何使用
- 驱动是内核中独立的”黑盒子”,向上提供标准化接口,向下操作具体硬件
- 模块化设计:可动态加载/卸载,是 Linux 驱动生态繁荣的基础
- 本书采用设备无关的方法讲解驱动开发的通用原则,搭配不需要特殊硬件即可测试的示例驱动(scull、snull、sbull 等)
全书知识体系(18章 · 510页)
第一部分:驱动基础(第1-5章)
第1章 设备驱动简介
- 驱动程序的角色:机制 vs 策略的分离(Unix 设计哲学的核心)
- 内核的划分:可加载模块是 Linux 的重要特性
- 设备和模块的分类:字符设备、块设备、网络设备 三大类
- 安全问题:内核代码的特权级与安全责任
- 加入内核开发社区
第2章 建立和运行模块
- Hello World 模块:
module_init/module_exit - 用户空间 vs 内核空间:地址空间隔离
- 内核的并发模型:竞态、抢占、SMP
- 编译模块:内核构建系统 Kbuild
- 加载/卸载模块:
insmod/rmmod/modprobe - 内核符号表:
EXPORT_SYMBOL - 初始化与关停:错误处理、模块加载竞争
- 模块参数:
module_param
第3章 字符驱动
- 主次设备号:
dev_t、MKDEV、MAJOR、MINOR - 设备编号的分配与释放:
register_chrdev_region/alloc_chrdev_region - 关键数据结构:
file_operations:文件操作方法表struct file:打开的文件实例struct inode:磁盘上的文件元数据
- 字符设备注册:
cdev_init/cdev_add open/release方法read/write方法:用户空间与内核空间数据拷贝(copy_from_user/copy_to_user)- scull 驱动示例:Simple Character Utility for Loading Localities
第4章 调试技术
- 用打印调试:
printk的日志级别(KERN_ALERT、KERN_INFO 等) - /proc 文件系统:调试信息的窗口
- ioctl 调试方法
- oops 消息:内核崩溃诊断
- 系统挂起的调试
- 调试工具:gdb、kdb、kgdb、UML(用户模式 Linux)、动态探针
第5章 并发和竞争情况
- 并发管理是驱动开发的核心难点之一
- 旗标(信号量)和互斥体:
struct semaphore、down/up - 读者写者旗标:读写分离优化
- completions 机制:线程同步
- 自旋锁(spinlock):
spin_lock/spin_unlock,忙等待,持有时间要短 - 自旋锁与原子上下文:不能在持有自旋锁时睡眠
- 读者写者自旋锁
- 锁陷阱:
- 加锁顺序规则(避免死锁)
- 细粒度 vs 粗粒度加锁的权衡
- 无锁算法选择:
- 原子变量:
atomic_t - 位操作
- seqlock:读多写少场景
- RCU(Read-Copy-Update):读取-拷贝-更新
- 原子变量:
第二部分:高级字符驱动(第6-8章)
第6章 高级字符驱动操作
- ioctl 接口:设备控制命令,
_IO/_IOR/_IOW/_IOWR宏定义命令号 - 阻塞 I/O:
- 睡眠机制:
wait_queue - 阻塞与非阻塞操作
interruptible_sleep_on/wake_up
- 睡眠机制:
- poll / select:多路复用I/O,
poll_table - 异步通知:
fasync,信号驱动I/O - llseek:文件定位
- 存取控制:单 open 设备、设备复制(类似 /dev/zero 的多实例)
第7章 时间、延时和延后工作
- 时间测量:jiffies 计数器、HZ
- 高精度计时:处理器特定寄存器(TSC等)
- 延后执行:
- 长延时:
schedule_timeout - 短延时:
udelay/mdelay(忙等待)
- 长延时:
- 内核定时器:
struct timer_list、add_timer/del_timer - tasklet:中断底半部机制,软中断上下文执行
- 工作队列:进程上下文执行延后工作,可睡眠
- 共享工作队列
keventd_wq
- 共享工作队列
第8章 分配内存
kmalloc:flags 参数(GFP_KERNEL、GFP_ATOMIC、GFP_DMA 等)、size 参数- Slab 缓存:
kmem_cache_create/kmem_cache_alloc,针对频繁分配/释放的对象 - 内存池(mempool):保证分配成功的场景
- 页级分配:
__get_free_pages/alloc_pages vmalloc:虚拟地址连续、物理地址不连续- 每-CPU 变量:
per_cpu,避免缓存颠簸 - 启动时预留大内存块
第三部分:硬件与中断(第9-11章)
第9章 与硬件通讯
- I/O 端口 vs I/O 内存:两种硬件寄存器访问方式
- I/O 端口操作:
inb/outb、request_region - I/O 内存操作:
ioremap/iowrite32/ioread32 - 并口驱动示例(short 驱动)
- ISA 内存:1MB 以下的传统内存区域
- 平台依赖性:不同架构的I/O操作差异
第10章 中断处理
- 安装中断处理:
request_irq/free_irq - /proc/interrupts
- IRQ 自动检测
- 中断处理程序:快速路径 vs 慢速路径
- 前半部 / 后半部分离:
- 前半部:中断上下文中快速响应硬件
- 后半部:延迟处理非紧急工作(tasklet、工作队列)
- 中断共享:多个设备共享同一个 IRQ
- 中断驱动 I/O:写缓存示例
第11章 内核中的数据类型
- 标准C类型在 kernel 中的注意事项
- 明确大小的类型:
u8/u16/u32/u64(<linux/types.h>) - 接口特定类型:
pid_t、size_t、loff_t等 - 移植性问题:
- 时间间隔:不能假设 HZ = 100
- 页大小:
PAGE_SIZE,不是固定 4KB - 字节序:大小端
- 数据对齐:架构差异
- 指针和错误值:用
IS_ERR/PTR_ERR
- 内核链表:
struct list_head,侵入式双向链表(内核最常用的数据结构)
第四部分:总线与设备模型(第12-14章)
第12章 PCI 驱动
- PCI 接口:寻址、配置空间、BAR(Base Address Register)
- PCI 驱动注册:
pci_driver结构、MODULE_DEVICE_TABLE - 存取配置空间:
pci_read_config_*/pci_write_config_* - 存取 I/O 和内存空间:
pci_resource_start/pci_iomap - PCI 中断
- 回顾 ISA 总线:传统总线编程
- 其他总线:PC/104、MCA、EISA、VLB、SBus、NuBus
第13章 USB 驱动
- USB 设备基础:端点(endpoint)、接口(interface)、配置(configuration)
- USB 与 sysfs
- URB(USB Request Block):USB 传输的核心数据结构
- 创建/销毁/提交/取消 URB
- 完成回调处理
- 编写 USB 驱动:
- 设备匹配(
usb_device_id) - 注册 USB 驱动(
usb_driver)
- 设备匹配(
- 无 URB 的简单传输:
usb_bulk_msg、usb_control_msg
第14章 Linux 设备模型
- Kobject / Kset / Subsystem:设备模型的核心基石
- kobject:引用计数、父子关系、sysfs 表示
- kset:同类 kobject 的集合
- subsystem:更高层的子系统抽象
- sysfs 文件系统:设备模型的用户空间视图
- 属性(attribute)、二进制属性、符号链接
- 总线 / 设备 / 驱动:设备模型的三大支柱
- 总线(bus_type):设备和驱动匹配的桥梁
- 设备(device):硬件的内核表示
- 驱动(device_driver):软件的内核表示
- 类(class):按功能分类设备(/sys/class/)
- 热插拔:
- 动态设备管理
- /sbin/hotplug / uevent 机制
- 固件加载:
request_firmware
第五部分:内存与DMA(第15章)
第15章 内存映射和 DMA
- Linux 内存管理总览:
- 地址类型:物理地址、虚拟地址、总线地址、内核逻辑地址
- 高/低内存:低端内存直接映射,高端内存需要临时映射
struct page:物理页的内核表示- 页表:虚拟地址→物理地址转换
- 虚拟内存区(VMA):
struct vm_area_struct
- mmap 设备操作:
remap_pfn_range:映射物理页到用户空间nopage方法:按需映射- 重映射 RAM、I/O 内存、内核虚拟地址
- 直接 I/O:绕过页缓存的 I/O
- 异步 I/O:
aio_*接口 - DMA(直接内存存取):
- DMA 传输原理:设备直接访问内存,不经过 CPU
- DMA 缓冲区分配与一致性
- 总线地址 vs 物理地址
- 通用 DMA 层:
dma_alloc_coherent/dma_map_single/dma_unmap_single - 流式 DMA 映射 vs 一致性 DMA 映射
- ISA DMA:传统 DMA 控制器
第六部分:块设备与网络(第16-17章)
第16章 块驱动
- 块设备注册:
register_blkdev - 磁盘注册:
gendisk结构 - sbull 驱动示例:Simple Block Utility for Loading Localities(RAM盘)
- 请求处理:
- 请求队列(
request_queue) request_fn:请求处理函数- 分析请求:
bio结构、段、扇区 - 请求完成:
end_request - 命令预准备
- 请求队列(
- 块设备 ioctl:HDIO_* 命令
第17章 网络驱动
- snull 驱动示例:Simple Network Utility for Loading Localities(虚拟网络设备)
net_device结构详解:硬件信息、接口信息、设备方法- 设备注册与初始化
- 报文传送:
hard_start_xmit- 发送并发控制
- 传送超时处理
- 发散汇聚 I/O(SG)
- 报文接收:
netif_rx - Socket 缓存(sk_buff):网络报文的核心数据结构
- 重要成员与操作函数
- MAC 地址解析:ARP
- 中断处理与 NAPI(中断缓解,New API)
- ethtool 支持、netpoll
- 多播支持
- 统计信息与 ioctl
第七部分:TTY 驱动(第18章)
第18章 TTY 驱动
- TTY 子系统概述
- 小型 TTY 驱动示例
termios结构:终端行设置- TTY 驱动函数指针:
- open / close
- 数据流:write、put_char、flush_chars
- 缓冲函数
- TTY 线路设置:
set_termios、 modem 控制 - TTY 的 proc 和 sysfs 接口
tty_driver/tty_operations/tty_struct结构详解
核心概念与方法论
1. 三大设备类型
| 类型 | 特点 | 示例 | 用户接口 |
|---|---|---|---|
| 字符设备 | 字节流、顺序访问 | 串口、终端、/dev/null | 字符设备文件 |
| 块设备 | 随机访问、按块读写 | 硬盘、U盘 | 块设备文件 + 文件系统 |
| 网络设备 | 面向报文、异步 | 网卡 | socket API |
2. 并发与同步工具选择指南
| 工具 | 上下文 | 能否睡眠 | 适用场景 |
|---|---|---|---|
| 信号量/互斥体 | 进程上下文 | ✅ 可以 | 大部分互斥场景 |
| 自旋锁 | 任意(含中断) | ❌ 不能 | 短时间临界区、中断上下文 |
| 原子变量 | 任意 | — | 简单计数 |
| seqlock | 任意 | — | 读多写少、简单数据 |
| RCU | 读无锁 | 读侧无锁 | 读极多写极少、链表等 |
| completions | 进程上下文 | ✅ | 等待某事件完成 |
3. 中断处理的”前后两半”思想
- 前半部(Top Half):在中断上下文中执行,只做最紧急的事(应答硬件、保存数据、调度底半部)
- 后半部(Bottom Half):延迟处理非紧急工作
- tasklet:软中断上下文,不能睡眠,执行快
- 工作队列:进程上下文,可以睡眠,灵活性高
- 软中断(softirq):最底层,一般驱动不直接使用
4. 驱动开发的重要原则
- 机制 vs 策略分离:驱动提供能力,策略留给用户空间
- 错误处理与健壮性:内核代码不能崩溃,每个分配都要检查失败
- 并发意识:永远假设多个执行流同时访问你的数据
- 尽量使用内核提供的通用接口:不要自己造轮子
- 调试友好:合理使用 printk、procfs、debugfs
价值与局限
✅ 价值
- Linux 驱动开发的圣经:最系统、最经典的驱动开发教材
- 示例驱动教学法:scull/snull/sbull 三大虚拟驱动,不需要真实硬件就能学习
- 知识覆盖面极广:从字符设备到块设备、网络、TTY、PCI、USB、DMA、设备模型
- 内核原理深度:不只讲 API,更深入内核内部实现机制
⚠️ 时代局限
- 基于 Linux 2.6 内核:与现代 5.x/6.x 内核 API 有较大差异
struct file_operations签名变化(loff_t *pos参数)- 设备模型演进(2.6 是设备模型引入的初期)
- 原子操作、锁机制 API 更新
- 很多老式 API(如
register_chrdev)已弃用
- 缺少现代内核特性:
- 设备树(Device Tree)
- pin control / gpiod
- 时钟框架(CCF)
- regulator / PM 框架
- IIO 子系统
- 现代 sysfs / kernfs
- 但核心设计思想和概念框架完全适用:字符/块/网络三大类、并发同步、中断前后半部、DMA、mmap 等基础概念至今未变
关联笔记
- 《Linux内核设计与实现》-第三版-Robert-Love — 内核开发的另一本经典,LDD3 的姊妹篇
- 《LINUX内核源代码情景分析》-毛德操胡希明 — 从源码角度深入理解内核
- 《结构化计算机组成》-Structured-Computer-Organization-第6版-Tanenbaum — 硬件层的基础
- 《高效程序的奥秘》-Hacker-s-Delight-Henry-S-Warren — 位操作技巧,驱动编程常用
延伸阅读
- 现代 Linux 驱动开发:
- Linux Kernel Driver Model 文档(内核源码
Documentation/driver-api/) - 《Linux Device Drivers Development》(John Madieu,更新的驱动开发书)
- 内核源码自带的示例驱动(
drivers/目录下)
- Linux Kernel Driver Model 文档(内核源码
- 内核调试进阶:
ftrace、perf、eBPF