4. 非阻塞IO在内核中的实现:O_NONBLOCK标志、驱动中如何检查并返回-EAGAIN
非阻塞IO是驱动开发中处理高速数据流或避免进程挂起的核心机制。当用户空间以 O_NONBLOCK 标志打开设备文件时,驱动必须放弃“等待数据就绪”的传统阻塞行为,转而立即返回一个特殊错误码 -EAGAIN,告知应用层“现在没有数据,请稍后再试”。本章深入剖析内核侧的实现细节。
4.1 O_NONBLOCK标志的传递路径
O_NONBLOCK 标志从用户空间的 open() 系统调用开始,经过虚拟文件系统(VFS)层,最终存入文件描述符对应的 struct file 结构体的 f_flags 字段。驱动开发者无需关心标志的传递过程,只需在读写方法中检查该字段即可。
关键数据结构:
struct file 中的 unsigned int f_flags 成员存储了打开文件时的所有标志,包括 O_NONBLOCK。
4.2 驱动中检查 O_NONBLOCK 的标准方法
在驱动的 read() 和 write() 方法中,通过检查 filp->f_flags & O_NONBLOCK 来判断当前操作是否为非阻塞模式。如果条件成立且数据未就绪(或缓冲区满),则立即返回 -EAGAIN。
// 驱动 read 方法中的典型检查
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
// 检查是否有数据可读
if (dev->data_available == 0) {
// 非阻塞模式:立即返回 -EAGAIN
if (filp->f_flags & O_NONBLOCK) {
return -EAGAIN;
}
// 阻塞模式:等待数据就绪
if (wait_event_interruptible(dev->read_queue,
dev->data_available != 0)) {
return -ERESTARTSYS;
}
}
// 执行实际的数据拷贝
// ...
return bytes_read;
}
最佳实践: 始终在检查数据是否就绪 之后、在可能引起阻塞的等待操作 之前 检查
O_NONBLOCK 标志。这样可以避免不必要的资源消耗。
4.3 -EAGAIN 与 -EWOULDBLOCK 的关系
在 Linux 内核中,-EAGAIN 和 -EWOULDBLOCK 在数值上完全相等(均为 11),且语义几乎相同。驱动开发者通常使用 -EAGAIN,但两种写法在内核中均可接受。
| 错误码 | 数值 | 含义 | 推荐使用场景 |
|---|---|---|---|
-EAGAIN |
11 | 资源暂时不可用,请重试 | 非阻塞 IO 操作、资源锁获取失败 |
-EWOULDBLOCK |
11 | 操作会阻塞,但当前不允许阻塞 | 非阻塞 socket 操作(网络子系统常用) |
注意: 虽然两者数值相同,但在跨平台代码或用户空间库中,某些实现可能只检查
EWOULDBLOCK。为保险起见,驱动中统一使用 -EAGAIN 即可。
4.4 非阻塞 write 的实现要点
非阻塞写操作与读操作对称,但检查的是“缓冲区是否可写”。当写缓冲区已满且设置了 O_NONBLOCK 时,驱动应返回 -EAGAIN。
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
size_t space_avail = dev->buf_size - dev->write_pos;
if (space_avail == 0) {
// 非阻塞模式:缓冲区满,立即返回
if (filp->f_flags & O_NONBLOCK) {
return -EAGAIN;
}
// 阻塞模式:等待缓冲区有空闲空间
if (wait_event_interruptible(dev->write_queue,
space_avail > 0)) {
return -ERESTARTSYS;
}
}
// 执行数据写入
// ...
return bytes_written;
}
4.5 非阻塞 IO 的完整决策流程
下图展示了驱动在非阻塞模式下处理读写请求的完整决策树:
4.6 常见陷阱与避坑指南
- 检查时机错误: 在等待队列上休眠 之后 才检查
O_NONBLOCK,导致进程已经休眠,违背非阻塞语义。 - 遗漏写操作检查: 只对
read()实现了非阻塞逻辑,而write()中忘记检查,导致写操作在缓冲区满时仍然阻塞。 - 错误码混淆: 在非阻塞模式下返回
-EINVAL或-ENODATA,用户空间无法正确识别“资源暂时不可用”的语义。 - 忽略
O_NONBLOCK在open()中的影响: 某些驱动在open()中也需要检查filp->f_flags,例如阻止非阻塞方式打开独占设备。
内核调试技巧: 使用
strace 工具跟踪系统调用,观察返回值是否为 EAGAIN (11)。如果应用层收到 Resource temporarily unavailable 错误,说明驱动正确实现了非阻塞逻辑。
4.7 本章小结
非阻塞IO的实现核心在于两点:检查 filp->f_flags & O_NONBLOCK 和 返回 -EAGAIN。驱动开发者需要将这一检查逻辑嵌入到读写方法的正确位置——在确认资源不可用之后、在进入休眠之前。掌握这一模式后,后续学习 poll 机制时将能更深刻地理解“事件通知”与“非阻塞轮询”的协同关系。