云计算百科
云计算领域专业知识百科平台

Linux 异步通知机制(fasync / SIGIO)简析

Linux 异步通知机制(fasync / SIGIO)简析

适用内核版本:2.6.x ~ 6.x


目录

  • 第 1 章 异步通知机制概述
  • 第 2 章 应用层视角:如何接收异步通知
  • 第 3 章 驱动层视角:如何发送异步通知
  • 第 4 章 内核版本演进:从 Device Tree 到 6.x
  • 第 5 章 机制深入解析
  • 第 6 章 最佳实践与常见问题

第 1 章 异步通知机制概述

1.1 什么是异步通知

Linux 异步通知(Asynchronous Notification),又称 Signal-Driven I/O,是一种让内核在设备状态变化时主动通知用户进程的机制。其核心思想是:

将硬件中断的响应模型映射到用户空间进程层面——当外设产生事件(如按键按下、数据到达、DMA 完成)时,驱动程序不等待用户轮询,而是主动向注册了异步通知的进程发送 SIGIO 信号。

该机制规避了轮询的 CPU 浪费,也避免了阻塞 I/O 导致的进程挂起,复用了 Linux 成熟的信号基础设施,体现了内核设计的模块化与复用性原则。

1.2 异步通知 vs 异步 I/O vs 阻塞/非阻塞 I/O

特性维度异步通知 (fasync)POSIX 异步 I/O (AIO)阻塞/非阻塞 I/O
触发主体 驱动在资源就绪时主动发起 应用主动发起 I/O 请求 应用主动发起读写
核心机制 信号 (SIGIO) io_submit() / io_getevents() read() / write()
回调执行 用户空间信号处理函数 内核完成 I/O 后返回结果 无回调,直接返回
资源访问 仅通知"资源可用",后续仍需显式 read()/write() 内核自动完成数据搬运 应用自己完成数据搬运
适用场景 状态变化检测(按键、传感器、串口触发) 高吞吐量磁盘/网络 I/O 简单同步数据流

⚠️ 重要区分:O_ASYNC(曾用名 FASYNC)启用的是信号驱动 I/O(通知"就绪"),而非 POSIX AIO(通知"完成")。两者名称相似但语义完全不同。

1.3 整体架构

在这里插入图片描述

