last day (16 days later) » 

6:24 PM
1
Q: Как работает асинхронный неблокирующий ввод-вывод на уровне ядра ОС?

Павел КозловЧитаю как работают асинхронные вызовы Системные вызовы (system calls) – это интерфейс между операционной системной (ОС) и пользовательской программой. Они создают, удаляют и используют различные объекты. Пользовательская программа запрашивает сервис у ОС, осуществляя системный вызов. При систем...

 
А почему вы так перемежаете слова "поток" и "процесс"? Это что-то типичное для linux? (я с ним не знаком, но фразы типа "будут работать 2 моих процесса, один в режиме ядра получать данные, а другой в обычном режиме работать параллельно как обычные потоки чередуясь?", "будет как бы 6 потоков? [...] чередуясь как обычные процессы, " немного смущают).
 
раньше в линуксе потоки реализовывались через процессы. Но это было так давно. Но пугает другое. Автор явно пишет "асинхронный неблокирующий", а в тексте описывает классический блокирующий подход. Поэтому и не сходится ничего
 
даже когда они реализовывались - неужели можно было их так описывать, сравнивая поведение, как поведение совершенно эквивалентных сущностей? Даже тогда потоки, скорее, были чем-то вроде упрощённого клона процесса.
 
@Сергей да это я уже напутал сам. Вопрос про потоки.
 
тогда советую отредактировать вопрос, включая более важный комментарий @KoVadim.
 
6:24 PM
@KoVadim я же могу вызвать асинхронно чтение, потом выполнить еще какой то код и потом уже принудительно вызвать ожидание чтения? Я примерно такую ситуацию описываю. Это разве не асинхронный неблокирующий?
 
Был похожий вопрос: Асинхронная работа с диском с помощью Linux AIO. А еще потоки в linux до сих пор реализованы через процессы.
 
@user7860670 из вашего ответа "выполняя обычное чтение и запись в фоном потоке" правильно ли я понимаю что это примерно то что я и предположил, фактически будет запушен еще один поток в режиме ядра? т.е. фактически будет 6 потоков 3 из которых фоновые?
 
Та фраза относится к posix AIO, там тупо создается один обычный поток и в нем выполняется блокирующее чтение. Практически то же самое можно сделать используя ::std::async. При толковом асинхронном вводе-выводе никаких доп потоков не создается.
 
@user7860670 тогда все равно не совсем понял что будет в linux... ? Если я запускаю код на python\nodejs это как работает там?
 
Так вам надо определиться, что именно вы используете... python\nodejs это может быть реализовано очень по-разному
 
6:24 PM
есть блокирующее/неблокирующее чтение. В первом случае операция будет ждать чтения хотя бы одного байта и потом возвратит результат, в другом если нет данных - просто возвращается eintr. Все. Синхронный/асинхронный - в первом случае делаете запрос и сразу получаете ответ. асинхронный - делается запрос, а данные либо в колбеке, либо по сингналу приходят. И теперь должно быть понятно, как оно работает - ядро просто дергает колбек/сингнал, когда запрошенные данные готовы.
 
@user7860670 мне хотелось бы на любом примере (в вакууме) просто понять как это может быть реализовано, например nodejs
 
там внутри event loop на базе select/poll/epoll или высокоуровневых libuv/libev. Почитайте, как сделано это и все будет понятно.
 
На любом примере это не пояснить. Тем более, что nodejs работает поверх цикла сообщений, реализованного через libev.
 
@KoVadim добавил пример кода. Я только хочу понять как у меня будет одновременно и считываться файл и выполняться следующая функция moreWork. Это будет как будто бы два потока?
 
нода может внутри запустить несколько потоков и разложить Ваши задачи. А может просто выполнить одна за другой. Кто ей указ?
 
6:24 PM
@KoVadim вы имеете ввиду что это уже реализация на уровне языка программирования может быть разная? Это понятно. Вот я вызвал системный вызов ОС не важно из какого языка, что в этот момент произошло, что подразумевается под фразой "При системном вызове задача переходит в привилегированный режим или режим ядра" этот же поток перешел в другой режим и продолжил в нем считывать данные с диска? или появился новый "поток" с режимом ядра?
 
