技术知识库
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。
关联
- 字符设备驱动 MOC
- poll 与 epoll 如何通过等待队列发现设备就绪
- 共享设备状态与每次 open 私有状态如何决定进程能否互相唤醒
- 使用 SIGIO 实现字符设备异步通知
- 设备节点缺失或设备号失配导致 FASYNC 测试失败
- 未初始化 fasync 指针可能导致 fasync_insert_entry Oops
待验证
- 后续用 GDB 完成未初始化
device->fasync的故障定位练习。