Linux之异步I/O - Go语言中文社区

Linux之异步I/O


提问:

  1. 异步I/O(AIO)有哪些接口?如何使用?
  2. 为什么需要异步I/O?优缺点是什么?

基本概念:

  1. 在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO 中,这是一个 aiocb(AIO I/O Control Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O 操作。

1.异步I/O(AIO)有哪些接口?如何使用?

1.1常用结构体和接口

API 函数

说明

aio_read

请求异步读操作

aio_error

检查异步请求的状态

aio_return

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

aio_write

请求异步写操作

aio_suspend

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

aio_cancel

取消异步 I/O 请求

lio_listio

发起一系列 I/O 操作

1.1.1结构体

manual手册中的截图

struct aiocb {    

        int aio_fildes;                   //文件描述符,可以使socket
        int aio_offset;                   //文件内偏移量
        volatile void *aio_buf;           //缓冲区的地址(指针)  
        size_t aio_nbytes;                //传输长度
        int aio_reqprio;                  //响应优先级
        struct sigevent aio_sigevent;     //通知方式
        int aio_lio_opcode;               
        
         ...  
};

 

 

1.1.2 aio_read 库函数

#include <aio.h>

int aio_read(struct aiocb *aiocbp);

Link with -lrt
本函数从aiocbp.aio_fildes指向的文件中读取aiocbp.aio_nbytes字节数的数据至aiocbp.aio_buf指向的缓冲区。
读取的起始地址由aiocbp.aio_offset指定。

成功返回0,失败返回-1,表示请求失败,并设置errno值。
如果是之后出现的错误,会有aio_return()和aio_error()得到相应返回值和errno 。

 

1.1.3 aio_write 库函数

#include <aio.h>

int aio_write(struct aiocb *aiocbp);

Link with -lrt
本函数从aiocbp.aio_buf指向的缓冲区中读取aiocbp.aio_nbytes字节数的数据至aiocbp.aio_fildes指向的文件。
写入的起始地址由aiocbp.aio_offset指定。

成功返回0,失败返回-1,表示请求失败,并设置errno值。
如果是之后出现的错误,会有aio_return()和aio_error()得到相应返回值和errno 。

 

1.1.4 aio_return

ssize_t aio_return(struct aiocb *aiocbp);

Link with -lrt.
该函数通过参数返回异步IO请求的控制块的最后返回状态。
成功,如果异步IO完成,返回传输的字节数;如果未完成,返回0 。
失败返回-1,设置errno值。
错误情况
1.aiocbp指向非struct aiocb
2.aio_return未实施

 

1.1.5 aio_error

int aio_error(const struct aiocb *aiocbp);

Link with -lrt
接收错误的异步IO请求的控制块的状态
返回值
       该函数返回下列值:

       *  EINPROGRESS, 请求还未完成.

       *  ECANCELED, 请求被取消了.

       *  0, 请求成功完成了.

       *  一个正的错误值, 如果异步I/O操作失败. 这个值同样会存入errno中

1.1.6 aio_read模板

#include <func.h>

#define BUFFER_SIZE 1024
int MAX_LIST = 2;
int main(int argc,char **argv)
{
    struct aiocb rd;

    int fd,ret,couter;

    fd = open("aio_write.i",O_RDONLY);
    if(-1 == fd)
    {
        perror("open");
    }
    //将rd结构体清空
    bzero(&rd,sizeof(rd));
    //为rd.aio_buf分配空间
    rd.aio_buf = malloc(BUFFER_SIZE + 1);
    bzero((void*)rd.aio_buf, BUFFER_SIZE+1);
    //填充rd结构体
    rd.aio_fildes = fd;
    rd.aio_nbytes = BUFFER_SIZE;
    rd.aio_offset = 0;

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

    couter = 0;
     // 循环等待异步读操作结束
    while(aio_error(&rd) == EINPROGRESS)
    {
    ret = aio_return(&rd);//这个不会阻塞,直接返回0

    printf("nn返回值为:%dn",ret);    
        printf("第%d次n",++couter);
    }
    //获取异步读返回值
    ret = aio_return(&rd);//这个不会阻塞,直接返回0

    printf("nn返回值为:%dn",ret);    

    //printf("%sn", (char*)rd.aio_buf);//不等待异步返回,就会导致直接输出 空字符串
    free((void*)rd.aio_buf);
    return 0;
}

1.1.6 aio_write模板

#include <func.h>

#define BUFFER_SIZE 1024

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

    fd = open("file2",O_RDWR| O_CREAT, 0666);
    if(-1 == fd)
    {
        perror("open");
        exit(-1);
    }

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

    char buf[20] = {"helloMonkey!"};
    //为wr.aio_buf分配空间
    wr.aio_buf = buf;//让结构体缓冲区指针指向需要传送的数据
    //填充wr结构体
    wr.aio_fildes = fd;
    wr.aio_nbytes = strlen(buf);//传输的字节数
    wr.aio_offset = 0;

    //进行异步读操作
    ret = aio_write(&wr);
    if(-1 == ret)
    {
        perror("aio_write");
        exit(-1);
    }

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

    return 0;
}





