AIO(7)

名前
aio - POSIX 非同期 I/O の概要

説明
POSIX 非同期 (AIO) インターフェースを使うと、アプリケーションは、非同期 に (つまり、バックグラウンドで) 実行されるI/O 操作を一つ以上発行できる ようになる。アプリケーションは I/O 操作の完了の通知方法を選択することが できる。選択できる通知方法は、シグナルの配送、スレッドの起動、通知を行 わないである。

POSIX AIO インターフェースは以下の関数で構成されている。

aio_read(3)
    読み出しリクエストをキューに入れる。 read(2) の非同期版である。

aio_write(3)
    書き込みリクエストをキューに入れる。 write(2) の非同期版である。

aio_fsync(3)
    ファイルディスクリプターに対して行われた I/O 操作の 同期 (sync) リクエストをキューに入れる。 fsync(2) や fdatasync(2) の非同期版である。

aio_error(3)
    キューに入れられた I/O リクエストのエラー状態を取得する。

aio_return(3)
    完了した I/O リクエストの終了ステータスを取得する。

aio_suspend(3)
    指定された I/O リクエストの集合 (要素は一つ以上) が完了するまで、 呼び出し側の実行を停止 (suspend) する。

aio_cancel(3)
    指定されたファイルディスクリプターに関する 完了していない I/O リクエストのキャンセルを試みる。

lio_listio(3)
    一回の関数呼び出しで複数の I/O リクエストをキューに入れる。

aiocb ("非同期 I/O 制御ブロック (asynchronous I/O control block)") 構造体は、I/O 操作を制御するパラメーターを定義する。この型の引数は上記 の全ての関数で使用されている。この構造体は以下の通りである。

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

    /* Operation codes for 'aio_lio_opcode': */

    enum { LIO_READ, LIO_WRITE, LIO_NOP };

この構造体のフィールドは以下の通りである。

aio_fildes
    I/O 操作の実行対象となるファイルディスクリプター。

aio_offset
    I/O 操作を行うファイルオフセットを示す。

aio_buf
    読み出し操作、書き込み操作でデータ転送に使用されるバッファー。

aio_nbytes
    aio_buf が指すバッファーのサイズ。

aio_reqprio
    このフィールドでは、呼び出したスレッドのリアルタイム優先度から 減算する値を指定する。この I/O リクエストの実行の優先度を 決定するために使用される (pthread_setschedparam(3) 参照)。 指定する値は 0 と sysconf(_SC_AIO_PRIO_DELTA_MAX) が返す値の間で なければならない。このフィールドは、ファイル同期操作では無視される。

aio_sigevent
    このフィールドは、非同期 I/O 操作が完了した際に呼び出し側に どのように通知を行うかを指定する構造体である。 aio_sigevent.sigev_notify に指定できる値は、 SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD である。 詳細は sigevent(7) を参照。

aio_lio_opcode
    実行される操作の種別。 lio_listio(3) でのみ使用される。

上記のリストにある標準の関数に加えて、GNU C ライブラリでは 以下に示す POSIX AIO API に対する拡張が提供されている。

aio_init(3)
    glibc の POSIX AIO 実装の動作を調整するパラメーターを設定する。

エラー

EINVAL
    aiocb 構造体の aio_reqprio フィールドが、0 より小さいか、 sysconf(_SC_AIO_PRIO_DELTA_MAX) が返す上限よりも大きかった。

バージョン
POSIX AIO インターフェイスは glibc バージョン 2.1 以降で提供されている。

準拠
POSIX.1-2001, POSIX.1-2008.

注意
使用前に制御ブロックバッファーを 0 で埋めるのはよい考えである (memset(3) 参照)。I/O 操作が実行中の間は、制御ブロックバッファーと aio_buf が指すバッファーを変更してはならない。I/O 操作が完了するまで、 これらのバッファーは有効な状態に保たなければならない。

同じ aiocb 構造体を使って、同時に複数の非同期の読み出し操作や 書き込み操作を行った場合に、どのような結果になるかは未定義である。

現在の Linux では、POSIX AIO 実装は glibc によりユーザー空間で提供 されている。このため、制限がいくつかあり、最も顕著なものは、I/O 操作を 実行する複数のスレッドの管理コストが高く、スケーラビリティに欠けること である。しばらくの間、カーネルのステートマシンによる非同期 I/O の実装 の作業が行われているが (io_submit(2), io_setup(2), io_cancel(2), io_destroy(2), io_getevents(2) 参照)、 この実装はまだ POSIX AIO 実装をカーネルシステムコールにより 再実装するほど成熟したものてはない。

