Наистина ли няма асинхронен блок I/O на Linux?

Помислете за приложение, което е обвързано с процесора, но също така има високопроизводителни I/O изисквания.

Сравнявам файлов I/O на Linux с Windows и не виждам как epoll изобщо ще помогне на Linux програма. Ядрото ще ми каже, че файловият дескриптор е „готов за четене“, но все пак трябва да извикам блокирането read(), за да получа данните си, и ако искам да прочета мегабайти, е доста ясно, че това ще блокира.

В Windows мога да създам манипулатор на файл с набор OVERLAPPED и след това да използвам неблокиращ I/O и да получавам известия, когато I/O завърши, и да използвам данните от тази функция за завършване. Не трябва да прекарвам време на стенен часовник на ниво приложение в чакане на данни, което означава, че мога прецизно да настроя броя на нишките си спрямо броя на ядрата си и да получа 100% ефективно използване на процесора.

Ако трябва да емулирам асинхронен I/O на Linux, тогава трябва да разпределя известен брой нишки, за да направя това, и тези нишки ще прекарат малко време в извършване на CPU неща и много време в блокиране за I/O, освен това ще има допълнителни разходи в съобщенията към/от тези теми. По този начин или ще се абонирам прекалено много, или ще използвам недостатъчно процесорните си ядра.

Разгледах mmap() + madvise() (WILLNEED) като „async I/O на бедния човек“, но все още не стига дотам, защото не мога да получа известие, когато е готово -- имам да "позная" и ако позная "погрешно", в крайна сметка ще блокирам достъпа до паметта, чакайки данните да дойдат от диска.

Linux изглежда има началото на async I/O в io_submit и изглежда също така има имплементация на POSIX aio в потребителско пространство, но това е така от известно време и не познавам никой, който би гарантирал за тези системи за критични , приложения с висока производителност.

Моделът на Windows работи приблизително така:

  1. Издайте асинхронна операция.
  2. Свържете асинхронната операция с конкретен I/O порт за завършване.
  3. Изчакайте операциите да завършат на този порт
  4. Когато I/O приключи, нишката, чакаща на порта, се деблокира и връща препратка към чакащата I/O операция.

Стъпки 1/2 обикновено се извършват като едно нещо. Стъпки 3/4 обикновено се извършват с набор от работни нишки, а не (непременно) същата нишка, която издава I/O. Този модел е донякъде подобен на модела, осигурен от boost::asio, с изключение на това, че boost::asio всъщност не ви дава асинхронен базиран на блок (диск) I/O.

Разликата с epoll в Linux е, че в стъпка 4 все още не се е случил вход/изход - тя издига стъпка 1, за да дойде след стъпка 4, което е "назад", ако знаете точно какво имате нужда вече.

След като програмирах голям брой вградени, настолни и сървърни операционни системи, мога да кажа, че този модел на асинхронен I/O е много естествен за определени видове програми. Освен това е с много висока пропускателна способност и ниски разходи. Мисля, че това е един от оставащите реални недостатъци на I/O модела на Linux на ниво API.


person Jon Watte    schedule 15.11.2012    source източник
comment
Изобщо не познавам модела на MS Windows, така че не мога да сравня, но просто бих посочил, че ако използвате някаква форма на select/poll/epoll/kqueue, тогава би било МНОГО необичайно да продължите с блокиране на read/write, когато получите известие, че файлов дескриптор е готов. Почти със сигурност искате да направите неблокиращо read или write там.   -  person Celada    schedule 17.11.2012
comment
select() е измислен за сокети, заедно със системните извиквания recv() и send(), които гарантират, че няма да блокират, ако select() ги върне като готови - с недостатъка, че количеството I/O не е гарантирано. Може да получите само няколко байта. Проблемът е, че не можете да реагирате с този модел. Неблокиращият I/O не е ефективен, защото изисква ядрото предварително да извлече известно количество данни, а ядрото няма представа колко ще ви трябва. Ако имате нужда само от страница и ядрото извлича мегабайт, губите. Ако имате нужда от мегабайт и ядрото извлича една страница, вие също губите.   -  person Jon Watte    schedule 19.11.2012
comment
възможен дубликат на Linux Disk File AIO   -  person J-16 SDiZ    schedule 20.11.2012
comment
@JonWatte Вярвам, че ще издадете модифицирано четене и ще поискате определено количество байтове и след това ще получите известие, когато буферът е запълнен.   -  person LtWorf    schedule 16.08.2015
comment
@LtWorf: Това е асинхронният модел read(). Търся асинхронния (неблокиращ) модел recv().   -  person Jon Watte    schedule 17.08.2015
comment
С пул от нишки с адаптивен размер няма да имате много блокиращи нишки, а само малък брой от тях, които вършат реална работа.   -  person peterh    schedule 05.09.2015