1.1.7 aio_suspend

int aio_suspend(const struct aiocb * const aiocb_list[],
                       int nitems, const struct timespec *timeout);

Link with -lrt.
阻塞当前进程,直到一个异步请求完成或者设定时间结束或者一个信号到达。
参数aiocb_list[]是struct aiocb的链表,其中任意一个完成都会唤醒进程。
参数nitems直到参数aiocb_list中元素个数。
参数timeout为NULL,表示阻塞等待时间为无限,直到有异步请求完成或者信号到达才继续执行。
           不为空,函数会阻塞进程设定好的时间。
           

上面一个参数表示秒,下面参数表示纳秒。

1.1.8 aio_cancel

int aio_cancel(int fd, struct aiocb *aiocbp);

Link with -lrt.
该函数尝试取消对文件描述符fd发起的异步IO申请。
成功返回以下三个值,失败返回-1.
AIO_CANCELED            所有申请被成功取消
AIO_NOTCANCELED         至少有一个没被取消,因为它在进程中。
AIO_ALLDONE             所有申请都已经被完成了
如果参数aiocbp为NULL,所有关于这个文件描述符的申请都会取消。否则,只有aiocbp指定的异步IO申请会取消。

 

1.1.9 lio_listio

int lio_listio(int mode, struct aiocb *const aiocb_list[],
                      int nitems, struct sigevent *sevp);

Link with -lrt.
将aiocb_list数组中的每一个异步IO申请初始化。

参数mode为
LIO_WAIT    进程会被阻塞,直到所有异步IO操作(不是申请)完成,这时参数sevp会被忽视
LIO_NOWAIT  所有异步IO申请进入队列,函数立即返回。当所有异步IO操作完成,由参数sevp指定的异步通知发生

参数aiocb_list为一个数组,存放每一个异步IO申请的结构体。
参数nitems指定参数aiocb_list的长度。
参数sevp指定的异步操作完成后的通知方式。

1.1.10 异步通知方式结构体分析及简单例子

union sigval {          /*随通知传送的数据 */
           int     sival_int;         /* 整形 */
           void   *sival_ptr;         /* 指针 */
       };

struct sigevent {
     int             sigev_notify;    /* 通知方式 */
     int             sigev_signo;     /* 通知信号 */
     union sigval    sigev_value;     /* 随通知传送的数据 */
     void            (*sigev_notify_function) (union sigval);   
                            /* 用于线程通知的函数 (SIGEV_THREAD) */
                   
     void            *sigev_notify_attributes;
                            /* 线程通知的属性(SIGEV_THREAD) */
                         
     pid_t           sigev_notify_thread_id;
                            /* 线程的id (SIGEV_THREAD_ID) */
       };

测试例子1:(通过线程回调通知)

#include <func.h>

#define BUFFER_SIZE 1024

void aioHandler(sigval_t sigval){
    //这时异步操作结束,释放资源,回收文件描述符
    struct aiocb *req;
    int ret;
    req = (struct aiocb*)sigval.sival_ptr;
    if(aio_error(req) == 0){
        ret = aio_return(req);
        printf("异步IO成功n");
        //将数据由缓冲区写入文件,可以由一个自己设的结构体带入想写入文件的fd
        write(STDOUT_FILENO, (void*)req->aio_buf, req->aio_nbytes);
    }
    close(req->aio_fildes);
    free((void*)req->aio_buf);
}


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

    int fd,ret;

    fd = open("aio_write.i",O_RDONLY);
    if(fd < 0)
    {
        perror("test.txt");
    }
    //将rd结构体清空
    bzero(&rd,sizeof(rd));
    //为rd.aio_buf分配空间
    rd.aio_buf = malloc(BUFFER_SIZE + 1);
    bzero((void*)rd.aio_buf, BUFFER_SIZE+1);
    //填充rd结构体
    rd.aio_fildes = fd;
    rd.aio_nbytes =  BUFFER_SIZE;
    rd.aio_offset = 0;
    //通知方式设定,函数结束栈空间会释放,堆空间不会
    rd.aio_sigevent.sigev_notify = SIGEV_THREAD;
    //将rd的地址传进去,也可以传入你想要的结构体指针(堆)
    rd.aio_sigevent.sigev_value.sival_ptr = &rd; 
    rd.aio_sigevent.sigev_notify_function = aioHandler;
    rd.aio_sigevent.sigev_notify_attributes = NULL;
    //进行异步读操作
    ret = aio_read(&rd);
    if(ret < 0)
    {
        perror("aio_read");
        exit(1);
    }
    sleep(2);//防止进程终止,等一下
    //获取异步读返回值
    ret = aio_return(&rd);//这个不会阻塞,直接返回0

    printf("nn返回值为:%dn",ret);    

    //printf("%sn", (char*)rd.aio_buf);//不等待异步返回,就会导致直接输出 空字符串
    return 0;
}





