技术知识库

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。

关联

待验证

  • 空 FIFO 使用 iflag=nonblock 读取并观察 EAGAIN。