`
aslijiasheng
  • 浏览: 58378 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

异步IO之AIO

    博客分类:
  • aio
aio 
阅读更多

什么是AIO

AIOAsynchronous Input and Output,字面上讲就是异步的IO。说到异步IO,网络上的端口复用selectpoll机制大家就很熟悉了,这个AIO到底是啥?

AIOLinux2.6中新加的特性,支持异步的IO读写,IO繁忙或者IO耗时较多时,可以将IO操作交给AIO,程序处理其他逻辑,实现并行加速,AIO一定程度上也能够减少IO操作的频率,减少IO负担

AIO API

linux /usr/include的下面,有aio.h这样一个文件,打开之后,可以看到如下函数:

API 函数

说明

aio_read

请求异步读操作

aio_error

检查异步请求的状态

aio_return

获得完成的异步请求的返回状态

aio_write

请求异步写操作

aio_suspend

挂起调用进程,直到一个或多个异步请求已经完成(或失败)

aio_cancel

取消异步 I/O 请求

aio_fsync

强制同步

lio_listio

发起一系列 I/O 操作

AIO控制结构

/* Asynchronous I/O control block.  */

 struct aiocb

 {

   int aio_fildes;       /* File desriptor.  */

   int aio_lio_opcode;       /* Operation to be performed.  */

   int aio_reqprio;      /* Request priority offset.  */

   volatile void *aio_buf;   /* Location of buffer.  */

   size_t aio_nbytes;        /* Length of transfer.  */

   struct sigevent aio_sigevent; /* Signal number and value.  */

 

   /* Internal members.  */

   struct aiocb *__next_prio;

   …

};

从定义中可以看到,核心结构还不算,句柄、操作码、offset都比较容易理解,buffervolatile来修饰,应该是内部采用多线程的原因,长度就不用说了,这里需要研究的就剩下sigevent需要了解,而内部(internal)数据看到第一个指针就明白是链表了。

sigevent

明显,是一个处理signal事件的结构体,定义如下:

struct sigevent {

    int          sigev_notify; /* Notification method */

    int          sigev_signo;  /* Notification signal */

    union sigval sigev_value;  /* Data passed with

                                  notification */

    void       (*sigev_notify_function) (union sigval);

                     /* Function used for thread

                        notification (SIGEV_THREAD) */

    void        *sigev_notify_attributes;

                     /* Attributes for notification thread

                        (SIGEV_THREAD) */

    pid_t        sigev_notify_thread_id;

                     /* ID of thread to signal (SIGEV_THREAD_ID) */

};

Ø  sigev_notify为处理模式,为SIGEV_NONESIGEV_SIGNALSIGEV_THREADSIGEV_THREAD_ID(只针对linux)当中的一个;

Ø  sigev_signosignal的值,当sigev_notifySIGEV_SIGNAL时,会将这个signal发送给进程;

Ø  sigev_value,信号传递的数据;

Ø  sigev_notify_function,当sigev_notifySIGEV_THREAD时,处理线程将调用这个处理函数;

Ø  sigev_notify_attributessigev_notify_function的参数;

Ø  sigev_notify_thread_id,当sigev_notifySIGEV_THREAD_ID时的处理线程ID

aio_read函数

函数原型:

/* Enqueue read request for given number of bytes and the given priority.  */

 extern int aio_read (struct aiocb *__aiocbp) __THROW;

先暂时不理sigevent,我们来一个简单的示例代码:

#include <unistd.h>

#include <stdio.h>

#include <aio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <errno.h>

#include <sys/timeb.h>

 

#define BUFFER_SIZE 1024*1024

 

void ptime(const char* tip = ""){

    struct timeb tb;

    ftime(&tb);

    fprintf(stdout, "%s %u : %u\n", tip, tb.time, tb.millitm);

}

 

int main(){

    /* 句柄,返回码 */

    int fd = -1, ret = -1;

 

    fd = open("./file.txt", O_RDONLY);

    if(fd <= 0){

        fprintf(stderr, "open file errro: %s\n", strerror(errno));

        return -1;

    }

 

    /* aio控制结构 */

    aiocb my_aiocb;

    memset(&my_aiocb, 0, sizeof(my_aiocb));

 

    /* 初始化 */

    my_aiocb.aio_fildes = fd;

    my_aiocb.aio_reqprio = 0;

    my_aiocb.aio_nbytes = BUFFER_SIZE;

    char buf[BUFFER_SIZE + 1] = {0};

    my_aiocb.aio_buf = buf;

   

    ptime("start read");

 

    /* aio  */

    ret = aio_read(&my_aiocb);

    if(ret < 0){

        fprintf(stderr, "aio read error: %s\n", strerror(errno));

        return -2;

    }

 

    ptime("reading");

 

    /* 检查状态 */

    while(aio_error(&my_aiocb) == EINPROGRESS);

   

    ptime("after read");

 

    /* aio 返回 */

    if(ret = aio_return(&my_aiocb) <= 0){

        fprintf(stdout, "return: %d\n", ret);

    }

    else{

        fprintf(stdout, "read: %10.10s\n", my_aiocb.aio_buf);

    }

 

    close(fd);

 

    return 0;

}

编译时,需要加上-lrt选项,这一点在man手册中没有说明实在是有些不厚道。

start read 1370399057 : 740

reading 1370399057 : 740

after read 1370399057 : 747

read: 1234567890

可以看到,整个过程耗时约为7毫秒,至于效率如何,咱们先放到一边,往后继续。
有了以上实例,下面的函数理解起来就比较简单了。

aio_error函数

函数原型:

  /* Retrieve error status associated with AIOCBP.  */

 extern int aio_error (__const struct aiocb *__aiocbp) __THROW;

man手册中可以看到,这个函数可以用来检测aio当前状态:

Ø  EINPROGRESS AIO处理中,请求尚未完成

Ø  ECANCELED AIO被取消

Ø  成功

Ø  其他 出错,错误码放在errno

aio_return函数

函数原型

/* Return status associated with AIOCBP.  */

 extern __ssize_t aio_return (struct aiocb *__aiocbp) __THROW;

这个函数理解起来更加简单,用于获取AIO操作的返回码,类似一般read/write函数的返回值。

aio_write函数

函数原型:

/* Enqueue write request for given number of bytes and the given priority.  */

 extern int aio_write (struct aiocb *__aiocbp) __THROW;

这与 read 系统调用类似,但是有一点不一样的行为需要注意。

回想一下对于 read 调用来说,要使用的偏移量是非常重要的。然而,对于 write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。

如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾;否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。

aio_suspend函数

这个函数需要稍微说明一下,功能是将调用进程(线程)挂起,直到AIO完成,有些类似wait的处理逻辑,函数原型如下:

/* Suspend calling thread until at least one of the asynchronous I/O

    operations referenced by LIST has completed.

 

    This function is a cancellation point and therefore not marked with

    __THROW.  */

 extern int aio_suspend (__const struct aiocb *__const __list[], int __nent,

             __const struct timespec *__restrict __timeout);

Ø  __listAIO控制结构的数组;

Ø  __nent__list中元素的个数;

Ø  __timeout为超时时间;

如果指定tiemout,则函数返回非0值,errno设置为EAGAIN

aio_cancel函数

不用多讲,应该很容易就能明白,取消一个AIO,函数原型如下:

/* Try to cancel asynchronous I/O requests outstanding against file

    descriptor FILDES.  */

 extern int aio_cancel (int __fildes, struct aiocb *__aiocbp) __THROW;

要取消一个请求,我们需要提供文件描述符和 aiocb 引用。如果这个请求被成功取消了,那么这个函数就会返回 AIO_CANCELED。如果请求完成了,这个函数就会返回 AIO_NOTCANCELED

要取消对某个给定文件描述符的所有请求,我们需要提供这个文件的描述符,以及一个对 aiocbp  NULL 引用。

如果所有的请求都取消了,这个函数就会返回 AIO_CANCELED;如果至少有一个请求没有被取消,那么这个函数就会返回 AIO_NOT_CANCELED;如果没有一个请求可以被取消,那么这个函数就会返回 AIO_ALLDONE。我们然后可以使用 aio_error 来验证每个 AIO 请求。

如果这个请求已经被取消了,那么 aio_error 就会返回 -1,并且 errno 会被设置为 ECANCELED

aio_fsync函数

AIO是交给其他线程来完成的,如果需要手动执行同步,则需要调用这个函数,原型为:

/* Force all operations associated with file desriptor described by

    `aio_fildes' member of AIOCBP.  */

 extern int aio_fsync (int __operation, struct aiocb *__aiocbp) __THROW;

