【I/O模型】异步IO的原理和使用

【I/O模型】异步IO的原理和使用但是使用默认信号 SIGIO 会存在一些问题 SIGIO 是标准信号 不可靠信号 非实时信号 不支持信号排队机制 不知道文描述符发生了什么事件 未判断文件描述符是否处于可读的就绪态 所以需要进一步优化

大家好,欢迎来到IT知识分享网。

为什么要有异步I/O

异步IO相比同步IO不会阻塞当前程序的执行,可以继续向下执行。即当应用程序发起一个IO操作后,调用者不会立刻得到结果,而是在内核完成IO操作后,通过信号或回调来通知调用者。

信号驱动I/O

信号驱动IO是异步IO的一种实现,在异步IO中,当文件描述符上可以执行I/O操作时,进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其他任务直到文件描述符可以执行I/O操作为止,此时内核会发送信号给进程。

使用信号驱动,程序需要按照如下步骤执行:

  • 通过指定O_NONBLOCK标志使能非阻塞I/O
  • 通过制定O_ASYNC标志使能异步I/O
  • 通过设置异步I/O时间的接收进程。当文件描述符上可执行I/O操作时会发送信号通知该进程。
  • 为内核发送的通知信号注册一个信号处理函数。异步信号I/O缺省是SIGIO,所以内核会给进程发送信号SIGIO。

以上步骤完成后,进程可以去执行其他的任务,当I/O就绪时,内核会向进程发送一个SIGIO信号,当进程接收到信号时,会执行预先注册号的信号处理函数,这样就可以在信号处理函数中进行I/O操作了

使能O_ASYNC

调用open时无法通过指定O_ASYNC标志来使能异步I/O,但是可以通过fcntl()函数添加O_ASYNC标志来使能I/O:

int flag; flag = fcntl(fd,F_GETFL); // 先从打开的文件描述符中获取原来的flag flag |= O_ASYNC; // 将O_ASYNC标志添加到flag fcntl(fd,F_SETFL,flag); // 重新设置flag 

设置异步I/O时间的接收过程

为文件描述符设置异步I/O时间的接收进程,也就是设置异步I/O的所有者:

fcntl(fd,F_SETOWN,getpid()); // 也可以传入其他进程的pid 

注册SIGIO信号的处理函数

通过signal()或sigaction()函数为SIGIO信号注册一个信号处理函数,当进程接收到内核发送过来的SIGIO信号时,会执行该函数。

代码实例:

#define _GNU_SOURCE // F_SETSIG #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #define MOUSE "/dev/input/mouse0" static int fd; static void sigio_handler(int sig) { 
    static int loops = 5; char buf[100] = { 
   0}; int ret; if(SIGIO != sig) { 
    return ; } ret = read(fd,buf,sizeof(buf)); if(0 < ret) printf("mouse : read %d bytes\n",ret); loops--; if(0>=loops) { 
    close(fd); exit(0); } } int main(void) { 
    int flag; // 打开设备,使能非阻塞IO fd = open(MOUSE,O_RDONLY|O_NONBLOCK); if(-1 == fd) { 
    perror("open mouse error"); exit(-1); } // 使能异步IO flag = fcntl(fd,F_GETFL); flag |= O_ASYNC; fcntl(fd,F_SETFL,flag); // 设置异步IO的所有者 fcntl(fd,F_SETOWN,getpid()); // 注册信号回调函数 signal(SIGIO,sigio_handler); for(;;) { 
    sleep(1); } } 

运行结果:

image

但是使用默认信号SIGIO会存在一些问题,SIGIO是标准信号,不可靠信号,非实时信号,不支持信号排队机制,不知道文描述符发生了什么事件,未判断文件描述符是否处于可读的就绪态,所以需要进一步优化(实时信号替换)。

1.使用实时信号替换默认信号SIGIO

比如使用SIGRTMIN信号替换SIGIO,比如:

fcntl(fd,F_SETSIG,SIGRTMIN); 

2.使用sigaction()函数注册信号处理函数

在应用程序中需要为实时信号注册信号处理函数,使用sigaction函数进行注册,sigaction原型:

#include <signal.h> int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact); 

使用实例:

