《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》

第 9 章 Linux 设备驱动中的异步通知与异步 I/O

参考:宋宝华 著,机械工业出版社,2015年版


9.1 异步通知的概念与作用

9.1.1 三种 I/O 模型的对比

在介绍异步通知之前,宋宝华在书中对三种 I/O 模型进行了系统对比:

三种 I/O 模型对比:

模型一:阻塞 I/O(Blocking I/O)
  应用程序调用 read()
      ↓ 没有数据
  进程睡眠等待
      ↓ 数据到来
  进程被唤醒,读取数据
  
  特点:简单,但进程被阻塞,无法同时处理其他事务

模型二:非阻塞 I/O + 轮询(Non-Blocking + Poll)
  应用程序调用 poll()/select()
      ↓ 监控多个 fd
  等待任一 fd 就绪
      ↓ fd 就绪
  调用 read() 读取数据
  
  特点:可同时监控多个设备,但仍需主动查询

模型三:异步通知(Async Notification / Signal-Driven I/O)
  应用程序注册信号处理函数
  应用程序继续执行其他任务
      ↓ 数据到来
  内核发送 SIGIO 信号给应用程序
  应用程序的信号处理函数被调用
  在信号处理函数中读取数据
  
  特点:真正的异步,应用程序无需等待,被动接收通知

9.1.2 异步通知的概念

**异步通知(Asynchronous Notification)是指设备可以在数据就绪时,主动通知应用程序,而不需要应用程序主动查询。Linux 通过信号(Signal)**机制实现异步通知,具体使用 SIGIO 信号。

异步通知的工作原理:

应用程序                          内核/驱动
─────────────────────────────────────────────────────────
1. 注册 SIGIO 信号处理函数
   signal(SIGIO, my_handler)
   
2. 设置设备文件的属主进程
   fcntl(fd, F_SETOWN, getpid())
   
3. 使能异步通知
   flags = fcntl(fd, F_GETFL)
   fcntl(fd, F_SETFL, flags | FASYNC)
   
4. 继续执行其他任务...
   do_other_work()
   do_other_work()
   do_other_work()
                                  5. 数据到来(如串口收到数据)
                                  
                                  6. 驱动调用 kill_fasync()
                                     发送 SIGIO 信号给属主进程
                                     
5. 信号处理函数被调用
   my_handler(SIGIO)
   read(fd, buf, n)  ← 读取数据
   
6. 继续执行其他任务...

9.1.3 异步通知的优势与适用场景

异步通知的优势:
  ✓ 应用程序无需阻塞等待,可以充分利用 CPU
  ✓ 无需轮询,不浪费 CPU 资源
  ✓ 响应及时,数据就绪时立即得到通知
  ✓ 适合低频率、不可预测的事件通知

适用场景:
  ✓ 键盘/鼠标输入事件
  ✓ 串口数据接收
  ✓ 网络数据包到达
  ✓ 硬件状态变化通知(如 GPIO 中断)
  ✓ 定时器到期通知

不适用场景:
  ✗ 高频率数据传输(信号开销大)
  ✗ 需要精确控制读写时序的场景
  ✗ 信号处理函数中不能调用非异步安全函数

9.2 Linux 异步通知编程

9.2.1 信号(Signal)基础

Linux 信号是一种软件中断机制,用于进程间通信和异步事件通知:

/* 常用信号 */
SIGHUP   (1)  ← 终端挂起
SIGINT   (2)  ← 键盘中断(Ctrl+C)
SIGKILL  (9)  ← 强制终止(不可捕获)
SIGSEGV  (11) ← 段错误(非法内存访问)
SIGPIPE  (13) ← 管道破裂
SIGALRM  (14) ← 定时器信号
SIGTERM  (15) ← 终止信号(可捕获)
SIGCHLD  (17) ← 子进程状态改变
SIGIO    (29) ← I/O 就绪通知(异步通知使用此信号)
SIGRTMIN~SIGRTMAX ← 实时信号(可排队,不丢失)

9.2.2 用户空间的异步通知编程

完整的用户空间异步通知程序

/*
 * async_notify_app.c —— 用户空间异步通知示例
 * 演示如何使用 SIGIO 信号接收设备的异步通知
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#define DEVICE_FILE  "/dev/globalfifo"
#define BUF_SIZE     128

static int fd = -1;
static volatile int data_ready = 0;

/*
 * SIGIO 信号处理函数
 * 注意:信号处理函数中只能调用"异步信号安全"函数
 * 安全函数:read/write/open/close/signal 等系统调用
 * 不安全函数:printf/malloc/free/mutex_lock 等
 */
static void sigio_handler(int signum)
{
    char buf[BUF_SIZE];
    ssize_t n;

    if (signum != SIGIO)
        return;

    /* 在信号处理函数中读取数据 */
    n = read(fd, buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        /*
         * 注意:printf 不是异步信号安全函数
         * 在实际驱动开发中,应该设置标志位,在主循环中处理
         * 这里为了演示简单起见直接使用 printf
         */
        write(STDOUT_FILENO, "收到异步通知,数据:", 24);
        write(STDOUT_FILENO, buf, n);
        write(STDOUT_FILENO, "\n", 1);
    }

    data_ready = 1;  /* 设置标志位,通知主循环 */
}