Ø  __operation为操作码;

Ø  __aiocbp为异步IO的控制结构;

函数执行时,将强制完成该AIO上的所有操作。具体来讲,如果操作码为O_SYNC,则同步异步IO数据,当前所有IO操作均将完成;如果操作码是O_DSYNC,则只是一个IO请求,并不等待所有的IO完成。

与一般文件IOfsync用法基本一致。

lio_listio函数

放在最后讲,是因为它居然不是aio打头的,函数原型如下

/* Initiate list of I/O requests.  */

 extern int lio_listio (int __mode,

                struct aiocb *__const __list[__restrict_arr],

                int __nent, struct sigevent *__restrict __sig) __THROW;

这个函数结构就复杂多了,我们来慢慢分析。

这个函数是用来发起一系列的IO请求,相当于并发读写多个文件,但是这样与写循环有什么区别呢?

先看mode参数,有LIO_WAITLIO_NOWAIT可选,这个容易理解,就是阻塞和非阻塞的区别。

第二个参数和第三个参数aio_suspend一致,不再赘述,需要说明的aiocb结构体在aio_readaio_write中无需指定操作码,函数本身就已经确定了操作,回头看下结构,里面果然有aio_lio_opcode这样一个参数,man手册中看到该取值可以为LIO_READLIO_WRITELIO_NOP。读、写好理解,这个LIO_NOP又是什么呢?“The LIO_NOP operation causes the list entry to be ignored.”原来什么也不干也行。

