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() 系统调用时,阻塞与非阻塞两种模式下内核驱动的处理路径差异。

阻塞 IO vs 非阻塞 IO 驱动处理流程 应用程序调用 read(fd, buf, len) 驱动 read() 函数入口 数据就绪? 否 进程加入等待队列 进程睡眠 (TASK_INTERRUPTIBLE) 唤醒后拷贝数据 是 直接拷贝数据到用户空间 应用程序调用 read(fd, buf, len) 驱动 read() 函数入口 数据就绪? 否 立即返回 -EAGAIN 是 拷贝数据到用户空间 返回读取字节数 阻塞 IO:进程睡眠等待 → 唤醒后拷贝 → 返回。非阻塞 IO:条件不满足立即返回 -EAGAIN,条件满足则拷贝后返回。

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 本课程的学习目标

完成本课程后,你将能够:

  1. 理解原理: 清晰阐述阻塞 IO、非阻塞 IO 和 poll 机制在内核中的实现原理。
  2. 编写驱动: 在字符设备驱动中正确实现 read()、write() 的阻塞/非阻塞版本,以及 .poll() 回调函数。
  3. 使用等待队列: 熟练运用 wait_queue_head_t、wait_event_interruptible()、wake_up_interruptible() 等内核 API。
  4. 调试与验证: 编写用户态测试程序,使用 O_NONBLOCK 标志和 poll() 系统调用验证驱动行为。
  5. 性能考量: 分析不同 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 图展示了本课程各章节之间的逻辑关系,帮助你建立全局视角。

《Linux字符设备驱动:阻塞与非阻塞IO与poll机制》课程结构 第1章:课程导论(本章) 第2章:等待队列机制 第3章:阻塞IO驱动实现 第4章:非阻塞IO驱动实现 第5章:poll机制驱动实现 章节依赖关系 • 第2章是第3、4、5章的基础 • 第3章和第4章是并列关系 • 第5章依赖第2章和第3章 • 每章均包含用户态测试程序 • 最后有综合实验章节

1.9 本章小结

本章作为课程的导论,建立了三个核心概念:

  • 阻塞 IO: 进程睡眠等待,适合单一设备、低延迟场景。
  • 非阻塞 IO: 立即返回,适合需要保持控制权或轮询多个设备的场景。
  • poll 机制: 多路复用,一个进程高效监视多个设备。