异步 I/O 访问

更新时间:
2024-05-13
下载文档

异步 I/O 访问

信号机制提供了一种以异步方式通知某种事件已发生的方法,但是,这种异步 I/O 是有限制的,它们并不能用在所有的文件类型上,而且只能使用一个信号。如果要对一个以上的文件描述符进行异步 I/O,那么在进程收到该信号时并不知道这一信号对应哪一个文件描述符。

SylixOS 支持 POSIX1003.1b 实时扩展协议规定的标准异步 I/O 接口,即 aio_read 函数、aio_write 函数、aio_fsync 函数、aio_cancel 函数、aio_error 函数、aio_return 函数、aio_suspend 函数和 lio_listio 函数。这组 API 用来操作异步 I/O。异步 I/O 是针对同步 I/O 提出的概念,它不需要线程等待 I/O 结果,只需要请求进行传输,系统会自动完成 I/O 传输,结束或者出现错误时会产生相应的 I/O 信号,用户程序只需要设置好对应的信号陷入函数,即可处理一个异步 I/O 事件。

POSIX 异步 I/O 接口为不同类型的文件进行异步 I/O 提供了一套一致的方法。这些接口来自实时草案标准,这些异步 I/O 接口使用 AIO 控制块来描述 I/O 操作。aiocb 结构体定义了 AIO 控制块,SylixOS 实现该结构体如所示:

struct aiocb {
    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 offset.        */
    struct sigevent     aio_sigevent;   /*  Signal number and value.        */
    int                 aio_lio_opcode; /*  Operation to be performed.      */
    ……
};

下面对该结构的成员做详细说明:

  • aio_fildes 是要操作的文件描述符,即 open 函数返回的文件描述符。
  • aio_offset 是读、写开始的文件偏移量。
  • aio_buf 是数据缓冲区指针,对于读,从该缓冲区读出数据。对于写,向该缓冲区中写入数据。
  • aio_nbytes 是读、写的字节数。
  • aio_reqprio 为异步 I/O 请求的优先级,该优先级决定了读写的顺序,也就意味着优先级越高越先被读或者写。
  • aio_sigevent 是要发送的信号,当一次读或者写完成后,会发送应用指定的信号。
  • aio_lio_opcode 是异步 I/O 操作的类型,如下表所示。
操作类型说明
LIO_NOP没有传输请求
LIO_READ请求一个读操作
LIO_WRITE请求一个写操作
LIO_SYNC请求异步 I/O 同步

在异步 I/O 操作之前我们需要先初始化 AIO 控制块,然后通过调用 aio_read 函数请求异步读操作和调用 aio_write 函数请求异步写操作。

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

函数 aio_read 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 paiocb 是 AIO 控制块。

函数 aio_write 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 paiocb 是 AIO 控制块。

调用 aio_read 函数和 aio_write 函数成功之后,异步 I/O 请求便已经被操作系统放入等待处理的队列中了。这些返回值与实际 I/O 操作的结果没有任何关系,如果需要查看函数的返回状态可调用 aio_error 函数。

如果想强制所有等待中的异步操作不等待而写入存储中,可以建立一个 AIO 控制块并调用 aio_fsync 函数。

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

函数 aio_fsync 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 op 是操作选项。
  • 参数 paiocb 是 AIO 控制块。

结构体 struct aiocb 中 aio_fildes 成员是要被同步的文件,如果 op 参数设定为 O_SYNC, aio_fsync 调用类似于 fsync,如果 op 参数设定为 O_DSYNC,aio_fsync 调用类似于 fdatasync(目前 SylixOS 没有对这两种情况做具体的区分)。

同 aio_read 函数和 aio_write 函数一样,查看 aio_fsync 函数处理的状态可调用 aio_error。

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

函数 aio_error 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 paiocb 是 AIO 控制块。

返回值应为下面的 4 种情况之一:

  • 0:异步操作成功完成,调用 aio_return 函数可获得返回码。
  • -1:对 aio_error 调用失败。
  • EINPROGRESS:异步操作等待中。
  • 其他情况:其他任何返回值是相关异步操作失败返回的错误码。

如果异步操作成功,可以调用 aio_return 函数来获得异步操作的返回值。

#include <aio.h>
ssize_t  aio_return(struct aiocb *paiocb);

