Основные идеи, понятия и объекты асинхронного ввода/вывода
Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных, продолжать работу параллельно с операциями передачи данных и получать асинхронные уведомления о завершении выполнения этих команд. Подобные возможности полезны для многих приложений реального времени, поскольку исключают задержки непредсказуемой длительности (или, по крайней мере, перекладывают эти задержки на другие компоненты). Типичный пример – сохранение данных для постпроцессирования (которое будет проводиться не в реальном времени).
Вообще говоря, операции асинхронного ввода/вывода могут перекрываться во времени не только с работой инициировавшего их процесса, но и между собой, то есть в принципе возможно параллельное выполнение множества команд обмена данными с множеством файлов.
Операция асинхронного чтения или записи данных считается завершенной, когда выполнен соответствующий синхронный ввод или вывод и модифицированы все ассоциированные поля состояния (например, время последнего доступа к файлу или последнего изменения файла).
За исключением параллельной работы с инициировавшим их приложением и интерфейсных отличий, операции асинхронного ввода/вывода ведут себя так же, как и обычные функции read(), write(), lseek() и fsync(). Выдать запрос на асинхронный ввод/вывод – все равно, что создать отдельный поток управления, который неделимым образом осуществит позиционирование в файле и обмен данными.
Параллельные асинхронные и синхронные операции обмена с одним и тем же файлом изменяют его так, как если бы они выполнялись последовательно.
После завершения асинхронной операции ввода/вывода приложению может быть доставлен сигнал, который можно интерпретировать как разрешение на доступ к читаемым данным и повторное использование задействованных в операции буферов и управляющих блоков.
"Жизненный цикл" операции асинхронного ввода/вывода включает два этапа:
- постановка запроса в очередь;
- выполнение запроса, осуществление ввода/вывода.
После завершения каждого из этапов приложение получает возвращаемое значение и статус ошибки, только по завершении второго этапа они извлекаются особым образом, но возвращаемое значение имеет естественный смысл – такое же значение (число переданных байт) вернула бы обычная синхронная операция ввода/вывода.
Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных, продолжать работу параллельно с операциями передачи данных и получать асинхронные уведомления о завершении выполнения этих команд. Подобные возможности полезны для многих приложений реального времени, поскольку исключают задержки непредсказуемой длительности (или, по крайней мере, перекладывают эти задержки на другие компоненты). Типичный пример – сохранение данных для постпроцессирования (которое будет проводиться не в реальном времени).
Вообще говоря, операции асинхронного ввода/вывода могут перекрываться во времени не только с работой инициировавшего их процесса, но и между собой, то есть в принципе возможно параллельное выполнение множества команд обмена данными с множеством файлов.
Операция асинхронного чтения или записи данных считается завершенной, когда выполнен соответствующий синхронный ввод или вывод и модифицированы все ассоциированные поля состояния (например, время последнего доступа к файлу или последнего изменения файла).
За исключением параллельной работы с инициировавшим их приложением и интерфейсных отличий, операции асинхронного ввода/вывода ведут себя так же, как и обычные функции read(), write(), lseek() и fsync(). Выдать запрос на асинхронный ввод/вывод – все равно, что создать отдельный поток управления, который неделимым образом осуществит позиционирование в файле и обмен данными.
Параллельные асинхронные и синхронные операции обмена с одним и тем же файлом изменяют его так, как если бы они выполнялись последовательно.
После завершения асинхронной операции ввода/вывода приложению может быть доставлен сигнал, который можно интерпретировать как разрешение на доступ к читаемым данным и повторное использование задействованных в операции буферов и управляющих блоков.
"Жизненный цикл" операции асинхронного ввода/вывода включает два этапа:
- постановка запроса в очередь;
- выполнение запроса, осуществление ввода/вывода.
После завершения каждого из этапов приложение получает возвращаемое значение и статус ошибки, только по завершении второго этапа они извлекаются особым образом, но возвращаемое значение имеет естественный смысл – такое же значение (число переданных байт) вернула бы обычная синхронная операция ввода/вывода.
Пока операция не завершена, ее статус ошибки имеет значение EINPROGRESS. Теоретически, приложение может дожидаться завершения асинхронной операции, периодически опрашивая статус ошибки, пока не получит значение, отличное от EINPROGRESS.
Из общих соображений следует, что если есть возможность поставить запрос в очередь, должны предоставляться средства для последующего удаления его из очереди (изменились обстоятельства, запрос стал не нужен). Стандарт POSIX-2001 удовлетворяет этому требованию.
Полезно иметь возможность за один вызов поставить в очередь целый список предварительно сформированных запросов, специфицирующих операции ввода/вывода, вообще говоря, с разными файлами. Стандарт POSIX-2001 позволяет и это.
Отметим, что потенциальное распараллеливание работы приложения и функций ввода/вывода дает реальный выигрыш в эффективности только при наличии аппаратной поддержки параллелизма. Для многих систем, функционирующих в режиме реального времени, такая поддержка имеется, так что приложение, пользуясь средствами асинхронного ввода/вывода, может мобильным образом полностью загрузить устройства ввода/вывода, не жертвуя при этом скоростью вычислений.
Наиболее важным объектом, обслуживающим асинхронный ввод/вывод, является управляющий блок, оформленный в стандарте POSIX-2001 как структура типа 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; // Запрошенная // операция
Значением элемента aio_fildes является дескриптор файла, над которым выполняется асинхронная операция.
Элемент aio_offset задает (абсолютную) позицию в файле, начиная с которой будет производиться ввод/вывод. Если для файлового дескриптора установлен флаг O_APPEND или заданное дескриптором устройство не поддерживает позиционирования, операции записи добавляют данные в конец.
Поля aio_buf и aio_nbytes имеют тот же смысл, что и соответствующие аргументы функций read() и write().
Если реализация поддерживает приоритетное планирование, очередь запросов на асинхронный ввод/вывод упорядочивается в соответствии с текущими приоритетами вызывающих процессов. Элемент aio_reqprio позволяет понизить (но не повысить) приоритет запроса. Его значение должно находиться в диапазоне от нуля до AIO_PRIO_DELTA_MAX включительно; оно вычитается из приоритета процесса.
Элемент aio_sigevent, имеющий структурный тип sigevent, определяет способ уведомления вызывающего процесса о завершении операции ввода/вывода. Если значение aio_sigevent.sigev_notify равно SIGEV_NONE, никаких сигналов по завершении генерироваться не будет, но возвращаемое значение и статус ошибки будут сформированы должным образом.
Элемент aio_lio_opcode используется только в списках запросов и специфицирует операцию чтения или записи.
Адрес управляющего блока играет роль идентификатора, позволяющего получить доступ к возвращаемому значению и статусу ошибки.
Управляющий блок и буфера данных, ассоциированные с операцией асинхронного ввода/вывода, используются системой тогда и только тогда, когда статус ошибки имеет значение EINPROGRESS. Разумеется, приложение не должно модифицировать в это время структуру aiocb.
Пока операция не завершена, ее статус ошибки имеет значение EINPROGRESS. Теоретически, приложение может дожидаться завершения асинхронной операции, периодически опрашивая статус ошибки, пока не получит значение, отличное от EINPROGRESS.
Из общих соображений следует, что если есть возможность поставить запрос в очередь, должны предоставляться средства для последующего удаления его из очереди (изменились обстоятельства, запрос стал не нужен). Стандарт POSIX-2001 удовлетворяет этому требованию.
Полезно иметь возможность за один вызов поставить в очередь целый список предварительно сформированных запросов, специфицирующих операции ввода/вывода, вообще говоря, с разными файлами. Стандарт POSIX-2001 позволяет и это.
Отметим, что потенциальное распараллеливание работы приложения и функций ввода/вывода дает реальный выигрыш в эффективности только при наличии аппаратной поддержки параллелизма. Для многих систем, функционирующих в режиме реального времени, такая поддержка имеется, так что приложение, пользуясь средствами асинхронного ввода/вывода, может мобильным образом полностью загрузить устройства ввода/вывода, не жертвуя при этом скоростью вычислений.
Наиболее важным объектом, обслуживающим асинхронный ввод/вывод, является управляющий блок, оформленный в стандарте POSIX-2001 как структура типа 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; // Запрошенная // операция
Значением элемента aio_fildes является дескриптор файла, над которым выполняется асинхронная операция.
Элемент aio_offset задает (абсолютную) позицию в файле, начиная с которой будет производиться ввод/вывод. Если для файлового дескриптора установлен флаг O_APPEND или заданное дескриптором устройство не поддерживает позиционирования, операции записи добавляют данные в конец.
Поля aio_buf и aio_nbytes имеют тот же смысл, что и соответствующие аргументы функций read() и write().
Если реализация поддерживает приоритетное планирование, очередь запросов на асинхронный ввод/вывод упорядочивается в соответствии с текущими приоритетами вызывающих процессов. Элемент aio_reqprio позволяет понизить (но не повысить) приоритет запроса. Его значение должно находиться в диапазоне от нуля до AIO_PRIO_DELTA_MAX включительно; оно вычитается из приоритета процесса.
Элемент aio_sigevent, имеющий структурный тип sigevent, определяет способ уведомления вызывающего процесса о завершении операции ввода/вывода. Если значение aio_sigevent.sigev_notify равно SIGEV_NONE, никаких сигналов по завершении генерироваться не будет, но возвращаемое значение и статус ошибки будут сформированы должным образом.
Элемент aio_lio_opcode используется только в списках запросов и специфицирует операцию чтения или записи.
Адрес управляющего блока играет роль идентификатора, позволяющего получить доступ к возвращаемому значению и статусу ошибки.
Управляющий блок и буфера данных, ассоциированные с операцией асинхронного ввода/вывода, используются системой тогда и только тогда, когда статус ошибки имеет значение EINPROGRESS. Разумеется, приложение не должно модифицировать в это время структуру aiocb.