【Linux驱动开发】Linux 设备驱动中的阻塞与非阻塞 I/O:机制、源码与示例

Linux 设备驱动中的阻塞与非阻塞 I/O:机制、源码与示例

1. 基本概念与区别

  • 阻塞 I/O:当资源不可用时,调用方进入睡眠(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE),直到条件满足或被信号打断后返回。
  • 非阻塞 I/O:当资源不可用时,系统调用立即返回(通常 -EAGAIN),不发生睡眠,由用户态自行决定重试或走多路复用。
  • 关键差异:
    • 进程状态:阻塞路径发生 TASK_RUNNING → TASK_* 的转换;非阻塞路径保持运行态。
    • 系统调用行为:阻塞路径等待条件成立;非阻塞路径迅速返回错误码。

2. 底层实现机制

等待队列 (wait_queue)

  • 类型与初始化:wait_queue_head_tinit_waitqueue_head()include/linux/wait.h:39-79)。
  • 睡眠与唤醒:wait_event*() 系列宏执行入队、设置任务睡眠态与调度;wake_up*() 负责唤醒等待者(include/linux/wait.h:165-175, 399-425)。
  • 内核实现:入队/出队与通用唤醒逻辑在 kernel/sched/wait.c:14-22, 65-77, 89-98, 171-183, 199-220

file_operations 中的相关方法

  • read/writeO_NONBLOCK:驱动读取 file->f_flags & O_NONBLOCK 决定是否走非阻塞路径(drivers/char/random.c:1456-1460drivers/char/rtc.c:370-377)。
  • poll/select/epoll:驱动 .poll 必须调用 poll_wait(file, &wait_queue, wait) 将等待队列登记到调用方,依据资源状态返回掩码(include/linux/poll.h:42-46)。

调度器与进程状态转换

  • 阻塞路径设置 TASK_INTERRUPTIBLE/UNINTERRUPTIBLE 并调度;唤醒后恢复运行。相关实现与内存序保证见 kernel/sched/wait.c:159-170, 171-183

3. 驱动开发实践

阻塞模式实现步骤

  1. 初始化等待队列头:init_waitqueue_head(&wq)(示例:drivers/char/example_blocking.c:103-105)。
  2. 检查资源并判断条件(示例:drivers/char/example_blocking.c:29-40)。
  3. 睡眠等待:wait_event_interruptible(wq, condition),被信号打断返回 -ERESTARTSYS(示例:drivers/char/example_blocking.c:38-40,参照 drivers/char/random.c:1448-1453)。
  4. 唤醒机制:资源状态变化后调用 wake_up_interruptible(&wq)(示例写路径:drivers/char/example_blocking.c:73-74)。

非阻塞模式实现要点

  • 检查 O_NONBLOCKfile->f_flags & O_NONBLOCKdrivers/char/example_blocking.c:35-37)。
  • 返回 -EAGAIN:资源不可用立即返回(drivers/char/random.c:1445-1447drivers/char/rtc.c:370-373)。
  • 即时检测逻辑:快速判断可读/可写并返回,避免睡眠和忙等。

.poll/select/epoll 实现

  • .poll 中调用 poll_wait(file, &wq, wait) 登记等待队列(drivers/char/example_blocking.c:80-81)。
  • 返回掩码:可读返回 POLLIN|POLLRDNORM,可写返回 POLLOUT|POLLWRNORM(示例:drivers/char/example_blocking.c:82-87;参考 drivers/char/random.c:1492-1496drivers/char/rtc.c:800-809)。

4. 性能与适用场景分析

  • 阻塞 I/O:
    • 优点:避免忙等,CPU 利用率高;适合低吞吐但需要正确同步的场景。
    • 缺点:响应受资源就绪时间影响,存在上下文切换。
  • 非阻塞 I/O:
    • 优点:适合实时与事件驱动;配合 poll/select/epoll 多路复用。
    • 缺点:用户态需编排重试与回退,不当实现可能忙等。
  • 混合策略:同时提供阻塞与非阻塞路径,并实现 .poll;用户空间以 select/poll/epoll 统一管理,参照 /dev/randomdrivers/char/random.c:1425-1460, 1484-1497, 1591-1599)。