int main(void)
{
    int flags;

    /* 1. 打开设备文件 */
    fd = open(DEVICE_FILE, O_RDWR);
    if (fd < 0) {
        perror("open failed");
        return EXIT_FAILURE;
    }
    printf("设备打开成功,fd = %d\n", fd);

    /* 2. 注册 SIGIO 信号处理函数 */
    if (signal(SIGIO, sigio_handler) == SIG_ERR) {
        perror("signal failed");
        close(fd);
        return EXIT_FAILURE;
    }
    printf("SIGIO 信号处理函数注册成功\n");

    /*
     * 3. 设置设备文件的属主进程(F_SETOWN)
     * 告诉内核:当设备就绪时,将 SIGIO 信号发送给当前进程
     */
    if (fcntl(fd, F_SETOWN, getpid()) < 0) {
        perror("fcntl F_SETOWN failed");
        close(fd);
        return EXIT_FAILURE;
    }
    printf("设置属主进程 PID = %d\n", getpid());

    /*
     * 4. 使能异步通知(F_SETFL + FASYNC)
     * 先获取当前文件标志,再添加 FASYNC 标志
     * 添加 FASYNC 标志会触发驱动的 fasync 函数
     */
    flags = fcntl(fd, F_GETFL);
    if (flags < 0) {
        perror("fcntl F_GETFL failed");
        close(fd);
        return EXIT_FAILURE;
    }

    if (fcntl(fd, F_SETFL, flags | FASYNC) < 0) {
        perror("fcntl F_SETFL FASYNC failed");
        close(fd);
        return EXIT_FAILURE;
    }
    printf("异步通知已使能(FASYNC 标志已设置)\n");

    /* 5. 主循环:执行其他任务,等待异步通知 */
    printf("主循环开始,等待异步通知...\n");
    printf("(在另一个终端执行:echo 'hello' > /dev/globalfifo)\n\n");

    int loop_count = 0;
    while (1) {
        /* 模拟应用程序的其他工作 */
        printf("主循环执行中... [%d]\n", ++loop_count);
        sleep(1);

        /* 检查是否收到异步通知 */
        if (data_ready) {
            printf("主循环检测到数据就绪标志\n");
            data_ready = 0;
        }

        if (loop_count >= 30) {
            printf("超时退出\n");
            break;
        }
    }

    /* 6. 关闭设备前,禁用异步通知 */
    fcntl(fd, F_SETFL, flags & ~FASYNC);
    close(fd);
    printf("设备已关闭\n");

    return EXIT_SUCCESS;
}

9.2.3 信号处理的最佳实践

/*
 * 最佳实践:信号处理函数只设置标志位,主循环处理实际工作
 * 避免在信号处理函数中调用非异步安全函数
 */

/* 使用 volatile sig_atomic_t 保证原子性 */
static volatile sig_atomic_t sigio_flag = 0;

static void sigio_handler(int signum)
{
    sigio_flag = 1;  /* 只设置标志位,不做复杂操作 */
}

int main(void)
{
    /* ... 设置信号处理和 FASYNC ... */

    while (1) {
        /* 等待信号(pause 会在收到信号后返回)*/
        pause();

        if (sigio_flag) {
            sigio_flag = 0;

            /* 在主循环中安全地处理数据 */
            char buf[128];
            ssize_t n = read(fd, buf, sizeof(buf));
            if (n > 0) {
                printf("读取到 %zd 字节数据\n", n);
                /* 可以调用任何函数,不受异步安全限制 */
                process_data(buf, n);
            }
        }
    }
}

/*
 * 使用 sigaction 代替 signal(更可靠)
 * signal 的行为在不同系统上可能不一致
 * sigaction 提供更精确的控制
 */
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sa.sa_flags   = SA_RESTART;  /* 被信号中断的系统调用自动重启 */
sigemptyset(&sa.sa_mask);

if (sigaction(SIGIO, &sa, NULL) < 0) {
    perror("sigaction failed");
    return -1;
}

9.2.4 实时信号与 SIGIO 的区别

/*
 * SIGIO 的局限性:
 * 1. 不可排队:多个 SIGIO 信号可能合并为一个
 * 2. 无法携带额外信息
 *
 * 实时信号(SIGRTMIN ~ SIGRTMAX)的优势:
 * 1. 可排队:每个信号都会被传递,不会丢失
 * 2. 可携带额外数据(si_value)
 * 3. 按发送顺序传递
 */

/* 使用实时信号的异步通知 */
struct sigaction sa;
sa.sa_sigaction = realtime_handler;  /* 使用 sa_sigaction 而非 sa_handler */
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGRTMIN, &sa, NULL);

/* 实时信号处理函数(可获取额外信息)*/
static void realtime_handler(int signum, siginfo_t *info, void *context)
{
    printf("收到实时信号 %d\n", signum);
    printf("发送进程 PID = %d\n", info->si_pid);
    printf("携带数据 = %d\n", info->si_value.sival_int);
}

