Неужели в Linux нет асинхронного блочного ввода-вывода?

Рассмотрим приложение, которое ограничено ЦП, но также имеет требования к высокопроизводительному вводу-выводу.

Я сравниваю ввод-вывод файлов Linux с Windows и не понимаю, как epoll вообще поможет программе Linux. Ядро сообщит мне, что файловый дескриптор «готов к чтению», но мне все равно нужно вызвать блокировку read (), чтобы получить мои данные, и если я хочу прочитать мегабайты, совершенно ясно, что это приведет к блокировке.

В Windows я могу создать дескриптор файла с установленным OVERLAPPED, а затем использовать неблокирующий ввод-вывод и получать уведомления, когда ввод-вывод завершается, и использовать данные из этой функции завершения. Мне не нужно тратить время настенных часов на уровне приложения на ожидание данных, а это означает, что я могу точно настроить количество потоков в соответствии с количеством ядер и получить 100% эффективное использование ЦП.

Если мне нужно имитировать асинхронный ввод-вывод в Linux, то мне нужно выделить для этого некоторое количество потоков, и эти потоки будут тратить немного времени на выполнение задач ЦП и много времени на блокировку ввода-вывода, плюс будут накладные расходы при обмене сообщениями между этими потоками. Таким образом, я буду либо чрезмерно подписываться, либо недоиспользовать ядра процессора.

Я смотрел на mmap () + madvise () (WILLNEED) как на "асинхронный ввод-вывод для бедняков", но он все еще не доходит до конца, потому что я не могу получить уведомление, когда это будет сделано - у меня "угадывать", и если я угадываю "неправильно", я буду блокировать доступ к памяти, ожидая прихода данных с диска.

В Linux, похоже, начинается асинхронный ввод-вывод в io_submit, и, похоже, также есть реализация POSIX aio в пользовательском пространстве, но так было некоторое время, и я не знаю никого, кто бы поручился за эти системы в критических ситуациях. , высокопроизводительные приложения.

Модель Windows работает примерно так:

  1. Выполните асинхронную операцию.
  2. Свяжите асинхронную операцию с конкретным портом завершения ввода-вывода.
  3. Дождитесь завершения операций на этом порту
  4. Когда ввод-вывод завершен, поток, ожидающий порта, разблокируется и возвращает ссылку на ожидающую операцию ввода-вывода.

Шаги 1/2 обычно выполняются как единое целое. Шаги 3/4 обычно выполняются с пулом рабочих потоков, а не (обязательно) с тем же потоком, который выполняет ввод-вывод. Эта модель в некоторой степени похожа на модель, предоставляемую boost :: asio, за исключением того, что boost :: asio фактически не предоставляет вам асинхронный блочный (дисковый) ввод-вывод.

Отличие epoll от Linux в том, что на шаге 4 ввода-вывода еще не произошло - он поднимает шаг 1 за шагом 4, что является «обратным», если вы уже точно знаете, что вам нужно.

Запрограммировав большое количество встроенных, настольных и серверных операционных систем, я могу сказать, что эта модель асинхронного ввода-вывода очень естественна для определенных типов программ. Это также очень высокая пропускная способность и низкие накладные расходы. Я думаю, что это один из оставшихся реальных недостатков модели ввода-вывода Linux на уровне API.


person Jon Watte    schedule 15.11.2012    source источник
comment
Я вообще не знаю модель MS Windows, поэтому я не могу сравнивать, но я хотел бы просто указать, что если вы используете любую форму _1 _ / _ 2 _ / _ 3 _ / _ 4_, то было бы ОЧЕНЬ необычно следить за блокирование _5 _ / _ 6_ при получении уведомления о готовности файлового дескриптора. Вы почти наверняка захотите сделать там неблокирующую read или write.   -  person Celada    schedule 17.11.2012
comment
select () был изобретен для сокетов вместе с системными вызовами recv () и send (), которые гарантируют отсутствие блокировки, если select () возвращает их как готовые - с недостатком, заключающимся в том, что количество операций ввода-вывода не гарантировано. Вы можете получить всего несколько байтов. Проблема в том, что с этой моделью нельзя реагировать. Неблокирующий ввод-вывод неэффективен, потому что он требует от ядра предварительной выборки некоторого количества данных, и ядро ​​не знает, сколько вам понадобится. Если вам нужна всего лишь страница, а ядро ​​извлекает мегабайт, вы проиграете. Если вам нужен мегабайт, а ядро ​​извлекает одну страницу, вы тоже проиграете.   -  person Jon Watte    schedule 19.11.2012
comment
возможный дубликат дискового файла Linux AIO   -  person J-16 SDiZ    schedule 20.11.2012
comment
@JonWatte Я считаю, что вы бы выполнили модифицированное чтение и запросили определенное количество байтов, а затем получили бы уведомление, когда буфер заполнен.   -  person LtWorf    schedule 16.08.2015
comment
@LtWorf: это модель асинхронного чтения (). Я ищу асинхронную (не неблокирующую) модель recv ().   -  person Jon Watte    schedule 17.08.2015
comment
С пулом потоков адаптивного размера у вас не будет много блокирующих потоков, только небольшое их количество будет выполнять реальную работу.   -  person peterh    schedule 05.09.2015


