文档章节

linux下aio异步读写详解与实例

mickelfeng
 mickelfeng
发布于 2017/07/18 13:39
字数 2623
阅读 52
收藏 0

1.为什么会有异步I/O

aio异步读写是在Linux内核2.6之后才正式纳入其标准。之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非阻塞式来操作I/O的话,那么我们在同一个程序中(不用多线程或多进程)就不能同时操作俩个以上的文件I/O,每次只能对一个文件进行I/O操作,很明显这样效率很低下(因为CPU速度远大于I/O操作的速度,所以当执行I/O时,CPU其实还可以做更多的事)。因此就诞生了相对高效的异步I/O

2.异步I/O的基本概念

所谓异步I/O即我们在调用I/O操作时(读或写)我们的程序不会阻塞在当前位置,而是在继续往下执行。例如当我们调用异步读API aio_read()时,程序执行此代码之后会接着运行此函数下面的代码,并且与此同时程序也在进行刚才所要读的文件的读取工作,但是具体什么时候读完是不确定的

3.异步aio的基本API

API函数 说明
aio_read 异步读操作
aio_write 异步写操作
aio_error 检查异步请求的状态
aio_return 获得异步请求完成时的返回值
aio_suspend 挂起调用进程,直到一个或多个异步请求已完成
aio_cancel 取消异步请求
lio_list 发起一系列异步I/O请求

上述的每个API都要用aiocb结构体赖进行操作 
aiocb的结构中常用的成员有

struct aiocb
{
    //要异步操作的文件描述符
    int aio_fildes;
    //用于lio操作时选择操作何种异步I/O类型
    int aio_lio_opcode;
    //异步读或写的缓冲区的缓冲区
    volatile void *aio_buf;
    //异步读或写的字节数
    size_t aio_nbytes;
    //异步通知的结构体
    struct sigevent aio_sigevent;
}

4异步I/O操作的具体使用

(1)异步读aio_read

aio_read函数请求对一个文件进行读操作,所请求文件对应的文件描述符可以是文件,套接字,甚至管道其原型如下

int aio_read(struct aiocb *paiocb);

该函数请求对文件进行异步读操作,若请求失败返回-1,成功则返回0,并将该请求进行排队,然后就开始对文件的异步读操作 
需要注意的是,我们得先对aiocb结构体进行必要的初始化 
具体实例如下

aio_read

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>


#define BUFFER_SIZE 1024

int MAX_LIST = 2;

int main(int argc,char **argv)
{
    //aio操作所需结构体
    struct aiocb rd;

    int fd,ret,couter;

    fd = open("test.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("test.txt");
    }



    //将rd结构体清空
    bzero(&rd,sizeof(rd));


    //为rd.aio_buf分配空间
    rd.aio_buf = malloc(BUFFER_SIZE + 1);

    //填充rd结构体
    rd.aio_fildes = fd;
    rd.aio_nbytes =  BUFFER_SIZE;
    rd.aio_offset = 0;

    //进行异步读操作
    ret = aio_read(&rd);
    if(ret < 0)
    {
        perror("aio_read");
        exit(1);
    }

    couter = 0;
//  循环等待异步读操作结束
    while(aio_error(&rd) == EINPROGRESS)
    {
        printf("第%d次\n",++couter);
    }
    //获取异步读返回值
    ret = aio_return(&rd);

    printf("\n\n返回值为:%d",ret);


    return 0;
}

上述实例中aiocb结构体用来表示某一次特定的读写操作,在异步读操作时我们只需要注意4点内容 
1.确定所要读的文件描述符,并写入aiocb结构体中(下面几条一样不再赘余) 
2.确定读所需的缓冲区 
3.确定读的字节数 
4.确定文件的偏移量 
总结以上注意事项:基本上和我们的read函数所需的条件相似,唯一的区别就是多一个文件偏移量

值得注意的是上述代码中aio_error是用来获取其参数指定的读写操作的状态的 
其原型如下

int aio_error(struct aiocb *aiopcb);

当其状态处于EINPROGRESS则I/O还没完成,当处于ECANCELLED则操作已被取消,发生错误返回-1