9.3 支持异步通知的 globalfifo 驱动

9.3.1 驱动中实现异步通知的机制

驱动程序需要实现 fasync 函数,并在数据就绪时调用 kill_fasync 发送信号:

驱动异步通知的实现要素:

1. fasync_struct 链表
   内核维护一个 fasync_struct 链表,记录所有注册了异步通知的进程
   每个注册了 FASYNC 的文件描述符对应一个 fasync_struct 节点

2. fasync 函数(驱动实现)
   当应用程序设置/清除 FASYNC 标志时,内核调用驱动的 fasync 函数
   驱动调用 fasync_helper 更新 fasync_struct 链表

3. kill_fasync 函数(驱动调用)
   当数据就绪时,驱动调用 kill_fasync 向所有注册进程发送 SIGIO 信号

9.3.2 fasync 相关 API

#include <linux/fs.h>

/*
 * fasync_struct:异步通知结构体
 * 内核维护一个 fasync_struct 链表,记录注册了异步通知的进程
 */
struct fasync_struct {
    spinlock_t        fa_lock;
    int               magic;
    int               fa_fd;
    struct fasync_struct *fa_next;
    struct file       *fa_file;
    struct rcu_head   fa_rcu;
};

/*
 * fasync_helper:更新 fasync_struct 链表
 * 当应用程序设置/清除 FASYNC 标志时调用
 *
 * fd:文件描述符
 * filp:文件结构体
 * on:1(添加到链表),0(从链表移除)
 * fapp:指向 fasync_struct 指针的指针(驱动私有)
 *
 * 返回:0(成功),负值(失败)
 */
int fasync_helper(int fd, struct file *filp, int on,
                  struct fasync_struct **fapp);

/*
 * kill_fasync:向注册了异步通知的进程发送信号
 *
 * fp:fasync_struct 链表头指针
 * sig:要发送的信号(通常为 SIGIO)
 * band:I/O 事件类型(POLL_IN 或 POLL_OUT)
 */
void kill_fasync(struct fasync_struct **fp, int sig, int band);

9.3.3 支持异步通知的完整 globalfifo 驱动

在第 8 章 globalfifo 驱动的基础上,添加异步通知支持:

/*
 * globalfifo_async.c —— 支持异步通知的 globalfifo 驱动
 *
 * 在 8.3 节 globalfifo 驱动基础上增加:
 * 1. fasync_struct 指针(记录注册了异步通知的进程)
 * 2. fasync 函数(处理 FASYNC 标志的设置/清除)
 * 3. 在 write 函数中调用 kill_fasync 发送 SIGIO 信号
 *
 * 参考:宋宝华《Linux设备驱动开发详解》第9章
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>

#define GLOBALFIFO_SIZE   0x1000
#define GLOBALFIFO_MAJOR  231
#define FIFO_CLEAR        0x1

/* ── 设备结构体(新增 fasync_queue)────────────────────── */
struct globalfifo_dev {
    struct cdev  cdev;

    /* FIFO 缓冲区 */
    unsigned char buf[GLOBALFIFO_SIZE];
    unsigned int  r_pos;
    unsigned int  w_pos;
    unsigned int  current_len;

    /* 并发控制 */
    struct mutex  mutex;

    /* 等待队列 */
    wait_queue_head_t r_wait;
    wait_queue_head_t w_wait;

    /*
     * 异步通知队列
     * 记录所有注册了 FASYNC 的文件描述符对应的进程信息
     * 当数据就绪时,通过此链表向所有注册进程发送 SIGIO
     */
    struct fasync_struct *async_queue;
};

static int globalfifo_major = GLOBALFIFO_MAJOR;
static struct globalfifo_dev *globalfifo_devp;
static struct class  *globalfifo_class;

/* ── open ────────────────────────────────────────────────── */
static int globalfifo_open(struct inode *inode, struct file *filp)
{
    struct globalfifo_dev *dev = container_of(inode->i_cdev,
                                               struct globalfifo_dev, cdev);
    filp->private_data = dev;
    return 0;
}

/* ── release ─────────────────────────────────────────────── */
static int globalfifo_release(struct inode *inode, struct file *filp)
{
    struct globalfifo_dev *dev = filp->private_data;

    /*
     * 关闭文件时,必须从异步通知队列中移除该文件描述符
     * 否则进程退出后,内核仍会尝试向已不存在的进程发送信号
     *
     * 通过调用 fasync 函数(传入 on=0)来移除
     */
    globalfifo_fasync(-1, filp, 0);  /* 从异步队列移除 */
    return 0;
}

/*
 * ── fasync 函数 ───────────────────────────────────────────
 *
 * 当应用程序通过 fcntl(fd, F_SETFL, flags | FASYNC) 设置 FASYNC 标志时,
 * 内核会调用此函数(on=1,添加到队列)
 *
 * 当应用程序通过 fcntl(fd, F_SETFL, flags & ~FASYNC) 清除 FASYNC 标志时,
 * 内核会调用此函数(on=0,从队列移除)
 *
 * 当文件被关闭时,内核也会调用此函数(on=0)
 */
