《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. 驱动开发的重要原则

  1. 机制 vs 策略分离:驱动提供能力,策略留给用户空间
  2. 错误处理与健壮性:内核代码不能崩溃,每个分配都要检查失败
  3. 并发意识:永远假设多个执行流同时访问你的数据
  4. 尽量使用内核提供的通用接口:不要自己造轮子
  5. 调试友好:合理使用 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 驱动开发:
    • Linux Kernel Driver Model 文档(内核源码 Documentation/driver-api/)
    • 《Linux Device Drivers Development》(John Madieu,更新的驱动开发书)
    • 内核源码自带的示例驱动(drivers/ 目录下)
  • 内核调试进阶:ftrace、perf、eBPF