Если упрощать, то для асинхронного считывания не нужен никакой "дополнительный поток". Само считывание, а это копирование данных с диска в память, делается на уровне железа с помощью DMA. Для этого не нужен никакой поток. Первоначальный вызов запускает эта процедуру, т.е. дает железу команду "а скопируй с помощью DMA такой то блок с диска в такой то адрес в RAM" и возвращает управление
 
@павел Козлов - да, Вы пытаетесь натянуть системное (реализация на си, к примеру) к такой высокоуровневой, абстрагированной от железа штуке, как нода
 
@KoVadim понятно... ну т.е. на низком уровне это может быть либо асинхронно заблочили и ждем когда вернуться к выполнению скажет ОС, либо неблокируемо прошли если смогли сразу получить данные. А дальше уже более высокоуровневые языки этим жонглируют более сложные разные схемы реализовывая, могут и потоки дополнительные запускать, а могут и как то по другому...
@Roman-StopRUaggressioninUA а если мы говорить не про файлы, а про сетевой обмен данными например, тоже так реализовываться может?
 
@ПавелКозлов Практически вся периферия, включая диски и сетевые карты умеют передавать данные в память через DMA, т.е. без задействования процессора. Только с сетью все еще интереснее, потому что ядро ОС отправляет пакет(ы) с запросом, а потом надо еще подождать пока другая сторона пришлет по сети ответ, а это может быть достаточно долго. Но никакого явного ожидания никто не делает, просто стоят всякие метки, что мы такие то данные ждем. Когда данные придут, сетевая карта в память их положит, вызовет прерывание, по которому ОС уже посмотрит кто их ждал и сообщит тому процессу, что они готовы
 
avp
Если при асинхронном чтении данные есть в файловом кэше ядра, то они будут скопированы в пользовательский процесс (и этот случай не отличается от синхронного чтения). Если их там нет, то запрос на чтение будет поставлен в очередь к драйверу, работающенму по прерываниям, а в соответствующем блоке управления будет запомнен pid процесса, которому надо будет отправить сигнал, когда данные будут считаны. При завершении операции драйвер возобновляет свою работу по прерыванию. Далее, диспетчер процессов пошлет сигнал в процесс (как всегда в комментарии мало места, а длинный ответ писать лень)
 
6:24 PM
@ПавелКозлов Вы несколько не так понимаете потоки. Поток - это выполняемые подряд инструкции процессора. Когда нужны данные от ядра, происходит вызов, если данные не доступны то потоку надо либо остановится и не делать ничего, либо заняться другой работой. Если ввод-вывод блокируемый, то ядро отмечает ваш поток ожидающим ввода-вывода и отдает процессор другим потокам, которым он нужен. Если вызов асинхронный, то ядро раздает запросы на данные куда надо и тут же возвращает управление вашему потоку....
Ваш поток при этом может заниматься любой работой, периодически спрашивая ядро, а не появились ли данные, но чаще всего ваш поток выдав ядру несколько запросов обнаруживает что ему делать больше нечего, в этом случае он просит ядро остановить себя до того момента как не появятся какие нибудь данные (как при синхронном запросе) и ядро когда данные появятся его разбудит и сообщит какие именно данные стали доступны
И да, ваш высокоуровневый язык большую часть этой кухни от вас скрывает, делая более удобные примитивы вроде промисов node.js
 
@Mike спасибо. Резюмирую, когда у меня идет мой поток и видит системный вызов он переходит в режим ядра, раздает команды нужным устройтсвам откуда что он хочет получить и куда это положить, и возвращается обратно в пользовательский режим выполнять код дальше если есть, и когда вернется ответ от ОС что данные готовы поток вернется в нужное место доделать работу, примерно так ?
@Mike а возможна ли ситуация когда мой поток вызвал системный вызов, потом вернулся и пошел дальше, и еще не успел доделать свою работу как ОС вернула что данные готовы. Что тогда будет? ведь поток как бы один?
 
поток вернется в нужное место доделать работу, примерно так - нет. Не может поток вернуться. Упрощенно: есть два способа, как ваша программа узнает, что асинхронная операция завершилась. Первый, это она вызовет библиотечную фукнцию типа epoll или select. Такая функция без блокировки сообщит, что асинхронная операция завершилась и есть данные для чтения.
второй - это как происходит в posix async io, там будет вызван callback, который вы передали, когда инициировали асинхронную операцию. Ее вызовет библиотека реализующая это API, я не знаю детали реализации, но она судя по всему, запускает поток предназначенный для таких нотификаций
 
