《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 9 章 Linux 设备驱动中的异步通知与异步 I/O
·
《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_iter 和 write_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信号;适用场景 | SIGIO、FASYNC |
| 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年
更多推荐



所有评论(0)