而aio_return则是用来返回其参数指定I/O操作的返回值 
其原型如下

ssize_t aio_return(struct aiocb *paiocb);

如果操作没完成调用此函数,则会产生错误

特别提醒在编译上述程序时必须在编译时再加一个-lrt

上述代码运行结果如下 

(2)异步写aio_write

aio_writr用来请求异步写操作 
其函数原型如下

int aio_write(struct aiocb *paiocb);

aio_write和aio_read函数类似,当该函数返回成功时,说明该写请求以进行排队(成功0,失败-1) 
其和aio_read调用时的区别是就是我们如果在打开文件是,flags设置了O_APPEND则我们在填充aiocb时不需要填充它的偏移量了 
具体实例如下

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>

#define BUFFER_SIZE 1025

int main(int argc,char **argv)
{
    //定义aio控制块结构体
    struct aiocb wr;

    int ret,fd;

    char str[20] = {"hello,world"};

    //置零wr结构体
    bzero(&wr,sizeof(wr));

    fd = open("test.txt",O_WRONLY | O_APPEND);
    if(fd < 0)
    {
        perror("test.txt");
    }

    //为aio.buf申请空间
    wr.aio_buf = (char *)malloc(BUFFER_SIZE);
    if(wr.aio_buf == NULL)
    {
        perror("buf");
    }

    wr.aio_buf = str;

    //填充aiocb结构
    wr.aio_fildes = fd;
    wr.aio_nbytes = 1024;

    //异步写操作
    ret = aio_write(&wr);
    if(ret < 0)
    {
        perror("aio_write");
    }

    //等待异步写完成
    while(aio_error(&wr) == EINPROGRESS)
    {
        printf("hello,world\n");
    }

    //获得异步写的返回值
    ret = aio_return(&wr);
    printf("\n\n\n返回值为:%d\n",ret);

    return 0;
}

具体运行结果请读者自己去试试

(3)使用aio_suspend阻塞异步I/O

aio_suspend函数可以时当前进程挂起,知道有向其注册的异步事件完成为止 
该函数原型如下

int aio_suspend(const struct aiocb *const cblist[],int n,const struct timespec *timeout);

第一个参数是个保存了aiocb块地址的数组,我们可以向其内添加想要等待阻塞的异步事件,第二个参数为向cblist注册的aiocb个数,第三个参数为等待阻塞的超时事件,NULL为无限等待

具体使用如下 
suspend:

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>


#define BUFFER_SIZE 1024

int MAX_LIST = 2;

int main(int argc,char **argv)
{
    //aio操作所需结构体
    struct aiocb rd;

    int fd,ret,couter;

    //cblist链表
    struct aiocb *aiocb_list[2];



    fd = open("test.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("test.txt");
    }



    //将rd结构体清空
    bzero(&rd,sizeof(rd));


    //为rd.aio_buf分配空间
    rd.aio_buf = malloc(BUFFER_SIZE + 1);

    //填充rd结构体
    rd.aio_fildes = fd;
    rd.aio_nbytes =  BUFFER_SIZE;
    rd.aio_offset = 0;

    //将读fd的事件注册
    aiocb_list[0] = &rd;

    //进行异步读操作
    ret = aio_read(&rd);
    if(ret < 0)
    {
        perror("aio_read");
        exit(1);
    }

    couter = 0;
//  循环等待异步读操作结束
    while(aio_error(&rd) == EINPROGRESS)
    {
        printf("第%d次\n",++couter);
    }

    printf("我要开始等待异步读事件完成\n");
    //阻塞等待异步读事件完成
    ret = aio_suspend(aiocb_list,MAX_LIST,NULL);

    //获取异步读返回值
    ret = aio_return(&rd);

    printf("\n\n返回值为:%d\n",ret);


    return 0;
}

(4)lio_listio函数

aio同时还为我们提供了一个可以发起多个或多种I/O请求的接口lio_listio 
这个函数效率很高,因为我们只需一次系统调用(一次内核上下位切换)就可以完成大量的I/O操作 
其函数原型如下

int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);