6:48 PM
@Roman-StopRUaggressioninUA да я вот имел ввиду под "доделать работу" как раз второй варианты - callback функцию
 
7:37 PM
@ПавелКозлов callback на уровне ядра нет. все callback реализуются уже на уровне языка высокого уровня. Идея в том, что пока потоку есть какую процессорную работу выполнять, он ее выполняет. Когда работы нет, он дергает функцию ядра select/epoll которая просто усыпляет его (забирает процессор) до тех пор пока не произойдет какое нибудь событие из ожидаемых. Как только событие происходит с точки зрения процесса вызов select/epoll завершается и возвращает список того, где есть данные
событием кроме ввода-вывода при этом может быть таймаут. т.е. поток может сказать этими вызовами "усыпи меня на N секунд либо до события ввода-вывода". И периодически просыпаясь поток может еще что нибудь поделать.
И да, конечно ситуация что ваш поток запросил данные, начал выполнять процессорную работу и тут вдруг у ядра есть уже данные может быть. При этом поток так и продолжит делать свою работу и ничего о новых данных не узнает до тех пор пока явно не позовет select/epoll. И вот как раз это надо четко себе представлять делая асинхронное приложение. В потоке работающем с вводом-выводом просто нельзя делать длительную процессорную работу, без вызовов await.
Если такую работу все таки выполнять надо и отдельный поток заводить не хочется то в середине этой работы (например основного ее цикла, если он есть) ставят что нибудь вроде await sleep(0.01) (в зависимости от вашего языка это может называться немного по разному, но очень вероятно что это зовется sleep и служит вроде как для остановки на заданное время, но под капотом оно всегда передает управление тому самому select/epoll который опрашивает ядро на наличие новых событий
 
7:56 PM
@Roman-StopRUaggressioninUA В posix aio ничего особо нового не придумано. На выбор даются варианты: 1. опрашивать самому. 2. попросить дать стандартный posix сигнал. 3. Запустить функцию обработчик события в новом отдельном потоке. У 2 и 3 вариантов есть минусы такие как собственно запуск нового потока, достаточно дорогая операция для 3 варианта.
 
@Mike т.е. когда мы делаем асинхронный вызов с callback, и продолжаем выполнять работу мы не попадем в коллбек пока не будет очередного асинхронного вызова который в свою очередь вызовет select и выберет следующее по приоритету готовое событие? Но работать все как работало так и будет работать в одном единственном процессе? (естественно если другово не написано именно в конкретной логике программы)
 
А сигнал это действительно как настоящий callback, текущий стек потока сохраняется, состояние процессора сохраняется, управление передается в обработчик сигнала с новым стеком в рамках текущего потока. При этом есть куча подводных камней, легко приводящих к состояниям гонки, срыву выполнения системных вызовов и т.п. в таких обработчиках рекомендуется выполнять минимум работы, по возможности без системных вызовов, например взведение флага, что есть данные что бы обработать его за пределами сигнал
@ПавелКозлов Вообще зависит от языка. В модели чистой асинхронности, да именно так, пока в данный момент выполняющийся код не выполнит какого либо асинхронного запроса с передачей управления в цикл ожидания событий (выполняющий select) то callback не сработает. И на сколько я понимаю обычно node.js именно так себя и ведет. А например какой нибудь C# может выполнять коллбеки в других потоках, там просто асинхронность более тесно связана с многопоточностью
 
@Mike понял. больше спасибо! Может быть что-то посоветуете чтобы структурировать всю эту внутреннюю кухню. Что лучше по архитектуре компьютера почитать или по ОС ?
 
avp
8:17 PM
@ПавелКозлов Ядро Linux. Описание процесса разработки | Лав Роберт достойная книга, мне понравлась. Возможно, будет не очень понятна, если плохо представляете железо и функции ОС
 
@ПавелКозлов Лично я читал в свое время ISBN 5-7315-0013-4, после писал на C несколько проектов где активно использовал select и "ручную асинхронность", т.е. без доп библиотек. А потом переходя на другие языки уже из интернета как то подчерпывал особенности, как у них это происходит. Просто осознавая к чему под капотом это должно сводиться (так как других механизмов кроме select (и более навороченного аналога epoll) просто нет
 

  last day (16 days later) »