Действительно ли нет асинхронного ввода-вывода блоков в Linux?
Рассмотрим приложение, которое связано с ЦП, но также имеет высокопроизводительные требования ввода-вывода.
Я сравниваю Linux файл ввода/вывода с Windows, и я не вижу, как epoll поможет программе Linux вообще. Ядро скажет мне, что дескриптор файла "готов к чтению", но мне все равно нужно вызвать функцию блокировки read(), чтобы получить мои данные, и если я хочу читать мегабайты, довольно ясно, что это будет заблокировано.
В Windows я могу создать дескриптор файла с набором OVERLAPPED, а затем использовать неблокирующий ввод-вывод и получить уведомление о завершении ввода-вывода и использовать данные из этой функции завершения. Мне нужно потратить время на настенные часы на уровне приложения, ожидая данных, а это значит, что я могу точно настроить количество потоков на мое количество ядер и получить 100% эффективное использование процессора.
Если мне нужно эмулировать асинхронный ввод-вывод в Linux, тогда мне нужно выделить некоторое количество потоков для этого, и эти потоки потратят немного времени на выполнение CPU, и много времени блокирует I/O, плюс в обмене сообщениями на/из этих потоков будут накладные расходы. Таким образом, я либо переподписываю, либо недоиспользую свои ядра процессора.
Я посмотрел на mmap() + madvise() (WILLNEED) как "асинхронный ввод-вывод" плохой человек ", но он все еще не проходит весь путь, потому что я не могу получить уведомление, когда это будет сделано - - Я должен" угадать ", и если я думаю" неправильно", я закончу блокирование доступа к памяти, ожидая, что данные будут поступать с диска.
Linux, похоже, запускает асинхронные операции ввода-вывода в io_submit, и, похоже, также имеет реализацию POSIX aio для пользовательского пространства, но на некоторое время это было так, и я не знаю никого, кто ручался бы за эти системы для критически важных высокопроизводительных приложений.
Модель Windows работает примерно так:
- Выполните асинхронную операцию.
- Свяжите асинхронную операцию с конкретным портом завершения ввода-вывода.
- Дождитесь завершения операций над этим портом
- Когда ввод-вывод завершен, поток, ожидающий от порта, блокируется и возвращает ссылку на ожидающую операцию ввода-вывода.
Шаги 1/2 обычно выполняются как одна вещь. Шаги 3/4 обычно выполняются с пулом рабочих потоков, а не (обязательно) тот же поток, что и проблемы ввода-вывода. Эта модель несколько похожа на модель, предоставляемую boost:: asio, за исключением boost:: asio фактически не дает вам асинхронного ввода-вывода на основе блоков (диска).
Разница с epoll в Linux заключается в том, что на шаге 4 никаких операций ввода-вывода еще не произошло - он поднимает шаг 1 после шага 4, который является "обратным", если вы точно знаете, что вам нужно.
Запросив большое количество встроенных, настольных и серверных операционных систем, я могу сказать, что эта модель асинхронного ввода-вывода очень полезна для определенных видов программ. Это также очень высокая пропускная способность и низкий расход. Я думаю, что это один из оставшихся реальных недостатков модели ввода-вывода Linux на уровне API.
Ответы
Ответ 1
Реальный ответ, на который косвенно указал Питер Тео, основан на io_setup() и io_submit().
В частности, функции "aio_", обозначенные Peter, являются частью эмуляции уровня пользователя glibc на основе потоков, что не является эффективной реализацией.
Реальный ответ:
io_submit(2)
io_setup(2)
io_cancel(2)
io_destroy(2)
io_getevents(2)
Обратите внимание, что в man-странице, датированной 2012-08, говорится, что эта реализация еще не созрела до такой степени, что она может заменить эмуляцию пользовательского пространства glibc:
http://man7.org/linux/man-pages/man7/aio.7.html
эта реализация еще не созрела до такой степени, что POSIX Реализация AIO может быть полностью реализована с использованием ядра системных вызовов.
Итак, согласно последней документации ядра, которую я могу найти, Linux еще не имеет зрелой, асинхронной модели ввода-вывода на основе ядра. И, если я предполагаю, что документированная модель на самом деле зрелая, она по-прежнему не поддерживает частичный ввод-вывод в смысле recv() vs read().
Ответ 2
Как объясняется в:
http://code.google.com/p/kernel/wiki/AIOUserGuide
и здесь:
http://www.ibm.com/developerworks/library/l-async/
Linux предоставляет асинхронные операции ввода-вывода на уровне ядра, API следующим образом:
aio_read Request an asynchronous read operation
aio_error Check the status of an asynchronous request
aio_return Get the return status of a completed asynchronous request
aio_write Request an asynchronous operation
aio_suspend Suspend the calling process until one or more asynchronous requests have completed (or failed)
aio_cancel Cancel an asynchronous I/O request
lio_listio Initiate a list of I/O operations
И если вы спросите, кто является пользователем этого API, это ядро - здесь показано только небольшое подмножество:
./drivers/net/tun.c (for network tunnelling):
static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
./drivers/usb/gadget/inode.c:
ep_aio_read(struct kiocb *iocb, const struct iovec *iov,
./net/socket.c (general socket programming):
static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov,
./mm/filemap.c (mmap of files):
generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
./mm/shmem.c:
static ssize_t shmem_file_aio_read(struct kiocb *iocb,
и др.
На уровне пользовательского пространства существует также API io_submit() и др. (от glibc), но в следующей статье предлагается альтернатива использованию glibc:
http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt
Он непосредственно реализует API для таких функций, как io_setup(), как прямой syscall (минуя зависимости glibc), должно существовать сопоставление ядра через ту же самую подпись "__NR_io_setup". При поиске исходного кода ядра:
http://lxr.free-electrons.com/source/include/linux/syscalls.h#L474 (URL-адрес применим для последней версии 3.13), вас приветствует прямая реализация этих API io _ *() в ядре:
474 asmlinkage long sys_io_setup(unsigned nr_reqs, aio_context_t __user *ctx);
475 asmlinkage long sys_io_destroy(aio_context_t ctx);
476 asmlinkage long sys_io_getevents(aio_context_t ctx_id,
481 asmlinkage long sys_io_submit(aio_context_t, long,
483 asmlinkage long sys_io_cancel(aio_context_t ctx_id, struct iocb __user *iocb,
Более поздняя версия glibc должна сделать это использование "syscall()" для вызова sys_io_setup() ненужным, но без последней версии glibc вы всегда можете сделать этот вызов самостоятельно, если вы используете более поздний ядро с этими возможностями из "sys_io_setup()".
Конечно, есть и другие параметры пользовательского пространства для асинхронного ввода-вывода (например, используя сигналы?):
http://personal.denison.edu/~bressoud/cs375-s13/supplements/linux_altIO.pdf
или perhap:
Каков статус асинхронного ввода-вывода POSIX (AIO)?
"io_submit" и друзья по-прежнему недоступны в glibc (см. io_submit manpages), которые я проверил в своем Ubuntu 14.04, но этот API специфичен для Linux.
Другие, такие как libuv, libev и libevent, также являются асинхронными API:
http://nikhilm.github.io/uvbook/filesystem.html#reading-writing-files
http://software.schmorp.de/pkg/libev.html
http://libevent.org/
Все эти API должны быть переносимыми через BSD, Linux, MacOSX и даже Windows.
С точки зрения производительности я не видел никаких цифр, но подозрение на libuv может быть самым быстрым из-за его легковесности?
https://ghc.haskell.org/trac/ghc/ticket/8400
Ответ 3
(2019) Если вы используете ядро 5.1 или выше, вы можете использовать интерфейс io_uring
для файлового ввода-вывода и получить отличную асинхронную работу.
По сравнению с существующим интерфейсом libaio
/KAIO io_uring
имеет следующие преимущества:
- Работает с буферизованным и прямым вводом/выводом
- Проще в использовании
- При желании может работать в режиме опроса
- Меньше накладных расходов на бухгалтерию на ввод/вывод
- Снижение нагрузки на процессор из-за меньшего количества переключений контекста системного вызова в пользовательском пространстве/ядре (в наши дни это очень важно из-за влияния смягчения последствий/расплавления)
- "Связанный режим", который можно использовать для выражения зависимостей между группами ввода-вывода (> = 5,3 ядра)
- Не блокируется каждый раз, когда звезды не идеально выровнены
По сравнению с glibc POSIX aio, io_uring
имеет следующие преимущества:
В документе "Эффективный ввод-вывод с io_uring" подробно рассматриваются преимущества и использование io_uring
. Есть также видео-презентация "Faster IO through io_uring" автора io_uring
Дженса Аксбо.
"Поддержка частичного ввода-вывода в смысле recv()
vs read()
": в ядро 5.3 вошел патч , который автоматически повторяет короткие чтения io_uring
. Еще не исправленный патч (который, как я предполагаю, появится в ядре 5.4) еще больше подправляет поведение, и автоматически выполняет только короткие чтения при работе с "обычными" файлами, когда запрос не REQ_F_NOWAIT
(похоже, вы можете запросить REQ_F_NOWAIT
через IOCB_NOWAIT
или открыв файл с помощью O_NONBLOCK
). Таким образом, похоже, что вы можете получить recv()
style- "короткое" поведение ввода/вывода также из io_uring
.
Очевидно, что на момент написания интерфейса io_uring
был очень новым, но, надеюсь, он откроет лучшую историю асинхронного ввода-вывода на основе файлов для Linux.
Ответ 4
Для ввода/вывода сетевого сокета, когда он "готов", он не блокируется. То, что означает O_NONBLOCK
и "ready".
Для диска i/o мы имеем posix aio, linux aio, sendfile и друзей.