还有一个sigevent,看来还是需要将sigevent搞明白才行,不过,可以看到man手册中,在modeLIO_NOWAIT的时候,sigevent才会有效。这个也容易理解,非阻塞方式才需要回调信号处理函数,否则没有意义。

注意:在错误码中,有提到AIO_MAXAIO_LISTIO_MAX限制,查了下资料,这个跟系统有关,当nent参数超过最大值时,会出现EINVAL错误。

sigevent使用解析

一般signal处理我们常使用signal函数,或者sigaction方法来进行绑定即可,sigevent接触的比较少。

Sigevent一般用于异步请求结果通知(咱们讨论的话题)、定时器(sigev_value函数)、消息抵达(mq_notify)。

union sigval {          /* Data passed with notification */

   int     sival_int;         /* Integer value */

   void   *sival_ptr;         /* Pointer value */

};

 

struct sigevent {

   int          sigev_notify; /* Notification method */

   int          sigev_signo;  /* Notification signal */

   union sigval sigev_value;  /* Data passed with

                             notification */

   void       (*sigev_notify_function) (union sigval);

                  /* Function used for thread

                     notification (SIGEV_THREAD) */

   void        *sigev_notify_attributes;

                  /* Attributes for notification thread

                     (SIGEV_THREAD) */

   pid_t        sigev_notify_thread_id;

                  /* ID of thread to signal (SIGEV_THREAD_ID) */

};
先不管其他模式,我们先看sigev_notify等于SIGEV_THREAD的情况。当为SIGEV_THREAD线程处理模式时,相关参数为:
Ø  sigev_notify_function线程处理函数指针。需要注意的是,函数是类型是返回值为空,且带一个union sigval参数,sigval包含两个值,一个int的数值型,一个void*的指针;
Ø  sigev_value这个就是上面函数指针的参数;
Ø  sigev_notify_attributes为处理线程的属性参数,void*类型有些让人摸不着头脑,一般情况下,给NULL值就好,如果需要指定处理线程属性的话,该值是一个指向pthread_attr_t结构体的指针,具体定义可以参见pthread
OK,现在咱们清晰明了,说白了sigevent就是当信号到达时,指定特定的处理方式来处理该信号。当sigev_notify为其他模式时,使用方式也不复杂,具体可以参见http://man7.org/linux/man-pages/man7/sigevent.7.html
说了那么多,我们还是来看个例子吧,简单明了:

#include <unistd.h>

#include <stdio.h>

#include <aio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <errno.h>

#include <sys/timeb.h>

#include <pthread.h>

 

#define BUFFER_SIZE 1024*1014

 

void ptime(const char* tip = ""){

    struct timeb tb;

    ftime(&tb);

    fprintf(stdout, "%s %u : %u\n", tip, tb.time, tb.millitm);

}

 

void aio_finish_hander(union sigval para){

    ptime("finish read");

    struct aiocb my_aiocb = *(struct aiocb*)(para.sival_ptr);

 

    int ret = -1;

 

    /* 检查状态 */

    ret = aio_error(&my_aiocb);

    if(0 == ret){

        /* aio 返回 */

        if(ret = aio_return(&my_aiocb) <= 0){

            fprintf(stdout, "return: %d\n", ret);

            fprintf(stderr, "error: %s\n" ,strerror(errno));

        }

        else{

            fprintf(stdout, "read: %10.10s\n", my_aiocb.aio_buf);

        }

    }

    else{

        fprintf(stderr, "%d error: %s\n" ,ret, strerror(errno));

    }

}

 

int main(){

    /* 句柄,返回码 */

    int fd = -1, ret = -1;

 

    fd = open("./file.txt", O_RDONLY);

    if(fd <= 0){

        fprintf(stderr, "open file errro: %s\n", strerror(errno));

        return -1;

    }

 

    fprintf(stdout, "fd no %d\n", fd);

 

    /* aio控制结构 */

    struct aiocb my_aiocb;

    memset(&my_aiocb, 0, sizeof(my_aiocb));

 

    /* 初始化 */

    my_aiocb.aio_fildes = fd;

    my_aiocb.aio_reqprio = 0;

    my_aiocb.aio_nbytes = BUFFER_SIZE;

    char buf[BUFFER_SIZE+1] = {0};

    my_aiocb.aio_buf = buf;

 

    /* sigevent初始化 */

    my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;

    my_aiocb.aio_sigevent.sigev_notify_function = aio_finish_hander;

    my_aiocb.aio_sigevent.sigev_notify_attributes = NULL;

    my_aiocb.aio_sigevent.sigev_value.sival_int = fd;

    fprintf(stdout, "int para %d\n",my_aiocb.aio_sigevent.sigev_value.sival_int);

    my_aiocb.aio_sigevent.sigev_value.sival_ptr = (void*)(&my_aiocb);

   

    ptime("start read");

 

    /* aio  */

    ret = aio_read(&my_aiocb);

    if(ret < 0){

        fprintf(stderr, "aio read error: %s\n", strerror(errno));

        return -2;

    }

 

    ptime("after read");

 

    sleep(3);

 

    close(fd);

 

    return 0;

}
 

需要说明的是,aio_read之后,不要先将文件句柄关闭,否则sigevent处理线程内aio函数将报错,当然,也不能在线程处理函数还未结束时,就将主线程退出(sleep一下),这样压根就看不到效果了。

程序运行结果如下:

fd no 4

int para 4

start read 1370399183 : 475

after read 1370399183 : 475

finish read 1370399183 : 476

read: 1234567890

看来比轮询aio_error查询AIO状态要快,耗时约为1毫秒

AIO效率如何

简单测试

AIO效率到底如何?咱们先直接读取试一试(代码就不贴了,太简单),运行结果如下:

fd no 4

start read 1370399316 : 870

after read 1370399316 : 871

read: 1234567890

耗时约为1毫秒,基本与AIO一致!看来在单次件读写AIO基本没有啥优势,考虑到线程开销,应该比一般IO要慢才对。