函数 aio_return 原型分析:

  • 此函数成功返回异步操作完成的返回值,失败返回-1 并设置错误号。
  • 参数 paiocb 是 AIO 控制块。

执行 I/O 操作时,如果还有其他事务要处理而不想被 I/O 操作阻塞,就可以使用异步 I/O。然而,如果在完成了所有事务时,还有异步操作未完成时,可以调用 aio_suspend 函数来阻塞进程,直到操作完成。

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

函数 aio_suspend 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 list 是 AIO 控制块数组。
  • 参数 nent 是数组元素个数。
  • 参数 timeout 是设定的超时时间。

aio_suspend 可能会返回以下 3 种情况中的一种:

  • 如果阻塞超时了,那么 aio_suspend 函数将返回-1。
  • 如果有任何 I/O 操作完成,aio_suspend 函数将返回 0。
  • 如果在调用 aio_suspend 函数时,所有的异步 I/O 操作都已完成,那么 aio_suspend 函数将在不阻塞的情况下直接返回。

参数 list 会自动忽略空指针,非空元素是经过初始化的 AIO 控制块。

当还存在不需要再完成的等待中的异步 I/O 操作时,可以调用 aio_cancel 函数来取消它们。

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

函数 aio_cancel 原型分析:

  • 此函数返回值,如下表所示。
返回值类型说明
AIO_CANCELED所有请求的操作已被取消
AIO_NOTCANCELED至少有一个请求操作没被取消
AIO_ALLDONE在请求取消之前已经完成
-1aio_cancel 函数调用失败
  • 参数 fildes 是要操作的文件描述符。
  • 参数 paiocb 是 AIO 控制块。

如果 paiocb 为 NULL,系统将会尝试取消所有该文件上未完成的异步 I/O 操作。其他情况下,系统将尝试取消由 paiocb 指定的 AIO 控制块描述的单个异步 I/O 操作。

如果异步 I/O 操作被成功取消,那么相应的 AIO 控制块调用 aio_error 函数将会返回错误 ECANCELED。如果操作不能被取消,那么相应的 AIO 控制块不会因为对 aio_cancel 函数的调用而被修改。

下面程序展示了以上函数的使用方法,程序构造一个读操作的 AIO 控制块,然后调用 aio_read 请求读操作,操作完成后会通过信号(见信号系统章节)通知给 signal_handler 函数,以做进一步的处理。

#include <aio.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define BUFSIZE  (64)

void signal_handler (union sigval val)
{
    struct aiocb   *paio = (struct aiocb *)val.sival_ptr;
    ssize_t         ret;

    ret = aio_return(paio);
    if (ret < 0) {
        fprintf(stderr, "aio_return error.\n");
        return;
    }
    fprintf(stdout, "len: %ld\n content: %s\n", ret, (char *)(paio->aio_buf));
}

int main (int argc, char *argv[])
{
    int                         fd;
    int                         ret;
    static struct aiocb         aio;
    const struct aiocb  *const  list[] = {&aio};

    fd = open("file", O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "open file failed.\n");
        return  (-1);
    }
    memset(&aio, 0, sizeof(struct aiocb));
    /*
     *  设置AIO控制块
     */
    aio.aio_fildes                          = fd;
    aio.aio_buf                             = malloc(BUFSIZE + 1);
    aio.aio_nbytes                          = BUFSIZE;
    aio.aio_offset                          = 0;
    aio.aio_reqprio                         = 1;
    aio.aio_lio_opcode                      = LIO_READ;
    aio.aio_sigevent.sigev_notify           = SIGEV_THREAD | SIGEV_SIGNAL;
    aio.aio_sigevent.sigev_value.sival_ptr  = (void *)&aio;
    aio.aio_sigevent.sigev_signo            = SIGUSR1;
    aio.aio_sigevent.sigev_notify_function  = signal_handler;
    ret = aio_read(&aio);
    if (ret < 0) {
        perror("aio_read");
        close(fd);
        return  (-1);
    }

    aio_suspend(list, 1, NULL);
    close(fd);
    return  (0);
}

在 SylixOS Shell 下运行程序:

# ./Function_AsynchronousIO
len: 64
content: This is a sylixos test file

调用 lio_listio 函数可以提交一系列由一个 AIO 控制块列表描述的 I/O 请求。