static int globalfifo_fasync(int fd, struct file *filp, int on)
{
    struct globalfifo_dev *dev = filp->private_data;

    /*
     * fasync_helper:内核提供的辅助函数
     * 根据 on 的值,将当前文件描述符添加到或从 async_queue 链表中移除
     */
    return fasync_helper(fd, filp, on, &dev->async_queue);
}

/* ── ioctl ───────────────────────────────────────────────── */
static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
                              unsigned long arg)
{
    struct globalfifo_dev *dev = filp->private_data;

    switch (cmd) {
    case FIFO_CLEAR:
        mutex_lock(&dev->mutex);
        dev->r_pos = 0;
        dev->w_pos = 0;
        dev->current_len = 0;
        mutex_unlock(&dev->mutex);
        wake_up_interruptible(&dev->w_wait);
        pr_info("globalfifo: FIFO 已清空\n");
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

/* ── read ────────────────────────────────────────────────── */
static ssize_t globalfifo_read(struct file *filp, char __user *buf,
                                size_t count, loff_t *ppos)
{
    int ret;
    struct globalfifo_dev *dev = filp->private_data;
    DECLARE_WAITQUEUE(wait, current);

    mutex_lock(&dev->mutex);

    while (dev->current_len == 0) {
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }

        __add_wait_queue(&dev->r_wait, &wait);
        set_current_state(TASK_INTERRUPTIBLE);
        mutex_unlock(&dev->mutex);
        schedule();
        mutex_lock(&dev->mutex);
        __remove_wait_queue(&dev->r_wait, &wait);
        set_current_state(TASK_RUNNING);

        if (signal_pending(current)) {
            ret = -ERESTARTSYS;
            goto out;
        }
    }

    if (count > dev->current_len)
        count = dev->current_len;

    /* 从环形缓冲区读取数据 */
    if (dev->r_pos + count <= GLOBALFIFO_SIZE) {
        if (copy_to_user(buf, dev->buf + dev->r_pos, count)) {
            ret = -EFAULT;
            goto out;
        }
    } else {
        unsigned int first = GLOBALFIFO_SIZE - dev->r_pos;
        if (copy_to_user(buf, dev->buf + dev->r_pos, first) ||
            copy_to_user(buf + first, dev->buf, count - first)) {
            ret = -EFAULT;
            goto out;
        }
    }

    dev->r_pos = (dev->r_pos + count) % GLOBALFIFO_SIZE;
    dev->current_len -= count;
    ret = count;

    /* 唤醒等待写入的进程 */
    wake_up_interruptible(&dev->w_wait);

out:
    mutex_unlock(&dev->mutex);
    return ret;
}

/* ── write ───────────────────────────────────────────────── */
static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
                                 size_t count, loff_t *ppos)
{
    int ret;
    struct globalfifo_dev *dev = filp->private_data;
    DECLARE_WAITQUEUE(wait, current);

    mutex_lock(&dev->mutex);

    while (dev->current_len == GLOBALFIFO_SIZE) {
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }

        __add_wait_queue(&dev->w_wait, &wait);
        set_current_state(TASK_INTERRUPTIBLE);
        mutex_unlock(&dev->mutex);
        schedule();
        mutex_lock(&dev->mutex);
        __remove_wait_queue(&dev->w_wait, &wait);
        set_current_state(TASK_RUNNING);

        if (signal_pending(current)) {
            ret = -ERESTARTSYS;
            goto out;
        }
    }

    if (count > GLOBALFIFO_SIZE - dev->current_len)
        count = GLOBALFIFO_SIZE - dev->current_len;

    /* 向环形缓冲区写入数据 */
    if (dev->w_pos + count <= GLOBALFIFO_SIZE) {
        if (copy_from_user(dev->buf + dev->w_pos, buf, count)) {
            ret = -EFAULT;
            goto out;
        }
    } else {
        unsigned int first = GLOBALFIFO_SIZE - dev->w_pos;
        if (copy_from_user(dev->buf + dev->w_pos, buf, first) ||
            copy_from_user(dev->buf, buf + first, count - first)) {
            ret = -EFAULT;
            goto out;
        }
    }

    dev->w_pos = (dev->w_pos + count) % GLOBALFIFO_SIZE;
    dev->current_len += count;
    ret = count;

    pr_info("globalfifo: 写入 %zu 字节,当前 %u 字节\n",
            count, dev->current_len);

    /* 唤醒等待读取的进程(阻塞 I/O)*/
    wake_up_interruptible(&dev->r_wait);

    /*
     * 发送异步通知(SIGIO 信号)
     *
     * kill_fasync 向 async_queue 链表中所有注册的进程发送信号
     * SIGIO:发送的信号类型
     * POLL_IN:表示有数据可读(对应 POLLIN 事件)
     *
     * 只有当 async_queue 不为 NULL 时才发送(即有进程注册了异步通知)
     */
    if (dev->async_queue)
        kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

out:
    mutex_unlock(&dev->mutex);
    return ret;
}

