AIO(7)

НАИМЕНОВАНИЕ
aio - введение в асинхронный ввод-вывод POSIX

ОПИСАНИЕ
Интерфейс асинхронного ввода-вывода POSIX (AIO) позволяет приложениям запускать одну или несколько операций ввода-вывода, которые выполняются асинхронно (т. е., в фоновом режиме). Приложение может выбрать каким образом оно будет уведомлено о завершении операции ввода-вывода: с помощью сигнала, созданием новой нити или вообще не получать уведомления.

Интерфейс POSIX AIO состоит из следующих функций:

aio_read(3)
    Ставит запрос на чтение в очередь. Это асинхронный аналог read(2).

aio_write(3)
    Ставит запрос на запись в очередь. Это асинхронный аналог write(2).

aio_fsync(3)
    Ставит запрос синхронизации операций ввода-вывода над файловым дескриптором. Это асинхронный аналог fsync(2) и fdatasync(2).

aio_error(3)
    Возвращает информацию о состоянии поставленного в очередь запроса ввода-вывода.

aio_return(3)
    Возвращает информацию о выполненном запросе ввода-вывода.

aio_suspend(3)
    Приостанавливает вызывающего до тех пор, пока не выполнится один или более указанных запросов ввода-вывода.

aio_cancel(3)
    Пытается отменить ожидающие выполнения запросы ввода-вывода над заданным файловым дескриптором.

lio_listio(3)
    Ставит в очередь сразу несколько запросов ввода-вывода за один вызов функции.

В структуре 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 */
    };

    /* Operation codes for 'aio_lio_opcode': */

    enum { LIO_READ, LIO_WRITE, LIO_NOP };

Поля этой структуры имеют следующее назначение:

aio_fildes
    Файловый дескриптор, над которым будут выполняться операции ввода-вывода.

aio_offset
    Файловое смещение, начиная с которого будут выполняться операции ввода-вывода.

aio_buf
    Буфер, используемый для пересылки данных при операции чтения или записи.

aio_nbytes
    Размер буфера, на который указывает aio_buf.

aio_reqprio
    В этом поле задаётся значение, которое вычитается из приоритета реального времени вызывающей нити, чтобы определить приоритет выполнения данного запроса ввода-вывода (смотрите pthread_setschedparam(3)). Указываемое значение должно быть в диапазоне от 0 и до значения, возвращаемого sysconf(_SC_AIO_PRIO_DELTA_MAX). Данное поле игнорируется при операциях синхронизации файла.

aio_sigevent
    This field is a structure that specifies how the caller is to be notified when the asynchronous I/O operation completes. Possible values for aio_sigevent.sigev_notify are SIGEV_NONE, SIGEV_SIGNAL, and SIGEV_THREAD. See sigevent(3type) for further details.

aio_lio_opcode
    Задаёт тип операции, которая будет выполнена; используется только в lio_listio(3).

В дополнении к стандартным функциям, перечисленным ранее, в библиотеке GNU C есть следующее расширение программного интерфейса POSIX AIO:

aio_init(3)
    Позволяет изменить настройки поведения реализации glibc для POSIX AIO.

ОШИБКИ

EINVAL
    Значение поля aio_reqprio структуры aiocb меньше 0 или больше, чем значение ограничения, возвращаемое вызовом sysconf(_SC_AIO_PRIO_DELTA_MAX).

СТАНДАРТЫ
POSIX.1-2008.

ИСТОРИЯ
POSIX.1-2001. glibc 2.1.

ПРИМЕЧАНИЯ
Желательно обнулять буфер блока управления перед использованием (смотрите memset(3)). Буфер блока управления и буфер, который задаётся в aio_buf, не должны изменяться во время выполнения операции ввода-вывода. Данные буферы должны оставаться рабочими до завершения операции ввода-вывода.

Одновременное выполнение операций чтения или записи через совместно используемую структуру aiocb приводит к непредсказуемым результатам.

Имеющаяся реализация Linux POSIX AIO предоставляется glibc в пользовательском пространстве. Она имеет ряд ограничений, наиболее существенные из которых — затраты на сопровождение нескольких нитей при операциях ввода-вывода и плохое масштабирование. Некогда для реализации асинхронного ввода-вывода велась работа над ядерной реализацией на основе машины состояний (смотрите io_submit(2), io_setup(2), io_cancel(2), io_destroy(2), io_getevents(2)), но эта реализация ещё недостаточно стабильна в тех местах, где POSIX AIO можно было бы полностью реализовать на системных вызовах ядра.

ПРИМЕРЫ
Представленная далее программа открывает все файлы, указанные в параметрах командной строки и ставит в очередь запрос на полученные файловые дескрипторы с помощью aio_read(3). Затем программа входит в цикл, в котором периодически следит за всеми выполняемыми операциями ввода-вывода с помощью aio_error(3). Для каждого запроса ввода-вывода настроено получение уведомления посредством сигнала. После завершения всех запросов ввода-вывода, программа возвращает их состояние с помощью aio_return(3).

The SIGQUIT signal (generated by typing control-\) causes the program to request cancelation of each of the outstanding requests using aio_cancel(3).

Вот результат работы программы. В этом примере программа ставит в очередь два запроса для стандартного ввода, и они отрабатываются двумя введёнными строками «abc» и «x».

    $ ./a.out /dev/stdin /dev/stdin
    открыт /dev/stdin в дескрипторе 3
    открыт /dev/stdin в дескрипторе 4
    aio_error():
        запрос 0 (дескриптор 3): выполняется
        запрос 1 (дескриптор 4): выполняется
    abc
    Получен сигнал завершения ввода-вывода
    aio_error():
        запрос 0 (дескриптор 3): ввод-вывод завершён
        запрос 1 (дескриптор 4): выполняется
    aio_error():
        запрос 1 (дескриптор 4): выполняется
    x
    Получен сигнал завершения ввода-вывода
    aio_error():
        запрос 1 (дескриптор 4): ввод-вывод завершён
    Завершены все запросы ввода-вывода
    aio_return():
        запрос 0 (дескриптор 3): 4
        запрос 1 (дескриптор 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 (size_t 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 cancelation requests. */

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

                for (size_t j = 0; j < numReqs; j++) {
                    if (ioList[j].status == EINPROGRESS) {
                        printf("    Request %zu 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 (size_t j = 0; j < numReqs; j++) {
                if (ioList[j].status == EINPROGRESS) {
                    printf("    for request %zu (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 (size_t j = 0; j < numReqs; j++) {
            ssize_t s;

            s = aio_return(ioList[j].aiocbp);
            printf("    for request %zu (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

ПЕРЕВОД
Русский перевод этой страницы руководства разработал(и) Dmitry Bolkhovskikh <d20052005@yandex.ru>, Yuri Kozlov <yuray@komyakino.ru> и Kirill Rekhov <krekhov.dev@gmail.com>

Этот перевод является свободной программной документацией; он распространяется на условиях общедоступной лицензии GNU (GNU General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html версии 3 или более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.

Если вы обнаружите какие-либо ошибки в переводе этой страницы руководства, пожалуйста, сообщите об этом разработчику(ам) по его(их) адресу(ам) электронной почты или по адресу списка рассылки русских переводчиков (<debian-l10n-russian@lists.debian.org>).