上图展示了异步通知的四层架构:

  • 应用层:通过 signal() + fcntl() 完成注册与启用;
  • 内核层:VFS 维护 filp->f_owner 与 filp->f_flags,信号子系统负责投递;
  • 驱动层:实现 .fasync 方法,在中断中调用 kill_fasync() 发送信号;
  • 硬件层:外设通过中断控制器向 CPU 发起中断请求。

  • 第 2 章 应用层视角:如何接收异步通知

    2.1 核心 API 介绍

    2.1.1 signal() / sigaction() —— 注册信号处理函数

    #include <signal.h>

    /* 方式一:signal() —— 简单但可移植性较弱 */
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);

    /* 方式二:sigaction() —— 推荐,支持 SA_SIGINFO 获取 siginfo_t */
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    参数说明:

    • signum:信号编号,异步通知固定使用 SIGIO(编号 29 on x86,23 on ARM)。
    • handler:信号处理函数。使用 sigaction 且设置 SA_SIGINFO 时,可接收 siginfo_t * 参数,从中获取触发信号的 fd(si_fd)与事件类型(si_band)。

    返回值:成功返回 0;失败返回 -1 并设置 errno。

    2.1.2 fcntl() —— 设置文件属主与 FASYNC 标志

    #include <fcntl.h>

    int fcntl(int fd, int cmd, ... /* arg */ );

    关键命令:

    命令作用示例
    F_SETOWN 设置接收 SIGIO 的进程/进程组 PID fcntl(fd, F_SETOWN, getpid())
    F_GETFL 获取文件状态标志 fcntl(fd, F_GETFL)
    F_SETFL 设置文件状态标志(或上 FASYNC/O_ASYNC) fcntl(fd, F_SETFL, flags | FASYNC)
    F_SETSIG 指定替代 SIGIO 的实时信号(可选) fcntl(fd, F_SETSIG, SIGRTMIN)

    注意:FASYNC 与 O_ASYNC 在 Linux 下语义等价,glibc 将 FASYNC 定义为 O_ASYNC 的别名。但 O_ASYNC 是标准名称,建议优先使用。

    2.2 应用层启用异步通知的三步骤

    /* 步骤 1:注册 SIGIO 信号处理函数 */
    signal(SIGIO, sigio_handler); // 或使用 sigaction()

    /* 步骤 2:设置当前进程为文件属主 */
    fcntl(fd, F_SETOWN, getpid());

    /* 步骤 3:启用 FASYNC 标志 */
    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);

    完成以上三步后,一旦驱动调用 kill_fasync() 发送 SIGIO,当前进程就会进入 sigio_handler。

    2.3 完整应用示例

    /* app_async_notify.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/types.h>

    static volatile int async_flag = 0;
    static int dev_fd = 1;

    /* SIGIO 信号处理函数 */
    static void sigio_handler(int sig, siginfo_t *info, void *context)
    {
    (void)sig;
    (void)context;

    /* 通过 siginfo_t 获取触发源 fd 与事件类型 */
    if (info) {
    printf("[APP] Received SIGIO, si_fd=%d, si_band=%ld\\n",
    info->si_fd, info->si_band);
    }
    async_flag = 1;
    }

    int main(int argc, char *argv[])
    {
    char buf[256];
    struct sigaction sa;

    if (argc < 2) {
    fprintf(stderr, "Usage: %s <device>\\n", argv[0]);
    return EXIT_FAILURE;
    }

    /* 打开设备 */
    dev_fd = open(argv[1], O_RDWR);
    if (dev_fd < 0) {
    perror("open");
    return EXIT_FAILURE;
    }

    /* 步骤 1:使用 sigaction 注册,带 SA_SIGINFO 可获取详细信息 */
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sigio_handler;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGIO, &sa, NULL) < 0) {
    perror("sigaction");
    close(dev_fd);
    return EXIT_FAILURE;
    }

    /* 步骤 2:设置属主 */
    if (fcntl(dev_fd, F_SETOWN, getpid()) < 0) {
    perror("fcntl F_SETOWN");
    close(dev_fd);
    return EXIT_FAILURE;
    }

    /* 步骤 3:启用 FASYNC */
    int flags = fcntl(dev_fd, F_GETFL);
    if (fcntl(dev_fd, F_SETFL, flags | FASYNC) < 0) {
    perror("fcntl F_SETFL FASYNC");
    close(dev_fd);
    return EXIT_FAILURE;
    }

    printf("[APP] Async notification enabled, waiting for SIGIO…\\n");

    /* 主循环:非阻塞执行其他任务,仅在收到信号时读取 */
    while (1) {
    /* 模拟其他业务逻辑 */
    for (int i = 0; i < 10 && !async_flag; i++) {
    usleep(100000); /* 100ms */
    putchar('.'); fflush(stdout);
    }

    if (async_flag) {
    async_flag = 0;
    memset(buf, 0, sizeof(buf));
    int n = read(dev_fd, buf, sizeof(buf) 1);
    if (n > 0) {
    printf("\\n[APP] Read %d bytes: %s\\n", n, buf);
    }
    }
    }

    close(dev_fd);
    return 0;
    }

    2.4 应用层使用注意事项

  • 边缘触发(Edge-Triggered)风险:SIGIO 是边缘触发的。若收到信号后未将数据全部读走,剩余数据不会再次触发 SIGIO,程序将永远挂起。收到信号后必须循环读取直到返回 EAGAIN。

  • 信号处理函数限制:信号处理函数处于异步上下文,只能调用**异步信号安全(async-signal-safe)**函数。禁止在信号处理函数中调用 malloc()、printf()(标准 I/O 非安全)、pthread_mutex_lock() 等。示例中的 printf 仅用于演示,生产环境应使用 write(STDOUT_FILENO, …) 或设置标志位由主循环处理。

  • 多线程环境:F_SETOWN 设置的是进程级属主。若使用 F_SETOWN_EX(Linux 2.6.32+),可精确指定线程 ID(TID),避免信号被错误线程接收。

  • 实时信号替代:通过 fcntl(fd, F_SETSIG, SIGRTMIN) 可改用实时信号(SIGRTMIN~SIGRTMAX),支持排队与携带 siginfo_t,避免标准信号丢失问题。

  • 文件关闭前清理:应用退出前只需 close(fd),驱动的 release() 会自动清理异步队列。但显式先清除 FASYNC 标志是更安全的做法。


  • 第 3 章 驱动层视角:如何发送异步通知

    3.1 核心数据结构

    3.1.1 struct fasync_struct

    /* include/linux/fs.h */
    struct fasync_struct {
    int magic; /* 魔数校验:FASYNC_MAGIC */
    int fa_fd; /* 关联的文件描述符 */
    struct fasync_struct *fa_next; /* 异步队列链表指针 */
    struct file *fa_file; /* 关联的 struct file 指针 */
    };

    每个打开该设备的进程都会在内核中对应一个 fasync_struct 节点,驱动通过链表管理所有等待异步通知的进程。

    3.1.2 struct fown_struct

    位于 struct file 中的 f_owner 字段,记录了接收信号的进程/进程组信息:

    struct fown_struct {
    rwlock_t lock; /* 5.x+ 引入读写锁细化并发 */
    struct pid *pid; /* 目标进程 */
    enum pid_type pid_type; /* PIDTYPE_PID / PIDTYPE_PGID */
    uid_t uid, euid;
    int signum; /* F_SETSIG 指定的信号,0 表示默认 SIGIO */
    };

    3.2 核心 API 介绍

    3.2.1 fasync_helper() —— 异步队列管理

    #include <linux/fs.h>

    int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp);

    功能:当用户空间通过 fcntl 改变 FASYNC 标志时,内核调用驱动的 .fasync 方法,驱动再将参数透传给 fasync_helper,由后者完成 fasync_struct 节点的分配/释放与链表维护。

    参数:

    • fd:文件描述符(来自 .fasync 方法参数);
    • filp:struct file 指针(来自 .fasync 方法参数);
    • on:非 0 表示添加节点,0 表示删除节点;
    • fapp:指向驱动中 struct fasync_struct * 指针的地址。

    返回值:

    • > 0:成功添加或删除了节点;
    • 0:未做任何修改;
    • < 0:内存分配失败(-ENOMEM)。
    3.2.2 kill_fasync() —— 发送异步信号

    #include <linux/fs.h>

    void kill_fasync(struct fasync_struct **fp, int sig, int band);

    功能:遍历 fp 指向的异步队列,向每个注册的进程发送指定信号。

    参数:

    • fp:指向驱动中 struct fasync_struct * 指针的地址(即 &dev->async_queue);
    • sig:信号编号,通常为 SIGIO;
    • band:事件类型,常用 POLL_IN(可读)、POLL_OUT(可写)、POLL_ERR(错误)。

    注意:kill_fasync() 内部会调用 send_sigio() → send_sig_info(),最终通过信号子系统将信号插入目标进程的挂起队列。该函数可在中断上下文安全调用。

    3.3 驱动实现四步骤

    /* 步骤 1:在设备结构体中定义 fasync_struct 指针 */
    struct my_dev {
    struct fasync_struct *async_queue;
    /* … 其他字段 … */
    };

    /* 步骤 2:实现 .fasync 方法 */
    static int my_fasync(int fd, struct file *filp, int on)
    {
    struct my_dev *dev = filp->private_data;
    return fasync_helper(fd, filp, on, &dev->async_queue);
    }

    /* 步骤 3:在数据就绪/事件发生时调用 kill_fasync */
    static irqreturn_t my_irq_handler(int irq, void *dev_id)
    {
    struct my_dev *dev = dev_id;
    /* … 处理硬件事件 … */

    if (dev->async_queue)
    kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

    return IRQ_HANDLED;
    }

    /* 步骤 4:在 release() 中清理 */
    static int my_release(struct inode *inode, struct file *filp)
    {
    struct my_dev *dev = filp->private_data;
    /* 参数 fd=-1, on=0 表示强制移除 */
    my_fasync(1, filp, 0);
    return 0;
    }

    static struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .fasync = my_fasync,
    .release = my_release,
    /* … */
    };

    3.4 完整驱动示例

    /* drv_async_notify.c —— 简化版字符设备驱动 */
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/interrupt.h>
    #include <linux/uaccess.h>
    #include <linux/wait.h>
    #include <linux/poll.h>

    #define DEVICE_NAME "async_demo"
    #define CLASS_NAME "async_cls"

    static int major;
    static struct class *async_class;
    static struct device *async_device;
    static struct cdev async_cdev;

    static DECLARE_WAIT_QUEUE_HEAD(read_waitq);
    static volatile int data_ready = 0;
    static char kbuf[256];
    static struct fasync_struct *async_queue = NULL;

    /* === .fasync 方法 === */
    static int async_demo_fasync(int fd, struct file *filp, int on)
    {
    printk(KERN_INFO "[%s] fasync called: fd=%d, on=%d\\n", DEVICE_NAME, fd, on);
    return fasync_helper(fd, filp, on, &async_queue);
    }

    /* === 模拟中断/事件触发 === */
    static ssize_t async_demo_write(struct file *filp, const char __user *buf,
    size_t count, loff_t *pos)
    {
    size_t n = min(count, sizeof(kbuf) 1);

    if (copy_from_user(kbuf, buf, n))
    return EFAULT;
    kbuf[n] = '\\0';
    data_ready = 1;

    /* 唤醒阻塞读 */
    wake_up_interruptible(&read_waitq);

    /* 发送异步通知 */
    if (async_queue) {
    kill_fasync(&async_queue, SIGIO, POLL_IN);
    printk(KERN_INFO "[%s] SIGIO sent to registered processes\\n", DEVICE_NAME);
    }

    return n;
    }

    /* === read 方法 === */
    static ssize_t async_demo_read(struct file *filp, char __user *buf,
    size_t count, loff_t *pos)
    {
    wait_event_interruptible(read_waitq, data_ready);

    size_t n = min(count, strlen(kbuf));
    if (copy_to_user(buf, kbuf, n))
    return EFAULT;

    data_ready = 0;
    return n;
    }

    /* === release 方法:必须清理异步队列 === */
    static int async_demo_release(struct inode *inode, struct file *filp)
    {
    async_demo_fasync(1, filp, 0);
    printk(KERN_INFO "[%s] release: async queue cleaned\\n", DEVICE_NAME);
    return 0;
    }

    static struct file_operations async_demo_fops = {
    .owner = THIS_MODULE,
    .write = async_demo_write,
    .read = async_demo_read,
    .fasync = async_demo_fasync,
    .release = async_demo_release,
    };

    static int __init async_demo_init(void)
    {
    dev_t devno;
    alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
    major = MAJOR(devno);

    cdev_init(&async_cdev, &async_demo_fops);
    cdev_add(&async_cdev, devno, 1);

    async_class = class_create(THIS_MODULE, CLASS_NAME);
    async_device = device_create(async_class, NULL, devno, NULL, DEVICE_NAME);

    printk(KERN_INFO "[%s] loaded, major=%d\\n", DEVICE_NAME, major);
    return 0;
    }

    static void __exit async_demo_exit(void)
    {
    device_destroy(async_class, MKDEV(major, 0));
    class_destroy(async_class);
    cdev_del(&async_cdev);
    unregister_chrdev_region(MKDEV(major, 0), 1);
    printk(KERN_INFO "[%s] unloaded\\n", DEVICE_NAME);
    }

    module_init(async_demo_init);
    module_exit(async_demo_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("Linux Async Notification Demo Driver");

    3.5 驱动层使用注意事项

  • 必须实现 .fasync 方法:若 file_operations 中未提供 .fasync,用户空间设置 FASYNC 将不会生效,内核无法建立异步队列。

  • kill_fasync() 前判空:虽然 kill_fasync() 内部有保护,但显式判断 if (dev->async_queue) 可避免不必要的函数调用开销,且代码语义更清晰。

  • release() 中强制清理:调用 .fasync(-1, filp, 0) 将当前 filp 从异步队列中移除。若遗漏此步骤,进程关闭 fd 后仍可能收到信号,导致 SIGIO 被投递到已不存在的进程上下文。

  • 中断上下文安全:kill_fasync() 内部使用 spin_lock_irqsave(&fasync_lock, …) 保护链表遍历,因此可在硬中断/软中断/进程上下文任意调用。但建议在中断上半部(ISR)中快速调用后立即返回,复杂逻辑移至下半部(tasklet/workqueue)。

  • 多进程并发访问:fasync_helper() 内部使用全局 fasync_lock(自旋锁)保护链表操作。驱动无需额外加锁,但需注意 fasync_struct 指针本身的内存可见性(建议用 struct fasync_struct * 而非裸指针数组)。


  • 第 4 章 内核版本演进:从 Device Tree 到 6.x

    在这里插入图片描述

    4.1 2.6.x 时期:基础框架成熟与 Device Tree 萌芽

    • 2.3.21+:kill_fasync() 引入第三个参数 band(POLL_IN/POLL_OUT),在此之前仅有两个参数(struct fasync_struct *queue, int signal)。
    • 2.6.x:fasync_helper() 与 kill_fasync() 接口完全定型,成为字符设备驱动标准范式。
    • Device Tree 萌芽:2005~2008 年,PowerPC 架构率先引入 Device Tree 以解决嵌入式板级代码臃肿问题。此时 ARM 仍使用硬编码的 arch/arm/mach-*/board-*.c。

    4.2 3.x 时期:Device Tree 标准化与 ARM 架构统一

    • Linux 3.0 (2011):Device Tree 正式合并入主线,成为 ARM SoC 的标准硬件描述方式。
    • 对异步通知的影响:platform_driver 开始通过 of_match_table 绑定 Device Tree 节点。驱动开发者不再需要关心板级差异,.fasync 的实现逻辑与硬件描述解耦,驱动可复用性大幅提升。
    • RCU 保护引入:3.x 后期开始对 fasync 相关链表进行 RCU 读端保护,减少 fasync_lock 自旋锁的争用。

    4.3 4.x 时期:安全增强与实时信号完善

    • 4.14+:send_sigio() 内部实现增强,对 siginfo_t 的构造更加严谨,si_uid 与 si_fd 的填充逻辑修正了早期版本中的重叠问题。
    • 实时信号支持:fcntl(fd, F_SETSIG, rt_signal) 在 4.x 系列中得到完善,实时信号可携带完整的 siginfo_t 信息,解决了标准 SIGIO 不排队、可能丢失的问题。
    • cgroup v1 信号隔离:4.x 引入的 cgroup 机制开始对信号投递进行命名空间级隔离,确保容器内进程不会收到容器外设备的 SIGIO。

    4.4 5.x 时期:并发优化与周边子系统协同

    • 5.x:struct file 中的 f_owner 字段从简单的读写锁保护升级为更细粒度的 rwlock_t,减少了高并发场景下多进程同时注册/注销 FASYNC 的锁争用。
    • lockdep 增强:内核死锁检测工具 lockdep 增加了对 fasync_lock 的依赖图分析,帮助驱动开发者发现潜在的死锁风险。
    • 无核心 API 变更:fasync_helper() 与 kill_fasync() 的函数原型在 5.x 全系列保持不变,体现了内核 API 的向后兼容承诺。

    4.5 6.x 时期:向后兼容与新范式并存

    • 6.x:fasync 机制本身无破坏性变更,保持完全向后兼容。但内核社区更推荐使用 io_uring + poll 作为高性能异步事件通知的新范式。
    • cgroup v2 信号隔离:6.x 进一步完善了 cgroup v2 下的信号投递策略,支持通过 cgroup.subtree_control 对 SIGIO 进行层级隔离。
    • BPF 与 fasync:6.x 引入了更多 BPF 钩子点,允许在信号投递路径上挂载 BPF 程序进行审计或过滤,为安全敏感场景提供了新选择。

    4.6 版本差异速查表

    版本fasync_helperkill_fasync.fasync 方法签名关键变化
    2.0 inode 参数 两参数(无 band) int (*fasync)(struct inode*, struct file*, int) 早期实现
    2.3.21+ 稳定接口 三参数(+band) int (*fasync)(int fd, struct file*, int) band 参数引入
    2.6.x 标准接口 POLL_IN/OUT 同上 设备树萌芽(PowerPC)
    3.x+ RCU 保护 send_sigio 增强 同上 ARM DT 标准化
    4.14+ fasync_lock siginfo_t 完善 同上 安全与实时性增强
    5.x+ 无变化 无变化 同上 周边子系统优化
    6.x+ 向后兼容 向后兼容 同上 io_uring 替代趋势

    第 5 章 机制深入解析

    5.1 SIGIO 信号投递完整流程

    在这里插入图片描述

    上图将信号投递划分为四个阶段:

    阶段 1 —— 硬件触发:外设产生事件 → 中断控制器(GIC/APIC)分发 → CPU 响应 IRQ → 进入 ISR(关中断/保存上下文)。

    阶段 2 —— 驱动响应:ISR 读取状态寄存器确认事件源 → 调用 kill_fasync() → 遍历 fasync_struct 异步队列链表 → 获取 filp->f_owner 中的目标进程 PID。

    阶段 3 —— 内核投递:send_sigio() 构造 siginfo_t(填充 si_signo=SIGIO、si_code=SI_SIGIO、si_fd=fa_fd、si_band=POLL_IN)→ send_sig_info() 将信号插入目标进程的 shared_pending 队列 → 进程调度时检查并递送信号。

    阶段 4 —— 用户处理:进程从内核态返回用户态 → do_signal() 处理挂起信号 → 调用用户注册的 SIGIO 信号处理函数 → 用户程序执行 read() 或业务逻辑。

    5.2 fasync_helper() 内部实现要点

    /* 简化逻辑,基于 4.14+ 内核 */
    int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
    {
    struct fasync_struct *fa, **fp;
    struct fasync_struct *new = NULL;
    int result = 0;

    if (on) {
    new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
    if (!new)
    return ENOMEM;
    }

    write_lock_irq(&fasync_lock); /* 全局自旋锁保护 */
    for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
    if (fa->fa_file == filp) { /* 已存在该 filp 的节点 */
    if (on) {
    fa->fa_fd = fd; /* 更新 fd */
    kmem_cache_free(fasync_cache, new);
    } else {
    *fp = fa->fa_next; /* 从链表摘除 */
    kmem_cache_free(fasync_cache, fa);
    result = 1;
    }
    goto out;
    }
    }
    if (on) {
    new->magic = FASYNC_MAGIC; /* 魔数校验,防止内存损坏 */
    new->fa_file = filp;
    new->fa_fd = fd;
    new->fa_next = *fapp;
    *fapp = new;
    result = 1;
    }
    out:
    write_unlock_irq(&fasync_lock);
    return result;
    }

    关键设计:

    • 使用 slab 缓存(fasync_cache)分配节点,避免频繁调用通用内存分配器;
    • 使用 全局自旋锁 fasync_lock(write_lock_irq)保证链表操作的原子性;
    • 通过 fa_file == filp 匹配节点,确保每个 struct file 实例在链表中唯一。

    5.3 边缘触发特性与数据排空

    SIGIO 属于**边缘触发(Edge-Triggered)**通知:

    仅当设备状态从"未就绪"跳变到"就绪"时发送一次信号。若应用未将数据全部读走,状态不会回退到"未就绪",因此不会产生第二次跳变,也就不会有新的 SIGIO。

    正确做法:

    static void sigio_handler(int sig)
    {
    int flags = fcntl(dev_fd, F_GETFL);
    fcntl(dev_fd, F_SETFL, flags | O_NONBLOCK); /* 设为非阻塞 */

    while (1) {
    char buf[256];
    int n = read(dev_fd, buf, sizeof(buf));
    if (n < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK)
    break; /* 数据已排空 */
    /* 其他错误处理 */
    break;
    }
    /* 处理 buf 中的 n 字节数据 */
    }
    }


    第 6 章 最佳实践与常见问题

    6.1 最佳实践清单

    场景推荐做法
    信号注册 优先使用 sigaction() + SA_SIGINFO,避免 signal() 的可移植性问题
    多线程 使用 F_SETOWN_EX 精确指定 TID,或统一由主线程接收后分发
    信号安全 信号处理函数中仅修改 volatile sig_atomic_t 标志,主循环执行 I/O
    数据排空 收到 SIGIO 后循环 read() 直到 EAGAIN,防止边缘触发漏事件
    驱动实现 .fasync 方法必须透传参数给 fasync_helper();release() 中必须调用 .fasync(-1, filp, 0)
    实时性要求 使用 fcntl(F_SETSIG, SIGRTMIN) 替代 SIGIO,支持信号排队
    高并发 考虑 epoll + O_NONBLOCK 或 io_uring 替代 fasync,减少信号开销

    6.2 常见问题 FAQ

    Q1:应用设置了 FASYNC,但收不到 SIGIO?

    排查步骤:

  • 驱动是否实现了 .fasync 方法?
  • fasync_helper() 返回值是否检查?
  • 数据就绪时是否调用了 kill_fasync()?
  • fcntl(F_SETOWN) 设置的 PID 是否正确?
  • 是否被信号掩码阻塞(sigprocmask)?
  • Q2:kill_fasync() 可以在中断上下文调用吗?

    可以。kill_fasync() → send_sigio() → send_sig_info() 全程使用自旋锁与 RCU,不调用可能睡眠的函数,因此可在硬中断、软中断、tasklet 中安全调用。

    Q3:多个进程同时打开设备,都能收到 SIGIO 吗?

    可以。fasync_helper() 为每个 filp 创建一个 fasync_struct 节点,所有节点挂在同一条链表上。kill_fasync() 会遍历链表向每个节点对应的进程发送信号。

    Q4:6.x 内核下 fasync 是否已被废弃?

    未被废弃,仍完全支持。但社区推荐新项目优先考虑 io_uring(通过 IORING_OP_POLL_ADD 实现异步事件通知),其性能与可扩展性优于信号驱动模型。

    Q5:Device Tree 与异步通知有什么关系?

    Device Tree 本身不直接改变 fasync 机制,但它改变了驱动的组织方式:

    • 驱动通过 compatible 字符串与 DT 节点绑定,不再依赖板级文件;
    • 中断资源通过 interrupts / interrupt-parent 属性描述,由内核自动解析并调用 request_irq();
    • 驱动代码更纯粹,.fasync 的实现与硬件描述完全解耦。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux 异步通知机制(fasync / SIGIO)简析
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!