技术知识库

FASYNC 与 SIGIO 如何实现字符设备异步通知

FASYNC 与 SIGIO 如何实现字符设备异步通知

摘要

FASYNC 是字符设备向用户进程发送就绪通知的一种机制。用户程序用 fcntl() 指定信号接收者并打开 O_ASYNC/FASYNC,VFS 调用驱动的 .fasync 回调登记进程;数据到达后驱动调用 kill_fasync() 发送 SIGIO,用户程序收到通知后再调用 read() 消费数据。

用户空间登记

fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETSIG, SIGIO);
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
  • F_SETOWN:指定接收异步通知的进程。
  • F_SETSIG:选择通知信号,本实验使用 SIGIO。
  • F_SETFL | FASYNC:启用异步通知,触发驱动的 .fasync 回调。

驱动登记

驱动为设备保存 FASYNC 订阅链表头:

struct fasync_struct *fasync;

回调通过内核辅助函数维护订阅关系:

static int demodrv_fasync(int fd, struct file *file, int on)
{
	struct mydemo_device *device = file->private_data;

	return fasync_helper(fd, file, on, &device->fasync);
}

数据到达时通知

if (!kfifo_is_empty(&device->fifo))
	kill_fasync(&device->fasync, SIGIO, POLL_IN);

kill_fasync() 不会替进程读取 KFIFO。它只告诉订阅者“设备现在可读”,用户程序的信号处理路径或主循环随后调用 read()。

已验证调用序列

echo(pid 315)
  → demodrv_write(actual_write=13)
  → kill_fasync(SIGIO, POLL_IN)
  → test(pid 389) 收到 SIGIO
  → read()
  → demodrv_read(actual_readed=13)

写入和读取均为 13 字节,证明通知和数据消费闭环成功。

信号处理的工程边界

实验手册的最小程序直接在信号处理函数中调用 printf() 和 read(),便于观察流程,但通用应用应尽量让处理函数只设置 sig_atomic_t 标志,再由主循环读取。许多 libc 函数不是异步信号安全函数。

关闭文件前也应撤销异步登记,避免设备保留失效订阅关系。驱动通常在 .release 路径调用相同的 .fasync 回调并传入 on=0。

证据与来源

  • 实验手册 v2.0.7,第 6.9 节,印刷页码 162~172。
  • 驱动参考代码:kmodules/rlk_lab/rlk_basic/chapter_6_device_driver/lab9_mydemodrv_fasync/mydemodrv_fasync.c。
  • Linux 5.0:fs/fcntl.c::fasync_helper()、fasync_insert_entry()。
  • 实际平台:Linux 5.0.0+ ARM64,QEMU virt,Debian 12。

关联

待验证

  • 后续用 GDB 完成未初始化 device->fasync 的故障定位练习。