5. 用户空间接口

  • 设置非阻塞:open(path, O_NONBLOCK)fcntl(fd, F_SETFL, O_NONBLOCK)
  • 行为表现:
    • 阻塞读:无数据时睡眠;收到信号返回 -ERESTARTSYSdrivers/char/random.c:1451-1453drivers/char/rtc.c:374-377)。
    • 非阻塞读:无数据立刻返回 -EAGAIN
    • poll/select/epoll:驱动 .poll 返回掩码,内核登记等待队列并在事件到来时唤醒调用方(include/linux/poll.h:42-46)。

6. 调试与问题排查

  • 常见死锁:持有不可睡眠锁(自旋锁)期间调用睡眠原语;应在睡眠前释放锁或使用合适锁策略。示例代码采用 mutex,在睡眠前释放(drivers/char/example_blocking.c:33-40)。
  • 竞态防护:在持锁下修改资源状态并确保等待/唤醒条件一致,之后再唤醒;等待宏内部已有必要的内存序(kernel/sched/wait.c:86-96)。
  • 核查要点:.poll 必须调用 poll_wait;错误码(-EAGAIN/-ERESTARTSYS)符合约定;唤醒路径触发预期事件掩码。

7. 源码对照与示例

/dev/random

  • 阻塞读与 O_NONBLOCKdrivers/char/random.c:1425-1460(非阻塞 -EAGAIN1445-1447;信号中断:1451-1453)。
  • .polldrivers/char/random.c:1484-1497
  • file_operationsdrivers/char/random.c:1591-1599

/dev/rtc

  • .read 阻塞与非阻塞:drivers/char/rtc.c:329-394
  • .polldrivers/char/rtc.c:793-809

/dev/mem

  • 直接读写物理内存,无等待队列与睡眠:drivers/char/mem.c:102-173, 174-243

等待队列与 poll API

  • wait_event_interruptible 宏:include/linux/wait.h:399-425
  • wake_up_interruptible 宏:include/linux/wait.h:171-175
  • poll_waitinclude/linux/poll.h:42-46
  • 核心实现:kernel/sched/wait.c:14-22, 65-77, 89-98, 171-183, 199-220

8. 典型驱动示例(已集成)

  • 文件:drivers/char/example_blocking.c
  • 关键路径:
    • 阻塞/非阻塞读:drivers/char/example_blocking.c:22-51
    • 写入与唤醒:drivers/char/example_blocking.c:53-75
    • .polldrivers/char/example_blocking.c:77-88
    • 等待队列初始化与设备注册:drivers/char/example_blocking.c:103-123

9. 构建与启用

  • 配置项:CONFIG_EXAMPLE_BLOCKINGdrivers/char/Kconfig)。
  • Makefile 目标:drivers/char/Makefile:63
  • 设备节点:启用 devtmpfs/udev 自动生成 /dev/exblk;或手动创建:
    • mknod /dev/exblk c $(grep exblk /proc/devices | awk '{print $1}') 0

10. 用户态测试程序

  • 文件:samples/exblk/exblk_test.c
  • 编译:
    • cc -O2 -Wall -o exblk_test samples/exblk/exblk_test.c
  • 运行与验证:
    • 非阻塞读:程序启动即测试 O_NONBLOCK 路径(预期 EAGAIN)。
    • poll/epoll:在另一终端执行 echo "hello" > /dev/exblk 触发事件;程序读取并打印内容。
    • 阻塞读:提示后执行写入,可观察阻塞到事件到来。
相关推荐
yolo_guo2 小时前
opencv 学习: QA_02 什么是图像中的高频成分和低频成分
linux·c++·opencv·计算机视觉
q***56383 小时前
在 Ubuntu 22.04 上安装和配置 Nginx 的完整指南
linux·nginx·ubuntu
大聪明-PLUS3 小时前
Linux 中的 CPU。文章 1. 利用率
linux·嵌入式·arm·smarc
热爱编程的OP4 小时前
Linux进程池与管道通信详解:从原理到实现
linux·开发语言·c++
想学好C++的oMen5 小时前
文件基础IO
linux·服务器
福旺旺9 小时前
Linux——解压缩各类文件
linux
MasterLi802311 小时前
我的读书清单
android·linux·学习
ha204289419411 小时前
Linux操作系统学习之---初识网络
linux·网络·学习
飞凌嵌入式12 小时前
【玩转多核异构】T153核心板RISC-V核的实时性应用解析
linux·嵌入式硬件·嵌入式·risc-v