#include <aio.h>
int  lio_listio(int mode, struct aiocb * const list[], int nent,
                struct sigevent *sig);

函数 lio_listio 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 mode 是传输模式(LIO_WAIT、LIO_NOWAIT)。
  • 参数 list 是请求 AIO 控制块数组。
  • 参数 nent 是 AIO 控制块数组。
  • 参数 sig 是所有 I/O 操作完成后产生的信号方法。
  • mode 参数决定了 I/O 是否真的是异步的。如果该参数被设定为 LIO_WAIT,lio_listio 函数将在所有由列表指定的 I/O 操作完成后返回,这种情况下,参数 sig 将被忽略。如果 mode 参数被设定为 LIO_NOWAIT,lio_listio 函数将在 I/O 请求入队后立即返回。进程将在所有 I/O 操作完成后,按照参数 sig 指定的被异步通知。如果不想被通知,可以把参数 sig 设置为 NULL,需要注意的是,每个异步 I/O 操作可以设定自己的通知方式,而参数 sig 指定的通知方式,是额外的通知方式,并且只有所有操作都完成后才会通知。

在每一个 AIO 控制块中,aio_lio_opcode 字段指定了该操作是一个读操作(LIO_READ)、写操作(LIO_WRITE),还是将被忽略的空操作(LIO_NOP)。

下面程序展示了 lio_listio 函数的用法,从代码可以看出一次 lio_listio 函数调用发起了多个传输,从这一点可以得出 lio_listio 函数的性能方面得到了提高。

#include <aio.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define BUFSIZE  (64)

void signal_handler (union sigval val)
{
    fprintf(stdout, "async operator complete.\n");
}

int main (int argc, char *argv[])
{
    int                     fd;
    int                     ret;
    struct aiocb            aio[2];
    struct aiocb  *const    list[] = {&aio[0], &aio[1]};
    static struct sigevent  notify;

    fd = open("file", O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "open file failed.\n");
        return  (-1);
    }

    memset(&aio[0], 0, sizeof(struct aiocb));
    memset(&aio[1], 0, sizeof(struct aiocb));
    /*
     *  设置第一个AIO控制块
     */
    aio[0].aio_fildes     = fd;
    aio[0].aio_buf        = malloc(BUFSIZE + 1);
    aio[0].aio_nbytes     = BUFSIZE;
    aio[0].aio_offset     = 0;
    aio[0].aio_reqprio    = 1;
    aio[0].aio_lio_opcode = LIO_READ;
    /*
     *  设置第二个AIO控制块
     */
    aio[1].aio_fildes             = fd;
    aio[1].aio_buf                = malloc(BUFSIZE + 1);
    aio[1].aio_nbytes             = BUFSIZE;
    aio[1].aio_offset             = BUFSIZE;
    aio[1].aio_reqprio            = 2;
    aio[1].aio_lio_opcode         = LIO_READ;
    notify.sigev_signo            = SIGUSR1;
    notify.sigev_notify_function  = signal_handler;
    notify.sigev_notify           = SIGEV_THREAD | SIGEV_SIGNAL;

    ret = lio_listio(LIO_NOWAIT, list, 2, &notify);
    if (ret < 0) {
        perror("lio_listio");
        close(fd);
        return  (-1);
    }

    sleep(60);
    close(fd);
    return  (0);
}

在 SylixOS Shell 下运行程序:

# ./lio_listio_test
signal handler.

SylixOS 异步 I/O 的实现中,会通过一个额外的线程(代理线程)对 I/O 进行操作,为了能够设置或者获取代理线程的栈信息,SylixOS 增加了下面的一组函数,这组函数并非标准中定义。需要注意的是,设置的栈信息只对将来启动的线程有效(这通常发生在将来的 I/O 请求)。

#include <aio.h>
int     aio_setstacksize(size_t  newsize);
size_t  aio_getstacksize(void);

函数 aio_setstacksize 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 newsize 是新的栈大小。

函数 aio_getstacksize 原型分析:

  • 此函数总是返回当前线程栈的大小。

调用 aio_setstacksize 函数可以设置将来启动线程(代理线程)的栈大小,调用 aio_getstacksize 函数可以获得当前线程(代理线程)栈的大小。

文档内容是否对您有所帮助?
有帮助
没帮助