Ответы (4)


(2020) Если вы используете ядро ​​Linux 5.1 или выше, вы можете использовать io_uring interface для файлового ввода-вывода и получения отличной асинхронной работы.

По сравнению с существующим интерфейсом libaio / KAIO, io_uring имеет следующие преимущества:

  • Сохраняет асинхронное поведение при выполнении буферизованного ввода-вывода (а не только при выполнении прямого ввода-вывода)
  • Легче использовать (особенно при использовании вспомогательной библиотеки liburing)
  • При желании может работать в режиме опроса (но для включения этого режима вам потребуются более высокие привилегии)
  • Меньше накладных расходов на бухгалтерию на ввод / вывод
  • Снижение нагрузки на ЦП из-за меньшего количества переключателей режима пользовательского пространства / системного вызова ядра (в наши дни это очень важно из-за смягчение последствий Spectre / Meltdown)
  • Дескрипторы файлов и буферы могут быть предварительно зарегистрированы для экономии времени сопоставления / отмены сопоставления.
  • Быстрее (можно достичь более высокой совокупной пропускной способности, операции ввода-вывода имеют меньшую задержку)
  • Связанный режим может выражать зависимости между вводом-выводом (›= 5.3 ядро)
  • Может работать с вводом-выводом на основе сокетов (recvmsg() / _ 6_ поддерживаются начиная с› = 5.3, см. сообщения с упоминанием слова support в история git io_uring.c)
  • Поддерживает попытки отмены ввода / вывода в очереди (›= 5.5)
  • Может запрашивать, чтобы ввод-вывод всегда выполнялся из асинхронного контекста, а не по умолчанию только откат к разбиению ввода-вывода в асинхронный контекст, когда встроенный путь отправки запускает блокировку (›= ядро ​​5.6)
  • Растущая поддержка для выполнения асинхронных операций за пределами read / write (например, fsync (›= 5.1), fallocate (› = 5.6), splice (›= 5.7) и другие)
  • Более высокий импульс развития
  • Не блокируется каждый раз, когда звезды не совсем выровнены

По сравнению с POSIX AIO от glibc, io_uring имеет следующие преимущества:

Документ Эффективный ввод-вывод с io_uring гораздо более подробно описывает преимущества и использование io_uring. Документ Что нового в io_uring описывает новые функции, добавленные в io_uring с момента его создания, а В статье о быстром росте количества статей о io_uring LWN описывается, какие функции были доступны в каждом из ядер 5.1–5,5, с кратким обзором того, что должен был появиться в версии 5.6 (также см. список статей LWN по io_uring). Также есть более быстрый ввод-вывод с помощью видео-презентации io_uring (slides) из конца 2019 года, автор io_uring Йенс Аксбо. Наконец, руководство io_uring дает введение в io_uring использование.

С сообществом io_uring можно связаться через список рассылки io_uring и io_uring архивы списков рассылки показывают ежедневный трафик на начало 2021 года.

Повторная поддержка частичного ввода-вывода в смысле recv() vs read(): патч вошел в ядро ​​5.3, автоматически повторить io_uring короткое чтение, и в ядре 5.4 была произведена дальнейшая фиксация, которая изменяет поведение до только автоматически обрабатывает короткие чтения при работе с обычными файлами по запросам, для которых не установлен флаг REQ_F_NOWAIT (похоже, вы можете запросить REQ_F_NOWAIT через IOCB_NOWAIT или открыв файл с помощью O_NONBLOCK). Таким образом, вы можете получить recv() стиль короткого ввода-вывода и от io_uring.

Программное обеспечение / проекты с использованием io_uring

Хотя интерфейс молодой (его первое воплощение появилось в мае 2019 года), некоторые программы с открытым исходным кодом используют io_uring в дикой природе:

Программное обеспечение исследует с использованием io_uring