/* ── poll ────────────────────────────────────────────────── */
static unsigned int globalfifo_poll(struct file *filp,
                                     struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct globalfifo_dev *dev = filp->private_data;

    mutex_lock(&dev->mutex);
    poll_wait(filp, &dev->r_wait, wait);
    poll_wait(filp, &dev->w_wait, wait);

    if (dev->current_len != 0)
        mask |= POLLIN | POLLRDNORM;
    if (dev->current_len != GLOBALFIFO_SIZE)
        mask |= POLLOUT | POLLWRNORM;

    mutex_unlock(&dev->mutex);
    return mask;
}

/* ── file_operations(新增 fasync)──────────────────────── */
static const struct file_operations globalfifo_fops = {
    .owner          = THIS_MODULE,
    .read           = globalfifo_read,
    .write          = globalfifo_write,
    .poll           = globalfifo_poll,
    .unlocked_ioctl = globalfifo_ioctl,
    .fasync         = globalfifo_fasync,   /* ← 新增异步通知支持 */
    .open           = globalfifo_open,
    .release        = globalfifo_release,
};

/* ── 模块加载 ─────────────────────────────────────────────── */
static int __init globalfifo_init(void)
{
    int ret;
    dev_t devno = MKDEV(globalfifo_major, 0);

    ret = register_chrdev_region(devno, 1, "globalfifo");
    if (ret < 0) return ret;

    globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
    if (!globalfifo_devp) {
        ret = -ENOMEM;
        goto fail_malloc;
    }

    mutex_init(&globalfifo_devp->mutex);
    init_waitqueue_head(&globalfifo_devp->r_wait);
    init_waitqueue_head(&globalfifo_devp->w_wait);
    /* async_queue 初始化为 NULL(kzalloc 已清零)*/

    cdev_init(&globalfifo_devp->cdev, &globalfifo_fops);
    globalfifo_devp->cdev.owner = THIS_MODULE;
    ret = cdev_add(&globalfifo_devp->cdev, devno, 1);
    if (ret) goto fail_cdev;

    globalfifo_class = class_create(THIS_MODULE, "globalfifo");
    if (IS_ERR(globalfifo_class)) {
        ret = PTR_ERR(globalfifo_class);
        goto fail_class;
    }

    device_create(globalfifo_class, NULL, devno, NULL, "globalfifo");

    pr_info("globalfifo: 驱动加载成功(支持异步通知)\n");
    return 0;

fail_class:
    cdev_del(&globalfifo_devp->cdev);
fail_cdev:
    kfree(globalfifo_devp);
fail_malloc:
    unregister_chrdev_region(devno, 1);
    return ret;
}

/* ── 模块卸载 ─────────────────────────────────────────────── */
static void __exit globalfifo_exit(void)
{
    device_destroy(globalfifo_class, MKDEV(globalfifo_major, 0));
    class_destroy(globalfifo_class);
    cdev_del(&globalfifo_devp->cdev);
    kfree(globalfifo_devp);
    unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
    pr_info("globalfifo: 驱动已卸载\n");
}

