zoukankan      html  css  js  c++  java
  • Linux 异步IO(AIO)

    1.select和poll

    IO多路转换技术, select, poll的原理是: 通过将待监听文件描述符(fd)加入集合, 然后通过查询其调用返回值, 取得数据有动静的fd数量, 再轮询集合中每个fd, 如果有数据, 就直接读取; 如果没有数据, 就等待下一次查询.
    select和poll实现了异步形式通知, 但本质上还是需要主动轮询.

    2. BSD异步IO

    System V和BSD都有一套各自的异步IO, 原理类似, 这里只介绍BSD异步IO.
    BSD异步IO是信号SIGIO, SIGURG的组合: SIGIO 通用异步IO信号; SIGURG 用来通知进程网络连接上的带外数据(data of band, 紧急数据)已经达到.

    进程接收SIGIO信号, 需要执行的步骤:

    1. 调用signal/sigaction为SIGIO信号建立处理程序;
    2. 调用fcntl, 命令参数F_SETOWN, 设置进程ID或进程组ID, 用于告诉驱动程序/内核, 指定进程接收SIGIO信号;
    3. 调用fcntl, 命令参数F_SETFL, 设置O_ASYNC文件状态标识, 以便在该fd上可以进行异步IO;

    进程接收SIGURG, 只需只需第1,2步. 信号仅对引用支持带外数据的网络连接描述符 产生, 如TCP连接(UDP不支持).

    什么是带外数据?
    请参考带外数据 | 博客园

    BSD异步IO例程
    完整源代码, 请参见 async.c
    关键步骤代码

    void sig_fun() {
        int data = 0;
        int n = read(mousefd, &data, sizeof(data));
        if (n < 0) {
            printf("read mouse error
    ");
        }
        else {
            printf("%d
    ", data);
        }
    }
    
    struct sigaction sa;
    struct sigaction od_sa;
    
    sa.sa_handler = sig_fun;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGIO); // SIGIO添加进信号集
    sa.sa_flags = 0;
    
    // 1. 调用sigaction为SIGIO信号建立信号处理程序
    sigaction(SIGIO, &sa, &od_sa); // 捕获SIGIO后, 处理信号时, 阻塞信号; 处理完毕后恢复
    
    // 2. 以命令F_SETOWN调用fcntl来设置进程ID, 用于接收对该描述符的信号(SIGIO)
    if (fcntl(mousefd, F_SETOWN, getpid()) < 0) {
        perror("fcntl F_SETOWN error");
        exit(1);
    }
    
    // 3. 以命令F_SETFL调用fcntl, 设置O_ASYNC文件状态标识, 使得在该描述符上可以进行异步IO
    int flag = fcntl(mousefd, F_GETFL);
    if (flag < 0) {
        perror("fcntl F_GETFL error");
        exit(1);
    }
    flag |= O_ASYNC;
    
    ret = fcntl(mousefd, F_SETFL, flag);
    if (ret < 0) {
        perror("fcntl F_SETFL error");
        exit(1);
    }
    
    while (1) {
        usleep(50);
    }
    

    3. POSIX异步IO(AIO)

    BSD对不同的设备文件进行异步IO方法不一样, 如终端设备是产生SIGIO信号, 仅支持带外数据的设备才能产生SIGURG信号.
    POSIX对不同类型文件进行异步IO提供一套一致的方法, SUSv4中, 这些接口被移到了基本部分中, 所以现在所有的平台都被要求支持这些接口.

    3.1 AIO控制块

    异步IO接口使用AIO控制块来描述IO操作.
    AIO控制块由aiocb结构定义:

    #include <aiocb.h>
    
    struct aiocb {
       /* The order of these fields is implementation-dependent */
    
       int             aio_fildes;     /* File descriptor */
       off_t           aio_offset;     /* File offset */
       volatile void  *aio_buf;        /* Location of buffer */
       size_t          aio_nbytes;     /* Length of transfer */
       int             aio_reqprio;    /* Request priority */
       struct sigevent aio_sigevent;   /* Notification method */
       int             aio_lio_opcode; /* Operation to be performed;
                                          lio_listio() only */
    
       /* Various implementation-internal fields not shown */
    };
    

    字段说明
    aio_fildes 表示已打开的文件描述符, 用于读/写;
    aio_offset 读写操作从aio_offset指定的偏移量开始;
    aio_buf 用于读写操作转存数据的缓冲区;
    aio_nbytes 缓冲区aio_buf的大小;
    aio_reqprio 应用程序使用该字段为异步IO请求提示顺序. 值必须介于0和sysconf(_SC_AIO_PRIO_DELTA_MAX)返回值之间. 文件同步操作忽略该字段;
    aio_lio_opcode 应当进行的操作类型, 只能用于lio_listio (基于列表的异步IO), 值描述见lio_listio章节;
    aio_sigevent 指明IO事件完成后, 如何通知应用程序.
    sigevent结构:

    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) */
    };
    

    sigevent字段说明:
    -sigev_notify 通知类型, 其取值只能是这3个之一: SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD.
    1)SIGEV_NONE 异步IO请求完成后, 不通知进程;
    2)SIGEV_SIGNAL 异步IO请求完成后, 产生由sigev_signo字段指定的信号. 也就是说, 需要应用程序捕捉sigev_signo表示的信号, 并在信号处理程序中完成IO数据操作.
    3)SIGEV_THREAD 异步IO请求完成时, 调用sigev_notify_function指定的函数, sigev_value作为唯一参数被传入. 除非sigev_notify_attributes字段被设定为pthread属性结构的地址, 且该结构指定了一个另外的线程属性, 否则该函数将在线程分离状态的一个单独的线程中执行.

    3.2 aio_read & aio_write

    使用异步IO前, 应先对AIO控制块(struct aiocb对象)进行初始化.

    aio_read - 异步读, aio_write - 异步写:

    #include <aio.h>
    
    int aio_read(struct aiocb *aiocb);
    
    int aio_write(struct aiocb *aiocb);
    

    描述
    将异步IO请求放入等待处理的队列中(函数提出请求, 由OS放入). 函数返回值与实际IO操作结果没有关系. IO操作等待时, 需确保AIO控制块和数据缓冲区保持稳定, 下面对应的内容也必须始终合法, 不能被释放, 也不能被复用, 除非IO操作完成.
    aio_read是read的异步模拟, aio_write是write的异步模拟.

    read(fd, buf, count);
    write(fd, buf, n);
    

    返回值
    成功返回0; 失败-1

    3.3 aio_fsync

    aio_fsync - 异步文件同步:
    强制所有(等待队中)等待的异步操作不等待, 而直接写入持久化的存储中(通常指磁盘, emmc等), 可以设置一个AIO控制块并调用aio_fsync.

    #include <aio.h>
    
    int aio_fsync(int ap, struct aiocb *aiocb);
    

    描述
    aiocb->aio_fildes字段(文件描述符)指定异步写操作被同步的文件.
    如果op = O_DSYNC, 那么操作执行像调用fdatasync, 函数立即返回, 但IO操作完成前, 文件数据不会被持久化;
    如果op = O_SYNC, 那么操作执行像调用fsync, 函数立即返回, 但IO操作完成前, 文件数据和属性不会被持久化;

    sync, fsync, fdatasync, fflush是什么?
    参考sync、fsync、fdatasync、fflush函数区别和使用举例 | CSDN

    函数名称 作用描述
    sync 将所有修改过的(内核)快缓存区排队进写队列, 然后返回, 并不等待实际写磁盘操作结束
    fsync 只对由fd指定单一文件起作用, 并且等待磁盘操作结束, 然后返回
    fdatasync 类似于fsync, 但只影响文件的数据部分, 不像fsync还会同步更新文件的属性
    fflush 冲刷IO库缓存, 将库缓存内容写入内核缓冲区

    3.4 aio_error

    aio_error - 获取异步IO操作(异步读、写或同步)的完成状态

    #include <aio.h>
    
    int aio_error(const struct aiocb *aiocb);
    

    描述
    函数返回异步IO请求的错误状态, aiocb指向AIO控制块, 代表了异步IO请求信息.

    返回值
    0 异步操作成功, 需要调用aio_return 函数获取操作返回值;
    -1 对aio_error调用失败, errno被设置;
    EINPROGRESS 异步读、写或同步操作仍在等待;
    其他值 相关异步操作失败返回的错误码(errno);

    3.5 aio_return

    aio_error提到, 返回0时表示异步操作成功, 可以调用aio_return获取操作返回值.
    aio_return - 获取异步IO操作返回值

    int <aio.h>
    
    int aio_return(const struct aiocb *aiocb);
    

    描述
    注意:

    1. 异步操作完成之前, 不要调用aio_return, 其行为是未定义的;
    2. 对每个异步操作调用一次aio_return, 因为一旦调用了, OS就能释放包含了IO操作返回值的记录;

    返回值
    失败返回-1, errno被设置; 成功时, 返回异步操作结果, 即返回(同步版本)read、write或fsync在被成功调用时可能返回的结果.

    3.6 aio_suspend

    aio_suspend - 等待异步IO操作完成, 或超时

    #include <aio.h>
    
    int aio_suspend(const struct aiocb *const list[], int nent, const strct timespec *timeout);
    

    描述
    执行IO操作时, 如果有其他事务处理而不想被IO操作阻塞, 可以使用异步IO. 如果事务执行完毕后, 还有异步操作尚未完成时, 可调用aio_suspend函数阻止进程, 直到操作完成.

    参数
    list 指向AIO控制块数组的指针
    nent 表明数组的元素个数
    timeout 超时时间

    返回值
    3种情况:

    1. 如果被一个信号中断, 返回-1, errno设置为EINTR;
    2. 如果没有任何IO操作完成, 阻塞时间超时, 返回-1, errno设置为EAGAIN;
    3. 如果有任何IO操作完成, 返回0; 如果所有的异步IO操作都已完成, aio_suspend将在不阻塞的情况下直接返回;

    3.7 aio_cancel

    aio_cancel - 取消未完成的异步IO请求

    #include <aio.h>
    
    int aio_cancel(int fd, struct aiocb *aiocb);
    

    描述
    如果不想完成还在等待中的异步IO操作时, 可以调用aio_cancel尝试取消. 描述为尝试, 是因为系统无法保证一定能取消正在进行的任何操作.
    如果异步IO操作成功取消, 相应AIO控制块调用aio_error将返回错误ECANCELED; 如果操作不能被取消, 那么相应的AIO控制块不会被修改

    参数
    fd 指定未完成的异步IO操作的文件描述符
    aiocb 如果aiocb = NULL, 系统会尝试取消所有该文件上未完成的异步IO操作; 其他情况, 系统将尝试取消aiocb指向的单个AIO控制块描述的单个异步IO操作.

    返回值
    4个值之一:
    AIO_ALLDONE 所有操作在尝试取消前, 已经完成;
    AIO_CANCELED 所有要求的操作已被取消;
    AIO_NOTCANCELED 至少有一个要求的操作没有被取消;
    -1 对aio_cancel调用失败, 设置errno;

    3.8 lio_listio

    lio_listio - 初始化io请求列表

    #include <aio.h>
    
    int lio_listio(int mode, struct aiocb *const aiocb_list[], int nitems, struct sigevent *sevp);
    

    描述
    既能以同步方式使用, 也能以异步的方式使用. 函数提交一系列由一个AIO控制块列表描述的IO请求.
    每个AIO控制块中, aio_lio_opcode字段指定了该操作是一个读操作(LIO_READ), 写操作(LIO_WRITE), 还是将忽略的空操作(LIO_NOP). 读操作, 会按照对应的AIO控制块被传给aio_read来处理; 写操作, 会被传给aio_write处理.

    参数
    mode 决定IO释放真的是异步的. 取值说明:

    1. LIO_WAIT 调用块将等到所有操作完成, sevp参数将会被忽略;
    2. LIO_NOWAIT IO请求入队后, 立即返回, 进程在所有IO操作完成后, 按sigev指定的, 被异步通知. 如果不想被通知, sigev可设置为NULL. 被sigev指定的异步通知, 是在每个AIO控制块本身的异步通知之外的.

    aiocb_list 指向AIO控制块列表, 指定了要运行的IO操作.

    nitems 指定了aiocb_list数组元素格式.

    实现限制
    实现一般会限制一些参数的实际取值
    POSIX.1中异步IO运行时不变量的值

    名称 描述 可接受的最小值
    AIO_LISTIO_MAX 单个列表IO调用中的最大IO操作数 _POSIX_AOI_LISTIO_MAX
    AIO_MAX 未完成的异步IO操作的最大数目 _POSIX_AIO_MAX
    AIO_PRIO_DELTA_MAX 进程可以减少的异步IO优先级的最大值 0

    4. AIO的使用例程

    以从一个文件读取数据, 然后写到另外一个文件为例.

    4.1 同步IO操作

    流程

    4.2 异步IO操作 (AIO)

    流程

  • 相关阅读:
    linux内核中GNU C和标准C的区别
    linux内核中GNU C和标准C的区别
    Getting start with dbus in systemd (02)
    Getting start with dbus in systemd (01)
    Getting start with dbus in systemd (03)
    物理内存相关的三个数据结构
    数据类型对应字节数(32位,64位 int 占字节数)
    Linux kernel 内存
    共模电感的原理以及使用情况
    [原创]DC-DC输出端加电压会烧毁
  • 原文地址:https://www.cnblogs.com/fortunely/p/14806255.html
Copyright © 2011-2022 走看看