第一个参数mode可以有俩个实参,LIO_WAIT和LIO_NOWAIT,前一个会阻塞该调用直到所有I/O都完成为止,后一个则会挂入队列就返回

具体实例如下 
lio_listio

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>

#define BUFFER_SIZE 1025

int MAX_LIST = 2;


int main(int argc,char **argv)
{
    struct aiocb *listio[2];
    struct aiocb rd,wr;
    int fd,ret;

    //异步读事件
    fd = open("test1.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("test1.txt");
    }

    bzero(&rd,sizeof(rd));

    rd.aio_buf = (char *)malloc(BUFFER_SIZE);
    if(rd.aio_buf == NULL)
    {
        perror("aio_buf");
    }

    rd.aio_fildes = fd;
    rd.aio_nbytes = 1024;
    rd.aio_offset = 0;
    rd.aio_lio_opcode = LIO_READ;   ///lio操作类型为异步读

    //将异步读事件添加到list中
    listio[0] = &rd;


    //异步些事件
    fd = open("test2.txt",O_WRONLY | O_APPEND);
    if(fd < 0)
    {
        perror("test2.txt");
    }

    bzero(&wr,sizeof(wr));

    wr.aio_buf = (char *)malloc(BUFFER_SIZE);
    if(wr.aio_buf == NULL)
    {
        perror("aio_buf");
    }

    wr.aio_fildes = fd;
    wr.aio_nbytes = 1024;

    wr.aio_lio_opcode = LIO_WRITE;   ///lio操作类型为异步写

    //将异步写事件添加到list中
    listio[1] = &wr;

    //使用lio_listio发起一系列请求
    ret = lio_listio(LIO_WAIT,listio,MAX_LIST,NULL);

    //当异步读写都完成时获取他们的返回值

    ret = aio_return(&rd);
    printf("\n读返回值:%d",ret);

    ret = aio_return(&wr);
    printf("\n写返回值:%d",ret);



    return 0;
}

5.I/O完成时进行异步通知

当我们的异步I/O操作完成之时,我们可以通过信号通知我们的进程也可用回调函数来进行异步通知,接下来我会为大家主要介绍以下回调函数来进行异步通知,关于信号通知有兴趣的同学自己去学习吧

使用回调进行异步通知

该种通知方式使用一个系统回调函数来通知应用程序,要想完成此功能,我们必须在aiocb中设置我们想要进行异步回调的aiocb指针,以用来回调之后表示其自身

实例如下 
aio线程回调通知

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#include<unistd.h>

#define BUFFER_SIZE 1025


void aio_completion_handler(sigval_t sigval)
{
    //用来获取读aiocb结构的指针
    struct aiocb *prd;
    int ret;

    prd = (struct aiocb *)sigval.sival_ptr;

    printf("hello\n");

    //判断请求是否成功
    if(aio_error(prd) == 0)
    {
        //获取返回值
        ret = aio_return(prd);
        printf("读返回值为:%d\n",ret);
    }
}

int main(int argc,char **argv)
{
    int fd,ret;
    struct aiocb rd;

    fd = open("test.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("test.txt");
    }



    //填充aiocb的基本内容
    bzero(&rd,sizeof(rd));

    rd.aio_fildes = fd;
    rd.aio_buf = (char *)malloc(sizeof(BUFFER_SIZE + 1));
    rd.aio_nbytes = BUFFER_SIZE;
    rd.aio_offset = 0;

    //填充aiocb中有关回调通知的结构体sigevent
    rd.aio_sigevent.sigev_notify = SIGEV_THREAD;//使用线程回调通知
    rd.aio_sigevent.sigev_notify_function = aio_completion_handler;//设置回调函数
    rd.aio_sigevent.sigev_notify_attributes = NULL;//使用默认属性
    rd.aio_sigevent.sigev_value.sival_ptr = &rd;//在aiocb控制块中加入自己的引用

    //异步读取文件
    ret = aio_read(&rd);
    if(ret < 0)
    {
        perror("aio_read");
    }

    printf("异步读以开始\n");
    sleep(1);
    printf("异步读结束\n");



    return 0;
}

