1. 课程导论:阻塞与非阻塞IO的概念、poll机制的作用、本课程的学习目标与前置知识
1.1 为什么需要理解阻塞与非阻塞 IO?
在 Linux 字符设备驱动开发中,应用程序通过 read()、write() 等系统调用与硬件交互。当硬件数据尚未准备好(例如串口未收到数据、按键未按下)时,驱动如何处理应用程序的请求,直接决定了用户体验和系统资源的利用率。阻塞与非阻塞 IO 正是解决这一核心问题的两种基本模型。
核心矛盾: 应用程序希望“拿到数据再继续”,而硬件可能“需要等待事件发生”。驱动必须在“让进程等待”与“让进程立即返回”之间做出选择。
1.2 阻塞 IO 与非阻塞 IO 的概念
| 特性 | 阻塞 IO (Blocking I/O) | 非阻塞 IO (Non-blocking I/O) |
|---|---|---|
| 行为 | 系统调用一直等待,直到数据就绪才返回 | 系统调用立即返回,无论数据是否就绪 |
| 进程状态 | 进程进入睡眠(TASK_INTERRUPTIBLE),不占用 CPU |
进程保持运行态,占用 CPU 轮询或立即返回 |
| 返回值 | 成功返回读取/写入的字节数 | 数据未就绪时返回 -EAGAIN 或 -EWOULDBLOCK |
| 典型场景 | 读取鼠标输入、等待网络连接 | 非阻塞 socket、定时检查设备状态 |
| 驱动实现 | 使用等待队列(wait queue)挂起进程 | 检查条件,不满足则直接返回错误码 |
关键区分: 阻塞 IO 节省 CPU 资源但降低程序响应灵活性;非阻塞 IO 让程序保持控制权,但可能引入忙等待(busy-waiting)导致 CPU 浪费。驱动开发者需要根据硬件特性和应用需求选择合适模型。
1.3 阻塞与非阻塞 IO 的流程对比
下面的 SVG 图展示了应用程序发起 read() 系统调用时,阻塞与非阻塞两种模式下内核驱动的处理路径差异。
1.4 poll 机制的作用
阻塞 IO 虽然高效,但一个进程只能等待一个文件描述符;非阻塞 IO 虽然灵活,但应用程序需要不断轮询,浪费 CPU。poll 机制(包括 poll()、select()、epoll())提供了第三种选择:多路复用。
- 核心能力: 允许一个进程同时监视多个文件描述符,等待其中任何一个变为“可读”、“可写”或发生异常。
- 驱动接口: 驱动需要实现
file_operations中的.poll函数,该函数返回设备当前的状态掩码(POLLIN、POLLOUT等),并在条件不满足时将进程挂起到设备的等待队列。 - 用户态 API:
poll()和select()是 POSIX 标准接口,epoll是 Linux 特有的高性能扩展。
常见误解: poll 机制不是让驱动“主动通知”应用程序,而是让应用程序“一次性询问多个设备”,然后阻塞等待内核告知“哪个设备就绪了”。驱动在其中扮演“状态汇报员”的角色。
1.5 三种 IO 模型的对比
| 模型 | 进程阻塞点 | CPU 利用率 | 同时监视多个 fd | 驱动实现复杂度 |
|---|---|---|---|---|
| 阻塞 IO | 在 read/write 系统调用内 |
高(进程睡眠,不占 CPU) | 否 | 低(等待队列) |
| 非阻塞 IO | 无(立即返回) | 低(可能忙等待) | 否(需应用层自行轮询) | 低(检查条件返回错误码) |
| poll/select/epoll | 在 poll/select/epoll_wait 系统调用内 |
高(一次阻塞等待多个 fd) | 是 | 中(实现 .poll 回调 + 等待队列) |
1.6 本课程的学习目标
完成本课程后,你将能够:
- 理解原理: 清晰阐述阻塞 IO、非阻塞 IO 和 poll 机制在内核中的实现原理。
- 编写驱动: 在字符设备驱动中正确实现
read()、write()的阻塞/非阻塞版本,以及.poll()回调函数。 - 使用等待队列: 熟练运用
wait_queue_head_t、wait_event_interruptible()、wake_up_interruptible()等内核 API。 - 调试与验证: 编写用户态测试程序,使用
O_NONBLOCK标志和poll()系统调用验证驱动行为。 - 性能考量: 分析不同 IO 模型对系统吞吐量和响应延迟的影响,做出合理的设计选择。
1.7 前置知识要求
为了顺利学习本课程,你需要具备以下基础:
| 领域 | 具体要求 |
|---|---|
| C 语言 | 指针、结构体、函数指针、链表操作 |
| Linux 基础 | 文件操作(open/read/write/close)、进程概念、系统调用流程 |
| 内核编程基础 | 模块加载/卸载、file_operations 结构体、cdev 注册、copy_to_user/copy_from_user |
| 并发与同步 | 自旋锁、互斥锁、信号量的基本使用(本课程会涉及等待队列与锁的配合) |
| 实验环境 | 一台运行 Linux 的机器(虚拟机亦可),具备内核头文件、gcc、make、insmod/rmmod 权限 |
学习建议: 如果你对“前置知识”中的内核编程基础部分还不熟悉,建议先复习字符设备驱动的注册流程和基本的
read/write 实现。本课程将在这些基础上逐步引入阻塞、非阻塞和 poll 机制。
1.8 课程知识结构总览
下面的 SVG 图展示了本课程各章节之间的逻辑关系,帮助你建立全局视角。
1.9 本章小结
本章作为课程的导论,建立了三个核心概念:
- 阻塞 IO: 进程睡眠等待,适合单一设备、低延迟场景。
- 非阻塞 IO: 立即返回,适合需要保持控制权或轮询多个设备的场景。
- poll 机制: 多路复用,一个进程高效监视多个设备。