由于测试资源有限,读取大文件50M,各个方法耗时如下:

直接Read

AIO读,aio_error轮询检查

AIO Sigevent

start read 1370400772 : 951

after read 1370400773 : 59

read: 1234567890

start read 1370400993 : 722

reading 1370400993 : 722

after read 1370400993 : 900

read: 1234567890

start read 1370401097 : 898

after read 1370401097 : 898

finish read 1370401098 : 8

read: 1234567890

耗时:108毫秒

耗时:178毫秒

耗时:110毫秒

更加可以确定,在单次IO上,AIO效率并没有提升。

同样的道理,采用多线程并发去读写,与AIO读写差别也不大。

Lio_listio并发测试

上面的测试结果多少在意料之中,也没太多失望,那么,作为可以单次处理多个异步IOAIO核心函数,读写效率是否会上升呢?

由于在我测试环境中,AIO_LISTIO_MAX仅仅为21,没有较强的说明性,不过咱们还是简单的来看一下,21次读取50M的文件,耗时是多少呢?854毫秒,其中,listio耗时为25毫秒。

对比上面的简单测试内容,如果直接单线程循环的话,21次读写理论值为2秒多一点儿,看来,对比单线程来讲,效率提升2.5倍,而且,这个数据应该还是与CPU内核数量有关系(测试环境仅为双核处理器)。

到这里,咱们应该很清楚了,对比自己写多线程处理,AIO的肯定会慢一些,跟简单测试中的结果应该类似,大家不妨试一试。

小结

AIO说白了,就是封装好了的多线程处理异步IO,这多少让我想起Java下面的NIO。对比NIOjava中的大放异彩,AIO却很少被人提及。

AIO在哪里被使用?目前,数据库使用的比较多,如OracleInnoDB,其他的就不太清楚了。

什么时候使用 AIO ?了解 AIO 机制之后,不难发现, AIO 其实是用于解决大量 IO 并发操作而出现的,牺牲一些信号处理耗时,用多线程的方式加速 IO ,提高并行效率。
0
1
分享到:
评论