module_init(globalfifo_init);
module_exit(globalfifo_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("支持异步通知的 globalfifo 字符设备驱动");

9.3.4 异步通知的完整流程分析

异步通知的完整流程(以 globalfifo 为例):

步骤1:应用程序注册异步通知
  signal(SIGIO, sigio_handler)
  fcntl(fd, F_SETOWN, getpid())
  fcntl(fd, F_SETFL, flags | FASYNC)
      ↓
  内核调用 globalfifo_fasync(fd, filp, 1)
      ↓
  fasync_helper 将当前进程添加到 async_queue 链表

步骤2:应用程序继续执行其他任务
  do_other_work()  ← 不阻塞,不轮询

步骤3:另一个进程向 globalfifo 写入数据
  write(fd2, "Hello", 5)
      ↓
  globalfifo_write 执行
      ↓
  数据写入 FIFO 缓冲区
      ↓
  wake_up_interruptible(&r_wait)  ← 唤醒阻塞读者
      ↓
  kill_fasync(&async_queue, SIGIO, POLL_IN)  ← 发送 SIGIO

步骤4:内核向注册进程发送 SIGIO 信号
  内核遍历 async_queue 链表
  向每个注册进程发送 SIGIO 信号

步骤5:应用程序的 SIGIO 处理函数被调用
  sigio_handler(SIGIO)
      ↓
  read(fd, buf, n)  ← 读取数据
      ↓
  处理数据

步骤6:应用程序关闭设备
  close(fd)
      ↓
  内核调用 globalfifo_release
      ↓
  globalfifo_fasync(-1, filp, 0)  ← 从 async_queue 移除

9.3.5 验证异步通知

# 编译并加载驱动
make && sudo insmod globalfifo.ko

# 编译测试程序
gcc -o async_test async_notify_app.c

# 终端1:运行异步通知测试程序
sudo ./async_test
# 输出:
# 设备打开成功,fd = 3
# SIGIO 信号处理函数注册成功
# 设置属主进程 PID = 12345
# 异步通知已使能(FASYNC 标志已设置)
# 主循环开始,等待异步通知...
# 主循环执行中... [1]
# 主循环执行中... [2]
# 主循环执行中... [3]

# 终端2:向 globalfifo 写入数据
echo "Hello Async!" | sudo tee /dev/globalfifo

# 终端1 立即显示:
# 收到异步通知,数据:Hello Async!
# 主循环检测到数据就绪标志
# 主循环执行中... [4]

# 查看内核日志
dmesg | grep globalfifo | tail -5
# globalfifo: 写入 13 字节,当前 13 字节

9.4 Linux AIO(异步 I/O)

9.4.1 AIO 的概念

Linux AIO(Asynchronous I/O,异步 I/O)是 Linux 2.6 引入的内核级异步 I/O 机制,允许应用程序提交 I/O 请求后立即返回,不等待 I/O 完成,当 I/O 完成时通过回调或事件通知应用程序。

AIO 与其他 I/O 模型的对比:

同步阻塞 I/O:
  提交请求 → 等待完成 → 处理结果
  (进程阻塞,无法做其他事)

同步非阻塞 I/O:
  提交请求 → 立即返回(-EAGAIN)→ 轮询直到完成
  (进程不阻塞,但需要轮询)

异步通知(SIGIO):
  提交请求 → 立即返回 → 收到信号 → 处理结果
  (进程不阻塞,被动接收通知,但信号处理有限制)

Linux AIO:
  提交 I/O 请求(io_submit)→ 立即返回
  继续执行其他任务
  查询/等待完成(io_getevents)→ 处理结果
  (真正的异步,高性能,适合大量 I/O 操作)

9.4.2 Linux AIO 的两种实现

Linux 提供了两种 AIO 实现:

1. POSIX AIO(用户空间实现)
   头文件:<aio.h>
   实现:glibc 用线程模拟异步 I/O
   特点:可移植性好,但性能较差(实际上是多线程同步 I/O)
   适用:需要跨平台的场景

2. Linux Native AIO(内核实现)
   头文件:<linux/aio_abi.h>
   系统调用:io_setup / io_submit / io_getevents / io_cancel / io_destroy
   特点:真正的内核异步 I/O,性能高
   适用:高性能存储、数据库(如 MySQL、PostgreSQL)
   限制:只支持 O_DIRECT 模式的文件 I/O(绕过页缓存)

9.4.3 POSIX AIO 编程

/*
 * posix_aio_example.c —— POSIX AIO 示例
 * 演示异步读写文件
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <aio.h>
#include <errno.h>
#include <signal.h>

#define FILE_PATH  "/tmp/aio_test.txt"
#define BUF_SIZE   1024

/* AIO 完成回调函数 */
static void aio_completion_handler(sigval_t sigval)
{
    struct aiocb *req = (struct aiocb *)sigval.sival_ptr;

    /* 检查 AIO 操作是否完成 */
    if (aio_error(req) == 0) {
        ssize_t ret = aio_return(req);
        printf("AIO 操作完成,传输 %zd 字节\n", ret);
    } else {
        printf("AIO 操作失败:%s\n", strerror(aio_error(req)));
    }
}

int main(void)
{
    int fd;
    struct aiocb aio_req;
    char write_buf[] = "Hello, Linux AIO! This is async I/O test.";
    char read_buf[BUF_SIZE] = {0};
    int ret;

    /* 创建测试文件 */
    fd = open(FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) { perror("open"); return -1; }

    /* ── 异步写操作 ──────────────────────────────────────── */
    memset(&aio_req, 0, sizeof(aio_req));
    aio_req.aio_fildes     = fd;
    aio_req.aio_buf        = write_buf;
    aio_req.aio_nbytes     = strlen(write_buf);
    aio_req.aio_offset     = 0;

    /* 设置完成通知方式:信号通知 */
    aio_req.aio_sigevent.sigev_notify          = SIGEV_THREAD;
    aio_req.aio_sigevent.sigev_notify_function = aio_completion_handler;
    aio_req.aio_sigevent.sigev_value.sival_ptr = &aio_req;

    /* 提交异步写请求(立即返回,不等待完成)*/
    ret = aio_write(&aio_req);
    if (ret < 0) { perror("aio_write"); close(fd); return -1; }
    printf("异步写请求已提交,继续执行其他任务...\n");

    /* 模拟执行其他任务 */
    printf("执行其他任务中...\n");
    sleep(1);

    /* 等待异步写完成 */
    while (aio_error(&aio_req) == EINPROGRESS) {
        printf("写操作进行中...\n");
        usleep(100000);  /* 100ms */
    }

    ssize_t bytes_written = aio_return(&aio_req);
    printf("异步写完成,写入 %zd 字节\n", bytes_written);

    /* ── 异步读操作 ──────────────────────────────────────── */
    memset(&aio_req, 0, sizeof(aio_req));
    aio_req.aio_fildes = fd;
    aio_req.aio_buf    = read_buf;
    aio_req.aio_nbytes = BUF_SIZE - 1;
    aio_req.aio_offset = 0;

    /* 设置完成通知方式:不通知(轮询检查)*/
    aio_req.aio_sigevent.sigev_notify = SIGEV_NONE;

    /* 提交异步读请求 */
    ret = aio_read(&aio_req);
    if (ret < 0) { perror("aio_read"); close(fd); return -1; }
    printf("异步读请求已提交\n");

    /* 使用 aio_suspend 等待完成(带超时)*/
    const struct aiocb *aio_list[] = { &aio_req };
    struct timespec timeout = { .tv_sec = 5, .tv_nsec = 0 };

    ret = aio_suspend(aio_list, 1, &timeout);
    if (ret < 0) {
        if (errno == EAGAIN)
            printf("aio_suspend 超时\n");
        else
            perror("aio_suspend");
    } else {
        ssize_t bytes_read = aio_return(&aio_req);
        read_buf[bytes_read] = '\0';
        printf("异步读完成,读取 %zd 字节:\"%s\"\n", bytes_read, read_buf);
    }

    close(fd);
    unlink(FILE_PATH);
    return 0;
}

9.4.4 Linux Native AIO 编程

/*
 * native_aio_example.c —— Linux Native AIO 示例
 * 使用内核原生 AIO 接口进行高性能异步 I/O
 *
 * 注意:Native AIO 要求文件以 O_DIRECT 方式打开
 *       缓冲区必须按扇区大小(通常512字节)对齐
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/syscall.h>
#include <linux/aio_abi.h>

/* 系统调用封装(glibc 通常不直接提供这些接口)*/
static inline int io_setup(unsigned nr, aio_context_t *ctxp)
{
    return syscall(__NR_io_setup, nr, ctxp);
}

static inline int io_destroy(aio_context_t ctx)
{
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp)
{
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
                                struct io_event *events,
                                struct timespec *timeout)
{
    return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
}

#define FILE_PATH   "/tmp/native_aio_test.dat"
#define BLOCK_SIZE  4096   /* 4KB,按页大小对齐 */
#define NUM_IOS     4      /* 同时提交的 I/O 数量 */

int main(void)
{
    aio_context_t ctx = 0;
    struct iocb   iocbs[NUM_IOS];
    struct iocb  *iocbps[NUM_IOS];
    struct io_event events[NUM_IOS];
    char *bufs[NUM_IOS];
    int fd, ret, i;
    struct timespec timeout = { .tv_sec = 5, .tv_nsec = 0 };

    /* 1. 初始化 AIO 上下文(最多同时处理 NUM_IOS 个 I/O)*/
    ret = io_setup(NUM_IOS, &ctx);
    if (ret < 0) {
        perror("io_setup");
        return -1;
    }
    printf("AIO 上下文初始化成功\n");

    /* 2. 打开文件(O_DIRECT:绕过页缓存,直接 I/O)*/
    fd = open(FILE_PATH, O_RDWR | O_CREAT | O_DIRECT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        io_destroy(ctx);
        return -1;
    }

    /* 3. 分配对齐的缓冲区(O_DIRECT 要求缓冲区按扇区大小对齐)*/
    for (i = 0; i < NUM_IOS; i++) {
        ret = posix_memalign((void **)&bufs[i], BLOCK_SIZE, BLOCK_SIZE);
        if (ret != 0) {
            perror("posix_memalign");
            goto cleanup;
        }
        /* 填充测试数据 */
        memset(bufs[i], 'A' + i, BLOCK_SIZE);
    }

    /* 4. 准备 I/O 控制块(iocb)*/
    for (i = 0; i < NUM_IOS; i++) {
        memset(&iocbs[i], 0, sizeof(iocbs[i]));
        iocbs[i].aio_fildes  = fd;
        iocbs[i].aio_lio_opcode = IOCB_CMD_PWRITE;  /* 异步写 */
        iocbs[i].aio_buf     = (uint64_t)bufs[i];
        iocbs[i].aio_nbytes  = BLOCK_SIZE;
        iocbs[i].aio_offset  = (off_t)i * BLOCK_SIZE;  /* 写到不同位置 */
        iocbps[i] = &iocbs[i];
    }

    /* 5. 提交所有 I/O 请求(批量提交,立即返回)*/
    ret = io_submit(ctx, NUM_IOS, iocbps);
    if (ret < 0) {
        perror("io_submit");
        goto cleanup;
    }
    printf("提交 %d 个异步写请求\n", ret);

    /* 6. 等待 I/O 完成(io_getevents)*/
    ret = io_getevents(ctx, NUM_IOS, NUM_IOS, events, &timeout);
    if (ret < 0) {
        perror("io_getevents");
        goto cleanup;
    }
    printf("完成 %d 个 I/O 操作\n", ret);

    /* 7. 检查每个 I/O 的结果 */
    for (i = 0; i < ret; i++) {
        if (events[i].res < 0) {
            printf("I/O[%d] 失败:%s\n", i, strerror(-events[i].res));
        } else {
            printf("I/O[%d] 成功:传输 %lld 字节\n", i, events[i].res);
        }
    }

cleanup:
    for (i = 0; i < NUM_IOS; i++)
        if (bufs[i]) free(bufs[i]);
    close(fd);
    unlink(FILE_PATH);
    io_destroy(ctx);
    return 0;
}

9.4.5 驱动中支持 AIO

Linux 4.0 中,驱动可以通过实现 read_iterwrite_iter 来支持 AIO:

#include <linux/aio.h>
#include <linux/uio.h>

/*
 * 支持 AIO 的驱动需要实现 read_iter 和 write_iter
 * 而不是传统的 read 和 write
 *
 * kiocb:I/O 控制块,描述一个 AIO 操作
 * iov_iter:I/O 向量迭代器,描述用户空间缓冲区
 */
static ssize_t my_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
    struct file *filp = iocb->ki_filp;
    struct my_dev *dev = filp->private_data;
    size_t count = iov_iter_count(to);
    loff_t pos = iocb->ki_pos;
    ssize_t ret;

    /* 检查是否是同步 I/O */
    if (is_sync_kiocb(iocb)) {
        /* 同步 I/O:直接执行 */
        ret = my_do_read(dev, to, pos, count);
        iocb->ki_pos += ret;
        return ret;
    }

    /* 异步 I/O:提交到工作队列,立即返回 -EIOCBQUEUED */
    /* 实际驱动中需要实现异步处理逻辑 */
    /* ... */
    return -EIOCBQUEUED;  /* 表示 I/O 已排队,尚未完成 */
}