测试例子2(通过信号通知):

  • siginfo_t 可以同过 man sigaction 查询
#include <func.h>

#define BUFFER_SIZE 1024

void aioHandler(int signo, siginfo_t* info, void* content){
    //这时异步操作结束,释放资源,回收文件描述符
    struct aiocb *req;
    int ret;
    if(info->si_signo == SIGIO){
        req = (struct aiocb *)info->si_value.sival_ptr;
        if(aio_error(req) == 0){
            printf("传输完成n");
            ret = aio_return(req);
            write(STDOUT_FILENO, (void*)req->aio_buf, req->aio_nbytes);
        }
        close(req->aio_fildes);
        free((void*)req->aio_buf);
    }
}


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

    int fd,ret;

    fd = open("aio_write.i",O_RDONLY);
    if(fd < 0)
    {
        perror("test.txt");
    }
    //将rd结构体清空
    bzero(&rd,sizeof(rd));
    //为rd.aio_buf分配空间
    rd.aio_buf = malloc(BUFFER_SIZE + 1);
    bzero((void*)rd.aio_buf, BUFFER_SIZE+1);
    //填充rd结构体
    rd.aio_fildes = fd;
    rd.aio_nbytes =  BUFFER_SIZE;
    rd.aio_offset = 0;
    //通知方式设定,函数结束栈空间会释放,堆空间不会
    rd.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
    //将rd的地址传进去,也可以传入你想要的结构体指针(堆)
    rd.aio_sigevent.sigev_value.sival_ptr = &rd; 
    rd.aio_sigevent.sigev_signo = SIGIO;
    
    //注册信号
    struct sigaction sig_act;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags = SA_SIGINFO;
    sig_act.sa_sigaction = aioHandler;
    ret = sigaction(SIGIO, &sig_act, NULL);
    //进行异步读操作
    ret = aio_read(&rd);

    //rd.aio_buf = NULL;//加上这个后,输出失败。
    //rd.aio_nbytes = 1;//加上这个后,只输出一个字符
    /*
        综合上面的结果,内核所拿到的是rd的地址,如果rd在函数空间,然后函数结束
        如果有其它数据覆盖了函数栈空间,那么就会出现脏数据和越界访问。
        可以通过申请堆空间来避免这一情况,或者函数运行时间足够,或者由上一层来分配rd空间
        再通过指针将rd传入。
        此外,异步IO过程中,不应该修改rd的值,不然会导致io失败。
    */

    if(ret < 0)
    {
        perror("aio_read");
        exit(1);
    }
    sleep(2);//防止进程终止,等一下
    //获取异步读返回值
    ret = aio_return(&rd);//这个不会阻塞,直接返回0
    
    printf("nn返回值为:%dn",ret);    

    //printf("%sn", (char*)rd.aio_buf);//不等待异步返回,就会导致直接输出 空字符串
    return 0;
}

 

1.1.11使用注意事项

  • 当进程结束,异步IO操作就直接终止了,不会继续执行。(内核中呢?直接释放还是操作完才终止?)
  • 注意资源的释放,一般来说,哪一层申请,哪一层负责释放。(个人看法)

问题:调用aio_read时,如果rd是在某个函数空间,那么函数结束后,空间释放了,是否异步IO就失败了?

       综合上面的结果,内核所拿到的是rd的地址,如果rd在函数空间,然后函数结束。如果有其它数据覆盖了函数栈空间,那么就会出现脏数据和越界访问。可以通过申请堆空间来避免这一情况,或者函数运行时间足够,或者由上一层来分配rd空间再通过指针将rd传入。此外,异步IO过程中,不应该修改rd的值,不然会导致io失败。

      也可以在函数的最后加上检测,保证io执行完毕。

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

 

2.为什么需要异步I/O?优缺点是什么?

2.1 异步I/O的使用场景

  1. 使用异步 I/O 可以帮助我们构建 I/O 速度更快、效率更高的应用程序。

2.2 异步I/O优点

  1. 阻塞模型需要在 I/O 操作开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 I/O 操作。同步非阻塞模型允许处理和 I/O 操作重叠进行,但是这需要应用程序根据重现的规则来检查 I/O 操作的状态。这样就剩下异步非阻塞 I/O 了,它允许处理和 I/O 操作重叠进行,包括 I/O 操作完成的通知。
  2. 异步I/O显着减少应用程序等待I / O所花费的时间

2.3 异步I/O的缺点

 

参考资料:

https://www.ibm.com/developerworks/cn/linux/l-async/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/birdunderastarrysky/article/details/90896808
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 19:53:11
  • 阅读 ( 1175 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