Поддержка дистрибутивом Linux для io_uring

  • (Конец 2020 г.) Последнее ядро ​​включения HWE в Ubuntu 18.04 - 5.4, поэтому можно использовать io_uring системные вызовы. Этот дистрибутив не содержит предварительной упаковки вспомогательной библиотеки liburing, но вы можете собрать ее для себя.
  • Исходное ядро ​​Ubuntu 20.04 - 5.4, поэтому можно использовать io_uring системные вызовы. Как и выше, дистрибутив не содержит предварительной упаковки liburing.
  • Исходное ядро ​​Fedora 32 - 5.6 , оно имеет упакованное liburing, поэтому io_uring можно использовать.
  • SLES 15 SP2 имеет ядро ​​5.3, поэтому можно использовать io_uring системные вызовы. Этот дистрибутив не содержит предварительной упаковки вспомогательной библиотеки liburing, но вы можете собрать ее для себя.
  • (Середина 2021 г.) Ядро RHEL 8 по умолчанию не поддерживает io_uring (в предыдущей версии этого ответа было ошибочно сказано, что он поддерживает). Согласно Добавить статью о поддержке io_uring в базе знаний Red Hat (содержимое находится за платным доступом подписчика) io_uring в ядро ​​RHEL 8 по умолчанию.

Надеюсь, io_uring откроет лучшую историю асинхронного файлового ввода-вывода для Linux.

(Чтобы добавить к этому ответу тонкую оболочку достоверности, в какой-то момент в прошлом Дженс Аксбо (Jens Axboe) (разработчик уровня блоков ядра Linux и изобретатель io_uring) подумал, что за этот ответ стоит проголосовать :-)

person Anon    schedule 11.08.2019
comment
Это выглядит довольно круто, теперь для использования ядра 5.1 просто нужны обычные используемые дистрибутивы, что, я думаю, будет, в зависимости от того, какой дистрибутив, вероятно, будет где-то между 2020 и 2022 годами (Ubuntu и Fedora сейчас на 5.0, Debian на 4.19, SuSE в 4.12). С нетерпением жду, это будет отличным дополнением. - person Damon; 12.08.2019
comment
И Fedora (fedoraproject.org/wiki/Kernel_Vanilla_Repositories), и Ubuntu (wiki.ubuntu.com/Kernel/MainlineBuilds) предлагает репозитории расширений, которые содержат ядра vanilla до версии 5.3 (но очевидно, что вы разрешаете любые дополнительные репозитории на свой страх и риск и т. д.). В SUSE тоже что-то есть (software.opensuse.org/package/kernel-vanilla ) а вот что внутри непонятно. Я предполагаю, что Ubuntu 20.10, скорее всего, будет иметь подходящее ядро, а Fedora частично переустанавливает ядра, поэтому Fedora 30, скорее всего, получит ядро ​​версии 5.1 (что означает, что в Fedora 31 наверняка что-то будет). - person Anon; 12.08.2019

Настоящий ответ, на который косвенно указал Питер Теох, основан на io_setup () и io_submit (). В частности, указанные Питером функции "aio_" являются частью эмуляции glibc на уровне пользователя, основанной на потоках, что не является эффективной реализацией. Настоящий ответ:

io_submit(2)
io_setup(2)
io_cancel(2)
io_destroy(2)
io_getevents(2)

Обратите внимание, что на странице руководства, датированной 2012-08 гг., Говорится, что эта реализация еще не достигла уровня, когда она может заменить эмуляцию пользовательского пространства glibc:

http://man7.org/linux/man-pages/man7/aio.7.html < / а>

эта реализация еще не достигла точки, когда реализация POSIX AIO может быть полностью переопределена с помощью системных вызовов ядра.

Итак, согласно последней документации ядра, которую я могу найти, Linux еще не имеет зрелой модели асинхронного ввода-вывода на основе ядра. И, если я предполагаю, что документированная модель действительно зрелая, она по-прежнему не поддерживает частичный ввод-вывод в смысле recv () vs read ().

person Jon Watte    schedule 20.02.2014
comment
В ядре linux философия заключается в том, что ничто не является стабильным и зрелым, поскольку сообщество всегда готово изменить свой API, когда это необходимо. Но все, что публикуется в пользовательском пространстве, например man7.org справочные страницы, ядро ​​должно следовать и никогда не нарушать пользовательское пространство. Это заявление от самого Линуса. Но если вы сейчас скажете, что ядро ​​(читайте здесь: lxr.free-Electrons .com / source / include / linux / syscalls.h, строка 474) имеет API syscall, тогда мы всегда можем использовать syscall () для прямого вызова ядра, независимо от того, имеет ли API io_submit () (и его семейство) реализован в glibc. - person Peter Teoh; 22.02.2014
comment
Проблема с вашим ответом заключается в том, что вы рекомендуете API функций aio_xxx () в качестве API асинхронного ввода-вывода. Однако этот API на самом деле не поддерживается системными вызовами Linux, а вместо этого реализован с использованием потоков пользовательского пространства в glibc. По крайней мере, согласно странице руководства. Таким образом, функции aio_xxx () не являются ответом на вопрос - они являются причиной вопроса. Правильный ответ - функции, начинающиеся с io_setup (). - person Jon Watte; 23.02.2014
comment
Под aio_xxx () я всегда имел в виду API ядра. Поэтому, конечно, для него нет стандартов системных вызовов Linux, равно как и нет никаких страниц руководства для него (имеется в виду официальный Linux API), который предназначен ТОЛЬКО для пользовательского пространства. В общем, все API ядра не попадают ни в какой стандартный список API, как всегда говорится (в списке рассылки, например, lkml.org/lkml/2006/6/14/164), что API ядра довольно гибок, но ни то, ни другое не так легко одобряются. - person Peter Teoh; 24.02.2014