static ssize_t my_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
    struct file *filp = iocb->ki_filp;
    struct my_dev *dev = filp->private_data;
    size_t count = iov_iter_count(from);
    loff_t pos = iocb->ki_pos;
    ssize_t ret;

    if (is_sync_kiocb(iocb)) {
        ret = my_do_write(dev, from, pos, count);
        iocb->ki_pos += ret;
        return ret;
    }

    /* 异步写:提交到工作队列 */
    return -EIOCBQUEUED;
}

/* AIO 完成时通知内核 */
static void my_aio_complete(struct kiocb *iocb, long ret, long ret2)
{
    /* 通知内核 AIO 操作已完成 */
    aio_complete(iocb, ret, ret2);
}

/* 支持 AIO 的 file_operations */
static const struct file_operations my_fops = {
    .owner      = THIS_MODULE,
    .read_iter  = my_read_iter,   /* 替代传统的 .read */
    .write_iter = my_write_iter,  /* 替代传统的 .write */
    .open       = my_open,
    .release    = my_release,
};

9.4.6 AIO 的性能优势

AIO 在高性能场景中的优势:

传统同步 I/O(顺序执行):
  提交请求1 → 等待完成1 → 提交请求2 → 等待完成2 → ...
  总时间 = T1 + T2 + T3 + ... + Tn