Отговори (4)


(2020) Ако използвате 5.1 или по-нова версия на ядрото на Linux, можете да използвате io_uring интерфейс за подобен на файл I/O и да получите отлична асинхронна работа.

В сравнение със съществуващия интерфейс libaio/KAIO, io_uring има следните предимства:

  • Запазва асинхронно поведение при извършване на буфериран I/O (а не само при извършване на директен I/O)
  • По-лесен за използване (особено при използване на liburing помощна библиотека)
  • По желание може да работи по анкетиран начин (но ще ви трябват по-високи привилегии, за да активирате този режим)
  • По-малко режийни разходи за счетоводно пространство за I/O
  • По-ниски разходи за процесора поради по-малко превключвания на режима на системно извикване на потребителско пространство/ядро (голяма работа в наши дни поради въздействие на смекчаване на призрак/стопяване)
  • Файловите дескриптори и буфери могат да бъдат предварително регистрирани, за да се спести време за картографиране/декартиране
  • По-бързо (може да постигне по-висока обща производителност, I/O имат по-ниска латентност)
  • Свързаният режим може да изразява зависимости между I/O (›=5.3 ядро)
  • Може да работи с I/O, базиран на сокет (recvmsg()/sendmsg() се поддържат от ›=5.3, вижте съобщенията, в които се споменава думата поддръжка в git история на io_uring.c)
  • Поддържа опит за анулиране на I/O в опашка (›=5.5)
  • Може да поиска I/O винаги да се извършва от асинхронен контекст, вместо по подразбиране да се връща само към punting I/O към асинхронен контекст, когато вграденият път на подаване задейства блокиране (›=5.6 ядро)
  • Нарастваща поддръжка за извършване на асинхронни операции след read/ write (напр. fsync (›=5,1), fallocate (›=5,6), splice (›=5,7) и повече)
  • По-висок импулс на развитие
  • Не блокира всеки път, когато звездите не са идеално подравнени

В сравнение с POSIX AIO на glibc, io_uring има следните предимства:

Документът Efficient IO with io_uring разглежда много повече подробности относно предимствата и употребата на io_uring. Документът Какво е новото с io_uring описва нови функции, добавени към io_uring от създаването му, докато Бързият растеж на io_uring LWN статия описва кои функции са налични във всяко от ядрата 5.1 - 5.5 с поглед напред към това, което щеше да бъде в 5.6 (вижте също списъка на LWN със статии за io_uring). Има и По-бързо IO чрез io_uring видео презентация (слайдове) от края на 2019 г. от io_uring автор Jens Axboe. И накрая, урокът Lord of the io_uring дава въведение в използването на io_uring.

io_uring общността може да бъде достигната чрез пощенския списък на io_uring и Архивите на пощенските списъци на io_uring показват дневен трафик в началото на 2021 г.

Повторна поддръжка на частичен I/O в смисъла на recv() срещу read(): корекция влезе в ядрото 5.3, което ще автоматично повторете io_uring кратки четения и допълнителен ангажимент влезе в ядрото 5.4, което променя поведението на автоматично се грижи само за кратки четения, когато работи с обикновени файлове по заявки, които не са задали флага REQ_F_NOWAIT (изглежда, че можете да поискате REQ_F_NOWAIT чрез IOCB_NOWAIT или като отворите файла с O_NONBLOCK). По този начин можете да получите recv() стил - кратко I/O поведение от 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 (предишна версия на този отговор погрешно каза, че поддържа). Според статията за базата знания на Red Hat за добавяне на поддръжка за io_uring (съдържанието е зад стена за плащане на абонати) обратно пренасяне на io_uring към ядрото RHEL 8 по подразбиране се изпълнява.

