ОПИСАНИЕ
Интерфейс асинхронного ввода-вывода 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_filedes
- Файловый дескриптор, над которым будут выполняться операции ввода-вывода.
- aio_offset
- Файловое смещение, начиная с которого будут выполняться операции ввода-вывода.
- aio_buf
- Буфер, используемый для пересылки данных при операции чтения или записи.
- aio_nbytes
- Размер буфера, на который указывает aio_buf.
- aio_reqprio
- В этом поле задаётся значение, которое вычитается из приоритета реального времени вызывающей нити, чтобы определить приоритет выполнения данного запроса ввода-вывода (смотрите pthread_setschedparam(3)). Указываемое значение должно быть в диапазоне от 0 и до значения, возвращаемого sysconf(_SC_AIO_PRIO_DELTA_MAX). Данное поле игнорируется при операциях синхронизации файла.
- aio_sigevent
- В этом поле задаётся структура, которая указывает как вызывающему должно быть сообщено о завершении анонимной операции ввода-вывода. Возможные значения для aio_sigevent.sigev_notify: SIGEV_NONE, SIGEV_SIGNAL и SIGEV_THREAD. Подробности смотрите в sigevent(7).
- 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 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 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 /* размер буферов для операций чтения */ #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) { write(STDOUT_FILENO, "I/O completion signal received\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("got SIGQUIT; canceling I/O requests: \n"); for (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 errMsg("aio_cancel"); } } gotSIGQUIT = 0; } /* проверяем состояние каждого запроса ввода-вывода, которые ещё не завершились */ printf("aio_error():\n"); for (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: errMsg("aio_error"); break; } if (ioList[j].status != EINPROGRESS) openReqs--; } } } printf("All I/O requests completed\n"); /* проверяем возвращаемое состояние всех запросов ввода-вывода */ printf("aio_return():\n"); for (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); }