AIO(并发执行):
  同时提交请求1、2、3...n
  等待所有请求完成
  总时间 ≈ max(T1, T2, T3, ..., Tn)

实际性能数据(数据库场景):
  MySQL 使用 AIO 后,随机读写性能提升 2~5 倍
  PostgreSQL 使用 AIO 后,大批量写入性能提升 3 倍

适用场景:
  ✓ 数据库(MySQL、PostgreSQL)
  ✓ 高性能存储系统
  ✓ 视频流媒体服务器
  ✓ 大文件传输
  ✗ 小文件、低并发场景(AIO 开销大于收益)

本章小结

章节 核心知识点 关键 API
9.1 异步通知概念 三种I/O模型对比(阻塞/非阻塞轮询/异步通知);异步通知的工作原理;SIGIO信号;适用场景 SIGIOFASYNC
9.2 Linux异步通知编程 用户空间四步设置(signal/F_SETOWN/F_SETFL+FASYNC);信号处理最佳实践(标志位模式);sigaction vs signal;实时信号 signal()fcntl(F_SETOWN)fcntl(F_SETFL, FASYNC)sigaction()
9.3 支持异步通知的globalfifo fasync_struct 链表;fasync_helper 更新链表;kill_fasync 发送信号;release 中清理;完整驱动代码 fasync_helper()kill_fasync()POLL_IN
9.4 Linux AIO POSIX AIO vs Native AIO;aio_read/write/suspend;Native AIO系统调用(io_setup/io_submit/io_getevents);驱动中的read_iter/write_iter aio_read()aio_write()io_submit()io_getevents()

三种 I/O 通知机制的选择指南

选择 I/O 通知机制的决策:

需要同时监控多个设备?
  是 → 使用 poll/select/epoll(第8章)
  否 → 继续判断

需要真正的异步(不阻塞主线程)?
  是 → 继续判断
  否 → 使用阻塞 I/O(最简单)

I/O 频率高,对性能要求极高?
  是 → 使用 Linux Native AIO(io_submit/io_getevents)
  否 → 继续判断

需要跨平台?
  是 → 使用 POSIX AIO(aio_read/aio_write)
  否 → 使用异步通知(SIGIO)

异步通知(SIGIO)适用场景:
  ✓ 低频率事件(键盘输入、串口数据)
  ✓ 不需要同时监控多个设备
  ✓ 信号处理逻辑简单

AIO 适用场景:
  ✓ 高频率、大量 I/O 操作
  ✓ 数据库、存储系统
  ✓ 需要最大化 I/O 吞吐量

参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