#define _GNU_SOURCE // F_SETSIG #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #define MOUSE "/dev/input/mouse0" static int fd; static void io_handler(int sig,siginfo_t *info,void *context) { 
    static int loops = 5; char buf[100] = { 
   0}; int ret; if(SIGRTMIN != sig) { 
    return ; } // 判断鼠标是否可读 if(POLL_IN == info->si_code) { 
    ret = read(fd,buf,sizeof(buf)); if(0 < ret) { 
    printf("mouse : read %d bytes\n",ret); } loops--; if(0>=loops) { 
    close(fd); exit(0); } } } int main(void) { 
    struct sigaction act; int flag; // 打开设备,使能非阻塞IO fd = open(MOUSE,O_RDONLY|O_NONBLOCK); if(-1 == fd) { 
    perror("open mouse error"); exit(-1); } // 使能异步IO flag = fcntl(fd,F_GETFL); flag |= O_ASYNC; fcntl(fd,F_SETFL,flag); // 设置异步IO的所有者 fcntl(fd,F_SETOWN,getpid()); // 指定实时信号SIGRTMIN作为异步I/O通知信号 fcntl(fd,F_SETSIG,SIGRTMIN); // 为实时信号SIGRTMIN注册信号处理函数 act.sa_sigaction = io_handler; act.sa_flags = SA_SIGINFO; sigemptyset(&act.sa_mask); sigaction(SIGRTMIN,&act,NULL); for(;;) { 
    sleep(1); } } 

运行结果:

image

Linux异步I/O – Native AIO

Linux Native AIO是Linux支持的原生AIO,很多第三方的异步IO库,比如libeio和glibc AIO。很多三方库异步IO库不是真正的异步IO,而是通过多线程来模拟异步IO,比如libeio。

aio_*系列的调用是有glibc提供的,是glibc用线程+阻塞调用来模拟的,性能较差,为了能更多的控制io行为,可以使用更低级的libaio。

Ubuntu安装livaio:

sudo apt install libaio-dev 

Linux AIO执行流程:

image

Linux原生AIO处理流程:

  • 当应用程序调用io_submit系统调用发起一个异步IO操作后,回想内核的IO任务队列添加一个IO任务,并且返回成功。
  • 内核会在后台处理IO任务队列中的IO任务,然后把处理结果存储在IO任务中
  • 应用程序可以调用io_getevents

从上面流程可以看出,Linux异步IO操作主要由两个步骤组成:

  • 1)调用io_submit函数发起一个异步IO操作
  • 2)调用io_getevents函数获取异步IO的结果

实例代码:

#define _GNU_SOURCE #include <stdlib.h> #include <string.h> #include <errno.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <libaio.h> #define FILEPATH "./aio.txt" int main() { 
    io_context_t context; // 异步IO的上下文 struct iocb io[1],*p[1] = { 
   &io[0]}; struct io_event e[1]; unsigned nr_events = 10; struct timespec timeout; char *wbuf; int wbuflen = 1024; int ret,num=0,i; posix_memalign((void **)&wbuf,512,wbuflen); memset(wbuf,'@',wbuflen); memset(&context,0,sizeof(io_context_t)); timeout.tv_sec = 0; timeout.tv_nsec = ; // 1.打开要进行异步IO的文件 int fd = open(FILEPATH,O_CREAT | O_RDWR | O_DIRECT,0644); if (fd < 0) { 
    printf("open error: %d\n", errno); return 0; } // 2.创建一个异步IO的上下文 if(0 != io_setup(nr_events,&context)) { 
    printf("io_setup error: %d\n", errno); return 0; } // 3.创建一个异步IO任务 io_prep_pwrite(&io[0],fd,wbuf,wbuflen,0); // 4.提交异步IO任务 if((ret = io_submit(context,1,p)) != 1) { 
    printf("io_submit error: %d\n", ret); io_destroy(context); return -1; } // 5.获取异步IO的结果 while(1) { 
    ret = io_getevents(context,1,1,e,&timeout); if (ret < 0) { 
    printf("io_getevents error: %d\n", ret); break; } if (ret > 0) { 
    printf("result, res2: %d, res: %d\n", e[0].res2, e[0].res); break; } } return 0; } 

编译命令:

cc aio_demo.c -laio 

运行结果:

目录下会出现一个aio.txt的文件,内容为1024个@字符

程序说明:

  • 通过调用open系统调用打开要进行异步IO的文件,AIO操作必须设置O_DIRECT直接IO标志位
  • 调用io_setup系统调用创建一个异步IO上下文
  • 调用io_prep_pwrite或者io_prep_pread函数创建一个异步写或者异步读任务
  • 调用io_submit系统调用将异步IO提交到内核
  • 调用io_getevents系统调用获取异步IO的结果

以上示例使用while检测,还可以使用epoll结合eventfd,结合事件驱动的方式来获取异步IO操作的结果。

Linux 原生异步 IO 原理与使用(Native AIO) – 腾讯云开发者社区-腾讯云 (tencent.com)









免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/122352.html

(0)
上一篇 2025-10-17 12:00
下一篇 2025-10-17 12:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信