线程会掉是通过使用aiocb结构体中的aio_sigevent结构体来控制的, 
其定义如下

struct sigevent
{
    sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;
    union {
        int _pad[SIGEV_PAD_SIZE];
         int _tid;

        struct {
            void (*_function)(sigval_t);
            void *_attribute;   /* really pthread_attr_t */
        } _sigev_thread;
    } _sigev_un;
}

#define sigev_notify_function   _sigev_un._sigev_thread._function
#define sigev_notify_attributes _sigev_un._sigev_thread._attribute
#define sigev_notify_thread_id   _sigev_un._tid

本文转载自:http://blog.csdn.net/Shreck66/article/details/48765533

mickelfeng

mickelfeng

粉丝 237
博文 2801
码字总数 604377
作品 0
成都
高级程序员
私信 提问
MySQL · 源码分析 · InnoDB 异步IO工作流程

之前的一篇内核月报InnoDB IO子系统 中介绍了InnoDB IO子系统中包含的同步IO以及异步IO。本篇文章将从源码层面剖析一下InnoDB IO子系统中,数据页的同步IO以及异步IO请求的具体实现过程。 在...

阿里云RDS-数据库内核组
2017/07/09
0
0
MySQL · 引擎特性 · InnoDB IO子系统

前言 InnoDB做为一款成熟的跨平台数据库引擎,其实现了一套高效易用的IO接口,包括同步异步IO,IO合并等。本文简单介绍一下其内部实现,主要的代码集中在os0file.cc这个文件中。本文的分析默...

阿里云RDS-数据库内核组
2017/03/02
0
0
linux异步IO浅析【转】

知道异步IO已经很久了,但是直到最近,才真正用它来解决一下实际问题(在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上。预先知道这些数据的位置,所以预先发起异步IO读请求。...

文艺小青年
2017/05/11
0
0
使用 acl 库编写高并发非阻塞网络通信程序

一、概述 acl 库的 C 库(libacl) 的 aio 模块设计了完整的非阻塞异步 IO 通信过程,在 acl 的C++库(libaclcpp) 中封装并增强了异步通信的功能,本文主要描述了 acl C++ 库之非阻塞IO库的设计...

郑树新
2014/08/25
1K
0
聊聊BIO,NIO和AIO (2)

本文从操作系统的角度来解释BIO,NIO,AIO的概念,含义和背后的那些事。本文主要分为3篇。 第一篇 讲解BIO和NIO以及IO多路复用 第二篇 讲解磁盘IO和AIO 第三篇 讲解在这些机制上的一些应用的...

大宽宽
2018/05/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

用原生js对表格排序

本文转载于:专业的前端网站➸用原生js对表格排序 阿里的模拟笔试题,当时时间有限没写出来,其实是因为自己对原生dom操作不熟悉,这里补一下。 题目的大意是有一个表格,如代码所示 <table>...

前端老手
16分钟前
3
0
IT兄弟连 HTML5教程 HTML5表单 HTML5新增表单元素

HTML5有一些新的表单元素:<datalist>、<keygen>、<output>。不是所有的浏览器都支持HTML5新的表单元素,但即使浏览器不支持该表单属性,仍然可以显示为常规的表单元素。 1 <datalist>元素 ...

老码农的一亩三分地
17分钟前
3
0
【朝花夕拾】Android自定义View篇之(一)View绘制流程

https://www.cnblogs.com/andy-songwei/p/10955062.html

shzwork
19分钟前
4
0
Qt编写自定义控件70-扁平化flatui

一、前言 对于现在做前端开发人员来说,FlatUI肯定不陌生,最近几年扁平化的设计越来越流行,大概由于现在PC端和移动端的设备的分辨率越来越高,扁平化反而看起来更让人愉悦,而通过渐变色产...

飞扬青云
29分钟前
2
0
教你玩转Linux—添加批量用户

添加和删除用户对每位Linux系统管理员都是轻而易举的事,比较棘手的是如果要添加几十个、上百个甚至上千个用户时,我们不太可能还使用useradd一个一个地添加,必然要找一种简便的创建大量用户...

Linux就该这么学
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部