AIO(7) Руководство программиста Linux 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 {

    /* Порядок данных полей определяется реализацией */

    int             aio_fildes;     /* файловый дескриптор */

    off_t           aio_offset;     /* файловое смещение */

    volatile void  *aio_buf;        /* расположение буфера */

    size_t          aio_nbytes;     /* длина передачи */

    int             aio_reqprio;    /* приоритет запроса */

    struct sigevent aio_sigevent;   /* метод уведомления */

    int             aio_lio_opcode; /* выполняемая операция;

                                       только в lio_listio() */

    /* Не показаны различные поля, используемые в реализациях */
};
/* Коды операций для 'aio_lio_opcode': */
enum { LIO_READ, LIO_WRITE, LIO_NOP };

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

Файловый дескриптор, над которым будут выполняться операции ввода-вывода.
Файловое смещение, начиная с которого будут выполняться операции ввода-вывода.
Буфер, используемый для пересылки данных при операции чтения или записи.
Размер буфера, на который указывает aio_buf.
В этом поле задаётся значение, которое вычитается из приоритета реального времени вызывающей нити, чтобы определить приоритет выполнения данного запроса ввода-вывода (смотрите pthread_setschedparam(3)). Указываемое значение должно быть в диапазоне от 0 и до значения, возвращаемого sysconf(_SC_AIO_PRIO_DELTA_MAX). Данное поле игнорируется при операциях синхронизации файла.
В этом поле задаётся структура, которая указывает как вызывающему должно быть сообщено о завершении анонимной операции ввода-вывода. Возможные значения для aio_sigevent.sigev_notify: SIGEV_NONE, SIGEV_SIGNAL и SIGEV_THREAD. Подробности смотрите в sigevent(7).
Задаёт тип операции, которая будет выполнена; используется только в lio_listio(3).

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

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

ОШИБКИ

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

ВЕРСИИ

Интерфейсы POSIX AIO появились в glibc в версии 2.1.

СООТВЕТСТВИЕ СТАНДАРТАМ

POSIX.1-2001, POSIX.1-2008.

ЗАМЕЧАНИЯ

Желательно обнулять буфер блока управления перед использованием (смотрите 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).

Сигнал SIGQUIT (генерируемый нажатием control-\) заставляет программу отменить все невыполненные запросы с помощью 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     /* размер буферов для операций чтения */
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
#define errMsg(msg)  do { perror(msg); } while (0)
struct ioRequest {      /* определяемая приложением структура для

                           слежения за запросами ввода-вывода */

    int           reqNum;

    int           status;

    struct aiocb *aiocbp;
};
static volatile sig_atomic_t gotSIGQUIT = 0;

                        /* при получении SIGQUIT мы пытаемся отменить

                           все невыполненные запросы ввода-вывода */
static void             /* обработчик SIGQUIT */
quitHandler(int sig)
{

    gotSIGQUIT = 1;
}
#define IO_SIGNAL SIGUSR1   /* сигнал, уведомляющий о завершении

                               ввода-вывода */
static void                 /* обработчик завершения ввода-вывода */
aioSigHandler(int sig, siginfo_t *si, void *ucontext)
{

    if (si->si_code == SI_ASYNCIO) {

        write(STDOUT_FILENO, "Получен сигнал завершения ввода-вывода\n", 31);

        /* соответствующая структура ioRequest была бы доступна как

               struct ioRequest *ioReq = si->si_value.sival_ptr;

           а файловый дескриптор был бы доступен через

               ioReq->aiocbp->aio_fildes */

    }
}
int
main(int argc, char *argv[])
{

    struct ioRequest *ioList;

    struct aiocb *aiocbList;

    struct sigaction sa;

    int s, j;

    int numReqs;        /* общее количество устанавливаемых в очередь

                           запросов ввода-вывода */

    int openReqs;       /* количество выполняющихся запросов

                           ввода-вывода */

    if (argc < 2) {

        fprintf(stderr, "Использование: %s <имя_файла> <имя_файла>...\n",

                argv[0]);

        exit(EXIT_FAILURE);

    }

    numReqs = argc - 1;

    /* выделяем место под массивы */

    ioList = calloc(numReqs, sizeof(struct ioRequest));

    if (ioList == NULL)

        errExit("calloc");

    aiocbList = calloc(numReqs, sizeof(struct aiocb));

    if (aiocbList == NULL)

        errExit("calloc");

    /* указываем обработчики SIGQUIT и сигнала завершения ввода-вывода */

    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");

    /* открываем каждый файл, заданный в командной строке и ставим в

       очередь запрос на чтение полученного файлового дескриптора */

    for (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;

    /* цикл, отслеживание состояние запросов ввода-вывода */

    while (openReqs > 0) {

        sleep(3);       /* задержка между проверками */

        if (gotSIGQUIT) {

            /* при получении SIGQUIT пытаемся отменить каждый

               невыполненный запрос ввода-вывода и показываем состояние,

               возвращаемое при отмене запроса */

            printf("получен SIGQUIT; отмена запросов ввода-вывода: \n");

            for (j = 0; j < numReqs; j++) {

                if (ioList[j].status == EINPROGRESS) {

                    printf("    запрос %d в дескриптор %d:", j,

                            ioList[j].aiocbp->aio_fildes);

                    s = aio_cancel(ioList[j].aiocbp->aio_fildes,

                            ioList[j].aiocbp);

                    if (s == AIO_CANCELED)

                        printf("ввод-вывод отменён\n");

                    else if (s == AIO_NOTCANCELED)

                        printf("ввод-вывод не отменён\n");

                    else if (s == AIO_ALLDONE)

                        printf("I/O all done\n");

                    else

                        errMsg("aio_cancel");

                }

            }

            gotSIGQUIT = 0;

        }

        /* проверяем состояние каждого запроса ввода-вывода, которые

           ещё не завершились */

        printf("aio_error():\n");

        for (j = 0; j < numReqs; j++) {

            if (ioList[j].status == EINPROGRESS) {

                printf("    запрос %d (дескриптор %d): ",

                        j, ioList[j].aiocbp->aio_fildes);

                ioList[j].status = aio_error(ioList[j].aiocbp);

                switch (ioList[j].status) {

                case 0:

                    printf("ввод-вывод завершён\n");

                    break;

                case EINPROGRESS:

                    printf("выполняется\n");

                    break;

                case ECANCELED:

                    printf("отменён\n");

                    break;

                default:

                    errMsg("aio_error");

                    break;

                }

                if (ioList[j].status != EINPROGRESS)

                    openReqs--;

            }

        }

    }

    printf("Завершены все запросы ввода-вывода\n");

    /* проверяем возвращаемое состояние всех запросов ввода-вывода */

    printf("aio_return():\n");

    for (j = 0; j < numReqs; j++) {

        ssize_t s;

        s = aio_return(ioList[j].aiocbp);

        printf("    запрос %d (дескриптор %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

2019-03-06 Linux