相关推荐

    linux异步IO.pdf

    ### Linux异步IO详解 #### 引言 在Linux环境下,输入/输出(I/O)操作是系统资源管理和数据交互的核心部分。传统上,Linux采用的最常见I/O模型是同步I/O,其中应用程序在发出请求后会阻塞,直至请求完成。然而,...

    网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO

    - **异步IO**:与之相反,异步IO允许程序在等待I/O操作完成时继续执行其他任务。Java NIO(非阻塞I/O)库提供了异步I/O的支持,通过`AsynchronousSocketChannel`和`AsynchronousServerSocketChannel`等类实现。 2....

    一个简单的异步IO库

    本项目是一个基于C语言在Linux环境下实现的简单异步IO库,旨在提供对socket、文件读取、定时器以及文件状态变更监控的支持。 首先,我们来看一下异步I/O的基本概念。传统的同步I/O模式在执行I/O操作时会阻塞,直到...

    使用异步IO应用程序接口API

    异步 I/O(AIO)是 Linux 系统中一种高效的数据处理机制,它允许应用程序在发起 I/O 操作后不被阻塞,而是继续执行其他任务,直到 I/O 完成并得到通知。这对于 I/O 密集型的进程尤其有益,因为它能够最大化 CPU 的...

    异步输入/输出aio.doc

    根据《Unix 网络编程》的分类,I/O模型可以分为五类:阻塞IO、非阻塞IO、IO复用(如select、poll、epoll)、信号驱动IO和异步IO。然而,POSIX标准将它们简化为两类:同步IO和异步IO。区分这两者的关键在于,同步I/O...

    在HP-UX上配置异步io

    在HP-UX操作系统中,配置异步IO(Asynchronous I/O,简称Aio)是为了提升系统性能,特别是在处理大量I/O操作的场景下,如运行Oracle、Sybase等数据库时。传统的同步IO模式会在每次I/O操作完成后等待确认,这在高负载...

    IO模型编程实例

    本篇文章将深入探讨四种主要的IO模型:阻塞IO、无阻塞IO、多路复用IO(也称为选择器或I/O多路复用)以及异步IO,结合源码分析来帮助理解它们的工作原理和应用。 首先,我们来看**阻塞IO**。在这种模型中,当一个...

    Linux中的异步IO包

    Linux下的异步I/O主要通过libaio库来实现,这正是“Linux中的异步IO包”标题所指的内容。 libaio,全称为“Linux Asynchronous I/O interface”,是一个开源的C语言库,提供了对Linux内核异步I/O接口的封装。libaio...

    IBM 禁用WebSphere应用程序服务器AIO(异步输入输出)

    IBM 禁用WebSphere应用程序服务器AIO(异步输入输出)

    aio.zip_aio_asynchronous io_linux aio

    在Linux操作系统中,AIO(Asynchronous Input/Output,异步I/O)是一种高级I/O接口,它允许程序在发起I/O操作后立即返回,而不是等待操作完成。AIO允许程序执行其他任务,提高系统效率,特别是在处理大量并发I/O请求...

    不同平台异步IO的使用.txt

    ### 不同平台异步IO的使用 #### 引言 异步I/O(Asynchronous I/O,简称AIO)是一种高效的数据访问方式,在多种操作系统平台上都有应用,如Unix、Linux等。相较于同步I/O,异步I/O可以显著提高系统的响应速度与吞吐...

    java IO、NIO、AIO详解.docx

    Java AIO(Asynchronous I/O)是 Java 语言中最新的输入/输出机制,使用异步 IO 模式实现输入/输出操作。在 AIO 中,输入/输出操作是异步式的,即应用程序可以继续执行其他任务,而不需要等待输入/输出操作的完成。...

    复习 J2SE基本内容 IO NIO AIO 的区别

    在J2SE中,我们可以找到三种主要的IO模型:BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O),它们各自具有不同的特性和适用场景。 **BIO(Blocking I/O)**: BIO是传统的Java I/O模型,它...

    AsynchronousIO,异步IO驱动代码和makefile

    //指定信号SIGIO,并绑定处理函数 signal(SIGIO,aio_async_func); //把当前线程指定为将接收信号的进程 fcntl(fd,F_SETOWN,getpid()); //获取当前线程状态 fcntl(fd, F_GETFD); //设置当前线程为 FASYNC 状态

    javaIO模型1

    这里有五种主要的IO模型,它们分别是阻塞IO(BIO)、非阻塞IO、IO多路复用、信号量和异步IO(AIO)。理解这些模型的关键在于区分同步与异步以及阻塞与非阻塞的概念。 同步与异步主要关乎应用程序与内核交互时的等待...

    基于Java的异步IO框架 Cindy.zip

    Java平台上的异步I/O(Asynchronous Input/Output,简称AIO)框架是开发者处理高并发、低延迟场景的重要工具。Cindy是一个这样的框架,它旨在简化Java中的异步编程模型,提供更好的性能和可扩展性。让我们深入探讨...

    18302010012_王栋辉_Java、JavaScript和C++对异步IO的实现方法及其效率1

    Java通过NIO(New IO)和AIO(Asynchronous IO)库提供了异步I/O的支持。NIO引入了选择器(Selector)和通道(Channel)的概念,允许程序在一个线程中同时监控多个通道的事件,当某个通道准备就绪时,可以选择进行...

    Java IO应届生培训讲义

    - 异步IO(AIO):用户进程发起IO请求后可以继续执行其他任务,一旦数据准备好,内核会直接将数据复制到进程空间并通知进程。 5. Java IO类库 Java IO类库主要包含字节流和字符流两大类。字节流通常用于处理二进制...

    IO进程.rar_IO进程基础课程_chairaj4

    1. **IO模型**:介绍各种IO模型,如简单阻塞IO、非阻塞IO、IO复用(select、poll、epoll)、信号驱动IO以及异步IO(AIO)。 2. **缓冲区机制**:如何使用内存缓冲区来优化IO性能,减少CPU与外部设备间的交互次数。 ...

Global site tag (gtag.js) - Google Analytics