Надяваме се, че io_uring ще въведе по-добра асинхронна файлово-подобна I/O история за 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 ) предлагат хранилища за разширение, които съдържат ванилни ядра до 5.3 (но очевидно разрешавате всякакви допълнителни репо сделки на свой собствен риск и т.н.). SUSE също изглежда има нещо (software.opensuse.org/package/kernel-vanilla ), но не е ясно какво има вътре. Предполагам, че Ubuntu 20.10 вероятно ще има подходящо ядро ​​и Fedora пребазира ядрата полу-често, така че Fedora 30 е много вероятно да получи ядро ​​след 5.1 (което означава, че Fedora 31 със сигурност ще има нещо). - person Anon; 12.08.2019

Истинският отговор, който беше индиректно посочен от Peter Teoh, се основава на 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 все още няма зрял, базиран на ядрото асинхронен I/O модел. И ако приема, че документираният модел всъщност е зрял, той все още не поддържа частичен I/O в смисъла на recv() срещу read().

person Jon Watte    schedule 20.02.2014
comment
В ядрото на linux философията е, че нищо не е стабилно и зряло, тъй като общността винаги е готова да промени своя API, когато е необходимо. Но всичко, публикувано в потребителското пространство като man7.org man страници, ядрото трябва да следва и никога да не прекъсва потребителското пространство. Това изявление е от самия Линус. Но ако кажете сега, че ядрото (прочетете тук: lxr.free-electrons .com/source/include/linux/syscalls.h ред 474) има syscall API, тогава винаги можем да използваме syscall() за директно извикване на ядрото, независимо дали io_submit() (и неговото семейство) API има е внедрен в glibc. - person Peter Teoh; 22.02.2014
comment
Проблемът, който имам с вашия отговор, е, че препоръчвате aio_xxx() функциите API като асинхронен I/O API. Въпреки това, този API всъщност не е подкрепен от системните извиквания на Linux, а вместо това е реализиран с помощта на нишки на потребителското пространство в glibc. Поне според man страницата. Следователно функциите aio_xxx() не са отговор на въпроса - те са причината за въпроса. Правилният отговор са функциите, започващи с io_setup(). - person Jon Watte; 23.02.2014
comment
Под aio_xxx() през цялото време имам предвид API на ядрото. Така че, разбира се, няма стандарти за системно извикване на Linux за него, нито има страници за ръководство за него (което означава официалния API на Linux), което е строго САМО за потребителско пространство. По принцип всички 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 предоставя асинхронен блок I/O на ниво ядро, 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,

и т.н.

На ниво потребителско пространство също има io_submit() и т.н. API (от 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) ще бъдете посрещнати с директното внедряване на тези 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 ()".

Разбира се, има и друга опция за потребителско пространство за асинхронен I/O (напр. използване на сигнали?):

http://personal.denison.edu/~bressoud/cs375-s13/supplements/linux_altIO.pdf

или може би:

Какво е състоянието на POSIX асинхронен I/O (AIO)?

„io_submit“ и приятелите все още не са налични в glibc (вижте manpages на 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() не може да се смесва със събитие или избор на стил I/O. Например: Няма ли aio версия на recv()? - person Jon Watte; 18.02.2014
comment
не точно. Ако търсите в Google asynchronous recv (без кавичките), ще получите много отговори, например този: wangafu.net/~nickm/libevent-book/01_intro.html. В него беше даден пример за неблокиращ recv(), чрез използването на fnctl(). но след това подчертава, че сега програмата се върти много бързо в цикъл, използвайки всички цикли на процесора. select() и libevent API са предложените алтернативи за решаване на горния проблем. - person Peter Teoh; 20.02.2014
comment
Много съм запознат с неблокиращия вход/изход на сокет. Неблокирането не е същото като асинхронното. И read не е същото като recv. Това, което търся, е начин да прокарам всички I/O през една и съща опашка с заявка, след което по-късно да получа известие за това кои заявки на опашка са завършили механизъм за всички I/O, като портове за завършване на I/O в 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

За мрежовия сокет i/o, когато е "готов", той не блокира. Това означава O_NONBLOCK и „готово“.

За диск i/o имаме posix aio, linux aio, sendfile и приятели.

person J-16 SDiZ    schedule 19.11.2012
comment
posix aio е внедрен в потребителската област в glibc, използвайки нишки, така че не, това не е вярно AIO. Linux AIO (io_submit) е нещо, за което искам да чуя повече, но съм виждал никой да не го използва за нищо, което за мен означава, че там има дракони. Това е част от това, което този въпрос се опитва да разбере. sendfile() няма нищо общо с асинхронния I/O, базиран на диск. Ще се радвам да приема отговора ви, ако той действително допринася за решение - но забележете, че вече споменах 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