例
下記のプログラムは、コマンドライン引数で指定された名前のファイルを それぞれオープンし、得られたファイルディスクリプターに対するリクエストを aio_read(3) を使ってキューに入れる。その後、このプログラムはループに 入り、定期的に aio_error(3) を使ってまだ実行中の各 I/O 操作を監視す る。各 I/O リクエストは、シグナルの配送による完了通知が行われるように設 定される。全ての I/O リクエストが完了した後、aio_return(3) を使って それぞれのステータスを取得する。

SIGQUIT シグナル (control-\ をタイプすると生成できる) を送ると、 このプログラムは aio_cancel(3) を使って 完了していない各リクエストにキャンセル要求を送る。

以下はこのプログラムを実行した際の出力例である。 この例では、標準入力に対して 2 つのリクエストを行い、 "abc" と "x" という 2 行の入力を行っている。

    $ ./a.out /dev/stdin /dev/stdin
    opened /dev/stdin on descriptor 3
    opened /dev/stdin on descriptor 4
    aio_error():
        for request 0 (descriptor 3): In progress
        for request 1 (descriptor 4): In progress
    abc
    I/O completion signal received
    aio_error():
        for request 0 (descriptor 3): I/O succeeded
        for request 1 (descriptor 4): In progress
    aio_error():
        for request 1 (descriptor 4): In progress
    x
    I/O completion signal received
    aio_error():
        for request 1 (descriptor 4): I/O succeeded
    All I/O requests completed
    aio_return():
        for request 0 (descriptor 3): 4
        for request 1 (descriptor 4): 2

  プログラムのソース
    #include <fcntl.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>
    #include <aio.h>
    #include <signal.h>

    #define BUF_SIZE 20     /* Size of buffers for read operations */

    #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

    struct ioRequest {      /* Application-defined structure for tracking
                               I/O requests */
        int           reqNum;
        int           status;
        struct aiocb *aiocbp;
    };

    static volatile sig_atomic_t gotSIGQUIT = 0;
                            /* On delivery of SIGQUIT, we attempt to
                               cancel all outstanding I/O requests */

    static void             /* Handler for SIGQUIT */
    quitHandler(int sig)
    {
        gotSIGQUIT = 1;
    }

    #define IO_SIGNAL SIGUSR1   /* Signal used to notify I/O completion */

    static void                 /* Handler for I/O completion signal */
    aioSigHandler(int sig, siginfo_t *si, void *ucontext)
    {
        if (si->si_code == SI_ASYNCIO) {
            write(STDOUT_FILENO, "I/O completion signal received\n", 31);

            /* The corresponding ioRequest structure would be available as
                   struct ioRequest *ioReq = si->si_value.sival_ptr;
               and the file descriptor would then be available via
                   ioReq->aiocbp->aio_fildes */
        }
    }

    int
    main(int argc, char *argv[])
    {
        struct sigaction sa;
        int s;
        int numReqs;        /* Total number of queued I/O requests */
        int openReqs;       /* Number of I/O requests still in progress */

        if (argc < 2) {
            fprintf(stderr, "Usage: %s <pathname> <pathname>...\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }

        numReqs = argc - 1;

        /* Allocate our arrays */

        struct ioRequest *ioList = calloc(numReqs, sizeof(*ioList));
        if (ioList == NULL)
            errExit("calloc");

        struct aiocb *aiocbList = calloc(numReqs, sizeof(*aiocbList));
        if (aiocbList == NULL)
            errExit("calloc");

        /* Establish handlers for SIGQUIT and the I/O completion signal */

        sa.sa_flags = SA_RESTART;
        sigemptyset(&sa.sa_mask);

        sa.sa_handler = quitHandler;
        if (sigaction(SIGQUIT, &sa, NULL) == -1)
            errExit("sigaction");

        sa.sa_flags = SA_RESTART | SA_SIGINFO;
        sa.sa_sigaction = aioSigHandler;
        if (sigaction(IO_SIGNAL, &sa, NULL) == -1)
            errExit("sigaction");

        /* Open each file specified on the command line, and queue
           a read request on the resulting file descriptor */

        for (int j = 0; j < numReqs; j++) {
            ioList[j].reqNum = j;
            ioList[j].status = EINPROGRESS;
            ioList[j].aiocbp = &aiocbList[j];

            ioList[j].aiocbp->aio_fildes = open(argv[j + 1], O_RDONLY);
            if (ioList[j].aiocbp->aio_fildes == -1)
                errExit("open");
            printf("opened %s on descriptor %d\n", argv[j + 1],
                    ioList[j].aiocbp->aio_fildes);

            ioList[j].aiocbp->aio_buf = malloc(BUF_SIZE);
            if (ioList[j].aiocbp->aio_buf == NULL)
                errExit("malloc");

            ioList[j].aiocbp->aio_nbytes = BUF_SIZE;
            ioList[j].aiocbp->aio_reqprio = 0;
            ioList[j].aiocbp->aio_offset = 0;
            ioList[j].aiocbp->aio_sigevent.sigev_notify = SIGEV_SIGNAL;
            ioList[j].aiocbp->aio_sigevent.sigev_signo = IO_SIGNAL;
            ioList[j].aiocbp->aio_sigevent.sigev_value.sival_ptr =
                                    &ioList[j];

            s = aio_read(ioList[j].aiocbp);
            if (s == -1)
                errExit("aio_read");
        }

        openReqs = numReqs;

        /* Loop, monitoring status of I/O requests */

        while (openReqs > 0) {
            sleep(3);       /* Delay between each monitoring step */

            if (gotSIGQUIT) {

                /* On receipt of SIGQUIT, attempt to cancel each of the
                   outstanding I/O requests, and display status returned
                   from the cancellation requests */

                printf("got SIGQUIT; canceling I/O requests: \n");

                for (int j = 0; j < numReqs; j++) {
                    if (ioList[j].status == EINPROGRESS) {
                        printf("    Request %d on descriptor %d:", j,
                                ioList[j].aiocbp->aio_fildes);
                        s = aio_cancel(ioList[j].aiocbp->aio_fildes,
                                ioList[j].aiocbp);
                        if (s == AIO_CANCELED)
                            printf("I/O canceled\n");
                        else if (s == AIO_NOTCANCELED)
                            printf("I/O not canceled\n");
                        else if (s == AIO_ALLDONE)
                            printf("I/O all done\n");
                        else
                            perror("aio_cancel");
                    }
                }

                gotSIGQUIT = 0;
            }

            /* Check the status of each I/O request that is still
               in progress */

            printf("aio_error():\n");
            for (int j = 0; j < numReqs; j++) {
                if (ioList[j].status == EINPROGRESS) {
                    printf("    for request %d (descriptor %d): ",
                            j, ioList[j].aiocbp->aio_fildes);
                    ioList[j].status = aio_error(ioList[j].aiocbp);

                    switch (ioList[j].status) {
                    case 0:
                        printf("I/O succeeded\n");
                        break;
                    case EINPROGRESS:
                        printf("In progress\n");
                        break;
                    case ECANCELED:
                        printf("Canceled\n");
                        break;
                    default:
                        perror("aio_error");
                        break;
                    }

                    if (ioList[j].status != EINPROGRESS)
                        openReqs--;
                }
            }
        }

        printf("All I/O requests completed\n");

        /* Check status return of all I/O requests */

        printf("aio_return():\n");
        for (int j = 0; j < numReqs; j++) {
            ssize_t s;

            s = aio_return(ioList[j].aiocbp);
            printf("    for request %d (descriptor %d): %zd\n",
                    j, ioList[j].aiocbp->aio_fildes, s);
        }

        exit(EXIT_SUCCESS);
    }

関連項目
io_cancel(2), io_destroy(2), io_getevents(2), io_setup(2), io_submit(2), aio_cancel(3), aio_error(3), aio_init(3), aio_read(3), aio_return(3), aio_write(3), lio_listio(3)

"Asynchronous I/O Support in Linux 2.5", Bhattacharya, Pratt, Pulavarty, and Morgan, Proceedings of the Linux Symposium, 2003, https://www.kernel.org/doc/ols/2003/ols2003-pages-351-366.pdf

この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.10 の一部である。プロジェクトの説明とバグ報告に関する情報は https://www.kernel.org/doc/man-pages/ に書かれている。