Как объяснено в:

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 (), как прямой системный вызов (в обход зависимостей glibc), должно существовать отображение ядра через ту же сигнатуру «__NR_io_setup». После поиска источника ядра по адресу:

http://lxr.free-electrons.com/source/include/linux/syscalls.h#L474 (URL применим для последней версии 3.13) вас приветствует прямая реализация этих io _ * () API в ядре:

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

или, может быть:

Каков статус асинхронного ввода-вывода POSIX (AIO)?

«io_submit» и его друзья по-прежнему недоступны в glibc (см. справочные страницы io_submit), что я проверил в своем 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

person Peter Teoh    schedule 17.02.2014
comment
Спасибо за ответ. Однако похоже, что aio_suspend () не может смешиваться с вводом-выводом события или выбора стиля. Например: нет ли AIO-версии recv ()? - person Jon Watte; 18.02.2014
comment
не совсем. Если вы гуглите асинхронный запрос (без кавычек), вы получите много ответов, например, этот: wangafu.net/~nickm/libevent-book/01_intro.html. В нем был приведен пример неблокирующей функции recv () с использованием fnctl (). но затем он подчеркивает, что теперь программа очень быстро превращается в цикл, используя все циклы ЦП. select () и libevent API - это альтернативы, предлагаемые для решения указанной выше проблемы. - person Peter Teoh; 20.02.2014
comment
Я хорошо знаком с неблокирующим вводом-выводом сокетов. Неблокирование - это не то же самое, что асинхронный. И читать - это не то же самое, что и recv. Я ищу способ протолкнуть все операции ввода-вывода через одну и ту же очередь запроса, а затем получить уведомление о том, какие запросы в очереди завершили механизм для всех операций ввода-вывода, например, порты завершения ввода-вывода в Windows. Как я уже сказал в вопросе ;-) - person Jon Watte; 20.02.2014
comment
В приведенном выше списке отсутствовала функция io_getevents (). С этим добавлением набор примитивов начинает больше походить на то, что я ожидал. Теперь, если бы существовал четкий пример кода из какого-нибудь надежного приложения пользовательского уровня, использующего это, это сделало бы мой день! - person Jon Watte; 20.02.2014
comment
На самом деле, если вы читаете больше, это неверный ответ: текущая реализация Linux POSIX AIO предоставляется в пространстве пользователя с помощью glibc. Правильный ответ основан на io_setup () и io_submit (). - person Jon Watte; 20.02.2014

Для ввода-вывода сетевого сокета, когда он «готов», он не блокируется. Вот что означают O_NONBLOCK и "готово".

Для дискового ввода-вывода у нас есть posix aio, linux aio, sendfile и друзья.

person J-16 SDiZ    schedule 19.11.2012
comment
posix aio реализован в пользовательском пространстве в glibc с использованием потоков, так что нет, это не настоящий AIO. Linux AIO (io_submit) - это то, о чем я хочу услышать больше, но я видел, что никто не использовал его для чего-либо, что для меня означает, что там есть драконы. Это часть того, что пытается выяснить этот вопрос. sendfile () не имеет ничего общего с асинхронным дисковым вводом-выводом. Я был бы рад принять ваш ответ, если он действительно способствует решению, но обратите внимание, что я уже упоминал io_submit в своем вопросе. - person Jon Watte; 20.11.2012
comment
Linux AIO не используется ... Например, InnoDB (MySQL) использует io_submit. - person J-16 SDiZ; 20.11.2012
comment
Также: когда дело доходит до сетевых сокетов, вам следует обратить внимание на разницу между read () и recv (). io_submit (), похоже, поддерживает семантику read (), а не семантику recv (). O_NONBLOCK никогда не требуется для сокетов, если вы используете recv () и правильный select () или уведомление о событии. - person Jon Watte; 18.05.2014