技术知识库
O_NONBLOCK 如何改变字符设备读写语义
O_NONBLOCK 如何改变字符设备读写语义
摘要
O_NONBLOCK 是一次 open() 所产生文件对象的状态标志,不是整个设备的固定属性。字符设备驱动通过 file->f_flags & O_NONBLOCK 判断本次访问是否允许等待:FIFO 暂时为空或已满时,非阻塞访问应立即返回 -EAGAIN。这与返回 EOF 的 0、表示固定容量耗尽的 -ENOSPC 含义不同。
问题/背景
基础 KFIFO 驱动本来就没有主动睡眠,但这不等于它已经实现 Linux 非阻塞 I/O 协议。实验 6-5 的目标是让同一设备根据打开方式返回不同的用户空间可见结果。
核心机制
标志属于本次打开
用户程序可以分别打开同一设备:
open("/dev/kfifo_mailbox", O_RDONLY);
open("/dev/kfifo_mailbox", O_RDONLY | O_NONBLOCK);
驱动在对应 struct file 中检查:
if (file->f_flags & O_NONBLOCK)
/* 本次访问不能睡眠等待 */
因此“设备是否非阻塞”不是一个全局开关,同一时刻可以存在普通文件描述符和非阻塞文件描述符。
空 FIFO 的读取
if (kfifo_is_empty(&fifo) &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
- 返回
0:EOF,表示没有更多数据。 - 返回
-EAGAIN:现在暂时没有数据,稍后重试可能成功。
满 FIFO 的写入
if (kfifo_is_full(&fifo) &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
- 返回正数:本次实际写入的字节数;即使小于请求长度,也是有效的部分写入。
- 返回
-EAGAIN:当前一字节也无法写入,但读取者释放空间后可能成功。 - 返回
-ENOSPC:当前教学版用于普通打开且 FIFO 已满时终止零进展重试的临时策略。
EAGAIN 的用户空间表现
内核中的 -EAGAIN 到达用户空间后,系统调用返回 -1,同时设置:
errno = EAGAIN;
命令行工具通常显示:
Resource temporarily unavailable
这句话的重点是“temporarily”:它不是永久故障。
dd 的 iflag 与 oflag
iflag=nonblock → 输入文件以 O_NONBLOCK 打开 → 测试 read()
oflag=nonblock → 输出文件以 O_NONBLOCK 打开 → 测试 write()
若把 /dev/kfifo_mailbox 放在 of=,却只指定 iflag=nonblock,非阻塞标志会作用于输入文件而不是设备,因此驱动的非阻塞写分支不会执行。
当前仍不是真正的阻塞驱动
本实验只建立非阻塞分支。没有 O_NONBLOCK 时,当前教学版:
- 空 FIFO 读取仍返回 0。
- 满 FIFO 写入仍返回
-ENOSPC。
真正的阻塞模式应让进程进入可中断睡眠,并在数据或空间出现时唤醒;这需要等待队列,是后续实验的目标。
证据与来源
- Running Linux Kernel 5.0:
include/uapi/asm-generic/fcntl.h中的O_NONBLOCK。 - Running Linux Kernel 5.0:
include/linux/fs.h中的struct file::f_flags。 - 实验手册 v2.0.7,第 6.5 节“把虚拟设备驱动改成非阻塞模式”。
- 实际验证:满 FIFO 以
oflag=nonblock再写1字节,dd显示Resource temporarily unavailable,退出状态为1。
关联
- 字符设备驱动 MOC
- KFIFO 如何用环形缓冲区管理字符设备数据
- 共享设备状态与每次 open 私有状态如何决定进程能否互相唤醒
- 扩展:为 KFIFO 邮箱加入 Nonblocking I/O
- dd 测试非阻塞写时误用了 iflag
待验证
- 空 FIFO 使用
iflag=nonblock读取并观察EAGAIN。