что такое сетевой демон

Как работают демоны, процесс Init и как у процессов рождаются потомки — изучаем основы Unix

Авторизуйтесь

Как работают демоны, процесс Init и как у процессов рождаются потомки — изучаем основы Unix

Если вы когда-нибудь работали c Unix-системами, то наверняка слышали термин «демон». В этой статье я хочу объяснить, что это за демоны и как они работают, тем более что их название заставляет думать, что это что-то плохое.

Вообще демон — это фоновый процесс, который не привязан к терминалу, в котором был запущен. Но как они создаются, как они связаны с другими процессами, как они работают? Об этом мы и поговорим, но сперва давайте узнаем, как работает процесс init и как происходит рождение новых процессов.

Как работает процесс Init

Для начала поговорим о процессе init, также известном как PID 1 (поскольку его ID всегда равен 1). Это процесс создаётся сразу при запуске системы, то есть все другие процессы являются его потомками.

Обычно init запускается, когда ядро вызывает конкретный файл, обычно находящийся по адресу /etc/rc или /etc/inittab. Процесс устанавливает путь, проверяет файловую систему, инициализирует серийные порты, задаёт время и т.д. В последнюю очередь он запускает все необходимые фоновые процессы — в виде демонов. Все демоны обычно расположены в папке /etc/init.d/; принято оканчивать имена демонов на букву d (например, httpd, sshd, mysqld и т.п.), поэтому вы можете подумать, что директория названа так по этому же принципу, но на самом деле существует соглашение об именовании папок, содержащих конфигурационные файлы, именем с суффиксом .d. Итак, init запускает демонов, но мы так и не выяснили, как это происходит. Процесс init запускает демонов, создавая свои ответвления для запуска новых процессов.

Как работает разветвление процессов

Единственный способ создать новый процесс в Unix — скопировать существующий. Этот метод, известный как разветвление или форкинг, включает в себя создание копии процесса в виде потомка и системный вызов exec для запуска новой программы. Мы использовали слово «форкинг», поскольку fork — это реальный метод C в стандартной библиотеке Unix, который создаёт новые процессы именно таким образом. Процесс, вызывающий команду fork, считается родительским по отношению к созданному. Процесс-потомок почти полностью совпадает с родительским: отличаются лишь ID, родительские ID и некоторые другие моменты.

В современных дистрибутивах Unix и Linux процессы можно создавать и другим способами (например, при помощи posix_spawn), но большая часть процессов создаётся именно так.

Источник

Что такое демоны (daemons) в Linux?

Обновл. 20 Июл 2021 |

В этой статье мы рассмотрим, что такое демоны (и их примеры) в Linux, а также версии происхождения термина «daemon».

Что такое демоны?

Демоны (англ. «daemons») — это работающие в фоновом режиме служебные программы (или процессы), целью которых является мониторинг определенных подсистем ОС и обеспечение её нормальной работы. Например, демон принтера контролирует возможности печати, демон сети контролирует и поддерживает сетевые коммуникации и т.д.

Демоны являются аналогом служб (services) в Windows: они выполняют определенные действия в заранее определенное время или в ответ на определенные события. Существует множество различных демонов, работающих в Linux, каждый из которых создан специально для наблюдения за своей собственной маленькой частью системы. Из-за того, что демоны выполняют основную часть своей работы в фоновом режиме и не находятся под прямым контролем пользователя, бывает трудно определить предназначение того или иного демона.

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

Процесс — это запущенная программа. В определенный момент времени процесс может либо выполняться, либо ожидать, либо быть «зомби».

В Linux существует три типа процессов:

Процессы переднего плана (или «интерактивные процессы») — это те процессы, которые запускаются пользователем в терминале.

Фоновые процессы (или «автоматические процессы») — это объединенные в список процессы, не подключенные к терминалу; они не ожидают пользовательского ввода данных.

Демоны (англ. «daemons») — это особый тип фоновых процессов, которые запускаются при старте системы и продолжают работать в виде системных служб; они не умирают.

Процессы переднего плана и фоновые процессы не являются демонами, хотя их можно запускать в фоновом режиме и выполнять некоторую работу по мониторингу системы. Для данных типов процессов необходимо участие пользователя, который бы их запускал. В то время как демонам для их запуска пользователь не требуется.

Когда завершается загрузка системы, процесс инициализации системы начинает создавать демоны с помощью метода fork(), устраняя необходимость в терминале (именно это подразумевается под «отсутствием управляющего терминала»).

Я не буду вдаваться в подробности работы метода fork(), отмечу лишь, что, хотя существуют и другие методы, традиционный способ создания дочернего процесса в Linux заключается в создании копии существующего процесса (посредством своеобразного «ответвления»), после чего выполняется системный вызов exec() для запуска другой программы.

Примечание: Термин «fork» не был взят с потолка. Он получил свое название от метода fork() из Стандартной библиотеки языка программирования Си. В языке Си данный метод предназначен для создания новых процессов.

Примеры демонов в Linux

Команда pstree показывает процессы, запущенные в настоящее время в нашей системе, и отображает их в виде древовидной диаграммы. Откройте терминал и введите следующую команду:

что такое сетевой демон. Смотреть фото что такое сетевой демон. Смотреть картинку что такое сетевой демон. Картинка про что такое сетевой демон. Фото что такое сетевой демон

Вывод команды pstree — это довольно хорошая иллюстрация того, что происходит с нашей системой. Перед нами появился список всех запущенных процессов, среди которых можно заметить и несколько демонов: cupsd, dbus-daemon, kdekonnectd, packagekitd и некоторые другие.

Вот несколько «популярных» примеров демонов, которые могут работать в вашей системе:

systemd — это системный демон, который (подобно процессу init) является родителем (прямым или косвенным) всех других процессов, и имеет PID=1.

rsyslogd — используется для регистрации системных сообщений. Это более новая версия syslogd, имеющая несколько дополнительных функций.

udisksd — обрабатывает такие операции, как: запрос, монтирование, размонтирование, форматирование или отсоединение устройств хранения данных (жесткие диски, USB-флеш-накопители и пр.).

logind — крошечный демон, который различными способами управляет входами пользователей в систему.

sshd — демон, отвечающий за управление службой SSH. Используется практически на любом сервере, который принимает SSH-соединения.

ftpd — управляет службой FTP. Протокол FTP (сокр. от англ. «File Transfer Protocol») является широко используемым протоколом для передачи файлов между компьютерами, где один компьютер действует как клиент, другой — как сервер.

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

Версии происхождения термина «daemon»

Есть несколько версий происхождения термина «daemon»:

Научная версия: Использование термина «daemon» в вычислительной технике произошло в 1963 году. Project MAC (сокр. от англ. «Project on Mathematics and Computation») — это проект по математике и вычислениям, созданный в Массачусетском технологическом институте. Именно здесь термин «daemon» вошел в обиход для обозначения любого системного процесса, отслеживающего другие задачи и выполняющего предопределенные действия в зависимости от их поведения. Процессы были названы термином «daemons» в честь демона Максвелла.

Примечание: Демон Максвелла — это результат мысленного эксперимента. В 1867 году Джеймс Клерк Максвелл представил себе разумное и изобретательное существо, способное наблюдать и направлять движение отдельных молекул в заданном направлении. Цель мысленного эксперимента состояла в том, чтобы показать возможность противоречия второму закону термодинамики.

Талисман BSD: В операционных системах BSD есть свой талисман — красный чертёнок (этакая игра слов «daemon/demon»). BSD-демона зовут Beastie (Бисти), и его часто можно увидеть с трезубцем, который символизирует системный вызов fork(), активно используемый программами-демонами.

что такое сетевой демон. Смотреть фото что такое сетевой демон. Смотреть картинку что такое сетевой демон. Картинка про что такое сетевой демон. Фото что такое сетевой демон

Примечание: «Бисти» по звучанию напоминает BSD (произносится как «Би-Эс-Ди»). При этом beastie является уменьшительной формой от слова beast (зверь).

Теологическая версия: Сторонники данной версии считают, что первоначальной формой произношения слова «daemon» было «daimon», что обозначает (по одной из версий) ангела-хранителя. В то время как «daemon» — помощник, «demon» — злой персонаж из Библии.

Примечание: Также «daemon» иногда произносится как «day-mon» или как рифма к слову «diamond».

Аббревиатура: Некоторые пользователи утверждают, что термин «daemon» является аббревиатурой от «Disk and Execution Monitor».

Поделиться в социальных сетях:

Android – это Linux? Сравнение Android и Linux

Источник

Еще раз об архитектуре сетевых демонов

Во многих статьях, в том числе на хабре, упоминаются и даже описываются разные способы построения архитектуры сетевых сервисов (демонов). При этом мало у кого из авторов есть реальный опыт создания и оптимизации демонов, работающих с десятками тысяч одновременных соединений и/или гигабитным трафиком.

Так как большинство авторов не удосуживается хотя бы залезть в документацию, то обычно в таких статьях вся информация базируется на неких слухах и пересказах слухов. Эти слухи бродят по сети и поражают википедию, хабрахабр и другие уважаемые ресурсы. В результате получаются опусы вроде «Вы наверное шутите, мистер Дал, или почему Node.js» (пунктуация автора сохранена): она, в основном, верная по сути, но изобилует неточностями, содержит ряд фактических ошибок и изображает предмет с какого-то непонятного ракурса.

Мне было сложно пройти мимо статьи, изобилующей фразами вроде «эффективные реализации polling’а на сегодняшний день имеются лишь в *nix-системах» (как будто poll() есть где-то, кроме некоторых *nix). Этот пост начинался как комментарий, разъясняющий уважаемому inikulin ошибки в его статье. В процессе написания оказалось, что проще изложить предмет с самого начала, что я собственно и делаю отдельным постом.
В моем очерке нет срыва покровов или каких-то неизвестных трюков, здесь просто описываются преимущества и недостатки разных подходов человеком, который проверял, как всё это работает на практике в разных операционных системах.
Для желающих просветиться — добро пожаловать под кат.

ТЗ для сетевого демона

Сначала нужно понять, что именно должны делать сетевые сервисы и в чем, вообще, состоит проблема.

Любой демон должен принимать и обрабатывать сетевые соединения. Так как стек протоколов TCP/IP вырос из UNIX, а эта ОС исповедует догму «всё есть файл», то сетевые соединения есть файлы особого типа, которые можно открывать, закрывать, читать и писать стандартными функциями ОС для работы с файлами. Обратите внимание на модальный глагол «можно», в совокупности со словом «теоретически» он очень точно описывает действительность.

Итак, первым делом любой демон вызывает системные функции socket(), затем bind(), затем listen() и в итоге получает файл специального типа «слушающий сокет». Параметры этих функций и дальнейшие действия демона очень сильно зависят от применяемого транспортного протокола (TCP, UDP, ICMP, RPD. ), впрочем, в большинстве ОС вы можете bind-ить только первые два. В этой статье в качестве примера мы рассмотрим наиболее популярный протокол TCP.

Хотя слушающий сокет есть файл, всё, что с ним может происходить — это периодически возникающие события типа «запрос входящего соединения». Демон может принять такое соединение функцией accept(), которая создаст новый файл, на этот раз уже типа «открытый сетевой сокет TCP/IP». Предположительно, демон должен прочитать из этого соединения запрос, обработать его и отправить назад результат.

При этом, сетевой сокет — это уже более-менее нормальный файл: хоть он и был создан не самым стандартным образом, по крайней мере из него можно пытаться читать и писать данные. Но есть и существенные отличия от обычных файлов, расположенных на файловой системе:
* Все события происходят действительно асинхронно и с неизвестной длительностью по времени. Любая операция в худшем случае может занять десятки минут. Вообще любая.
* Соединения, в отличие от файлов, могут закрываться «сами собой» в любой, самый неожиданный момент.
* ОС не всегда сообщает о закрывшемся соединении, «мертвые» сокеты могут висеть по полчаса.
* Соединения на клиенте и на сервере закрываются в разное время. Если клиент попытается создать новое соединение и «доотправить» данные, возможно дублирование данных, а при неправильно написанном клиенте — и их потеря. Также возможно наличие на сервере нескольких открытых соединений от одного клиента.
* Данные расцениваются как поток байтов и могут буквально приходить порциями по 1 байту. Поэтому считать их, например, строками UTF-8 нельзя.
* Никаких буферов кроме тех, которые предоставил сам демон, в сети нет. Поэтому запись в сокет даже 1 байта может заблокировать демон на десятки минут (см. выше). Кроме того, память на сервере «не резиновая», демон должен уметь ограничивать скорость, с которой генерируются результаты.
* Любые ошибки могут случаться в любом месте, демон должен корректно обрабатывать их все.

Если написать цикл по всем открытым соединениям «в лоб», то первое же «подвисшее» соединение заблокирует все остальные. Да-да, на десятки минут. И тут возникают различные варианты организации взаимодействия различных модулей демона. Смотрите рисунок:
что такое сетевой демон. Смотреть фото что такое сетевой демон. Смотреть картинку что такое сетевой демон. Картинка про что такое сетевой демон. Фото что такое сетевой демон
Disclaimer: На рисунке приведен не соответствующий реальности код на псевдоязыке. Многие важные системные вызовы и весь код обработки ошибок опущены для ясности.

2. Многопроцессная архитектура

Простейший способ не дать соединениям влиять друг на друга — это запустить для каждого из них отдельный процесс (то есть отдельную копию вашей программы). Недостатки этого метода очевидны — запуск отдельного процесса это очень ресурсоёмкая операция. Но в большинстве статей не объясняется, почему именно такой способ используется в том же Apache.

А всё дело в том, что именно процесс во всех ОС является единицей учета системных ресурсов — памяти, открытых файлов, прав доступа, квот и так далее. Если вы создаете демон удаленного доступа к операционной системе вроде Shell или FTP — вы просто обязаны запускать отдельный процесс от имени каждого залогинившегося пользователя, чтобы правильно учитывать права доступа к файлам. Аналогично, на сервере shared-хостинга одновременно, на одном «физическом» порту крутятся сотни сайтов разных пользователей — и процессы нужны apache для того, чтобы сайты одних пользователей хостинга не могли залезть в данные других пользователей. Использование процессов не очень-то влияет на производительность Апача:
что такое сетевой демон. Смотреть фото что такое сетевой демон. Смотреть картинку что такое сетевой демон. Картинка про что такое сетевой демон. Фото что такое сетевой демон
На графике — количество обрабатываемых запросов к статическому файлу в секунду в зависимости от версии ядра Linux. Больше — лучше.
Тестовый стенд: Core i7 970, 3Gb DDR3, Nvidia GTX 460, 64GB OCZ Vertex SSD.
Источник: Phoronix.
Желаю и вашим демонам отдавать по 17к файлов в секудну.

Даже если пользователи вашего демона не зарегистрированы в операционной системе, но к вашему сервису выдвигаются повышенные требования по безопасности, выделение отдельного процесса под каждого пользователя есть очень разумное архитектурное решение. Это не позволит пользователям-«плохишам» получать или блокировать доступ к данным других пользователей, даже если они найдут баг в вашем демоне, позволяющий читать чужие данные или просто рушащий процесс демона.

Наконец, у каждого процесса имеется собственное адресное пространство, и разные процессы не мешают друг другу пользоваться памятью. Почему это преимущество — объясняется в части

3. Многопоточная архитектура

Потоки — это максимально легкие «процессы», имеющие общую память, системные ресурсы и права доступа, но разные стеки. Это означает, что у потоков общие динамические, глобальные и статические переменные, но разные локальные. В многопроцессорных и/или многоядерных системах разные потоки одного процесса могут выполняться физически одновременно.

Многопоточная архитектура похожа на многопроцессную, в которой безопасность и стабильность принесены в жертву производительности и уменьшению расходов памяти.

Главным плюсом многопоточной архитектуры после производительности является последовательность и синхронность алгоритма обработки открытого соединения. Это значит, что алгоритм выглядит и выполняется именно так, как нарисовано на иллюстрации в первой части. Сначала из сокета читаются данные, столько времени, сколько для этого нужно, потом они обрабатываются — опять же, столько времени, сколько эта обработка потребует, потом результаты отправляются клиенту. При этом, если вы начнете отправлять результаты слишком быстро — поток автоматически заблокируется на функции write(). Алгоритм обработки прост и понятен хотя бы на самом верхнем уровне. Это очень, очень большой плюс.

Для относительно небольшого количества одновременных соединений многопоточная архитектура — отличный выбор. Но если соединений действительно много, скажем десять тысяч, переключение между потоками начинает занимать слишком много времени. Но даже это — не главный недостаток многопоточной архитектуры.

А главный состоит в том, что потоки не независимы и могут (и будут) друг друга блокировать. Чтобы понять, как это происходит, рассмотрим пример.

Допустим, нам нужно вычислить значение выражения
a = b + c;
, где a, b и с есть глобальные переменные.

В обычной, однопоточной ситуации компилятор сгенерирует примерно такой машинный код:
a = b; // MOV A, B
a += c; // ADD A, C

В многопоточном варианте использовать этот код нельзя. Другой поток может изменить значение b между первой и второй инструкциями, в результате чего мы получим неверное значение a. Если где-то в другом месте считается, что a всегда равно b+c, возникнет очень трудно воспроизводимая «плавающая» ошибка.

Поэтому, в многопоточном варианте используется код вроде такого:
lock a;
lock b;
lock c;
a = b;
a += c;
unlock c;
unlock b;
unlock a;

, где lock и unlock — это атомарные операции блокировки и разблокировки доступа к переменной. Они устроены таким образом, что если переменная уже заблокирована другим потоком, операция lock() будет ждать её освобождения.

Таким образом, если два потока начнут одновременно выполнять операции a = b + c и b = c + a, они заблокируют друг друга навсегда. Такая ситуация называется клинчем, поиск и разрешение клинчей — отдельная «больная тема» параллельного программирования. Но и без клинчей потоки, если они не снимают блокировки быстро, могут останавливать друг друга на достаточно длительные промежутки времени.

Кроме того, атомарные операции физически реализуются при помощи монопольного захвата шины ОЗУ и самой ОЗУ. Работа напрямую с памятью, а не с кэшем, очень медленна сама по себе, а в данном случае еще и вызывает инвалидацию (сброс) соответствующих кэш-линий всех ядер всех остальных процессоров сервера. То есть даже в лучшем случае, при отсутствии блокировок, каждая атомарная операция выполняется достаточно долго и ухудшает производительность остальных потоков.

Но, казалось бы, поскольку соединения в демонах почти независимы, откуда у них могут взяться общие переменные?
А вот откуда:
* Общая очередь новых соединений;
* Общая очередь доступа к базе данных или подобным ресурсам;
* Общая очередь запросов на выделение памяти (да-да, malloc() и new() могут вызвать блокировку);
* Общий журнал (log-файл) и общие объекты подсчета статистики.
Это только самые очевидные.

В некоторых случаях существуют способы обойтись без общих переменных. Например, от блокировок очереди новых соединений можно отказаться, если наделить один из потоков функцией «диспетчера», который будет неким хитрым образом раздавать задания заранее. Иногда удается применить специальные «неблокирующие» структуры данных. Но в целом проблема взаимных блокировок в многопоточной архитектуре не решена.

4. Неблокирующая архитектура

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

Неблокирующий ввод-вывод — это просто режим доступа к файлу, который можно установить в большинстве современных ОС. Если в обычном, «блокирующем» режиме функция read читает из файла столько байт, сколько заказал программист, а пока это чтение происходит — «усыпляет» вызвавший её поток, то в неблокирующем режиме та же самая функция read читает не из файла, а из его кэша, столько байт, сколько в этом кэше есть, и после этого сразу возвращается, никаких потоков не усыпляя и не блокируя. Если кэш был пуст, неблокирующий read() прочтет 0 байт, установит код системной ошибки в значение EWOULDBLOCK и сразу вернется. Но всё равно это обычный синхронный вызов обычной синхронной функции.

Некоторая путаница, в частности, в англоязычной википедии, в которой неблокирующий синхронный ввод-вывод назван «асинхронным», вызвана, видимо, не очень любознательными апологетами ОС Linux. В этой операционной системе достаточно долго, вплоть до ядер 2.6.22-2.6.29, просто не было никаких асинхронных функций ввода-вывода вообще (да и сейчас есть не весь необходимый набор, в частности нет асинхронной fnctl), и некоторые программисты, писавшие только под эту ОС, ошибочно называли неблокирующие синхронные функции «асинхронными», что прослеживается в ряде старых мануалов для Linux.

Подробно асинхронный ввод-вывод рассматривается в следующей части, а здесь сосредоточимся на применении неблокирующих функций чтения и записи.

В реальных условиях 95% вызовов неблокирующего read() будут читать по 0 байт. Чтобы избежать этих «холостых» вызовов ядра ОС, существует функция select(), позволяющая попросить операционную систему выбрать из вашего списка соединений те, из которых уже можно читать и/или писать. В некоторых *nix ОС есть вариант этой функции под названием poll().

Подробнее касательно poll(): эта функция появилась как требование очередной версии стандарта POSIX. В случае Linux poll() сначала был реализован как функция стандартной библиотеки языка Си (libc>=5.4.28) в виде обертки поверх обычного select(), и только через некоторое время «переехал» в ядро. В Windows, например, нормальной функции poll() нет до сих пор, но начиная с Vista есть некий паллиатив для упрощения миграции приложений, так же реализованный в виде обертки вокруг select() на Си.
Не могу не поделиться графиком, показывающим, к чему все эти нововведения приводят. На графике — время прокачки 10 Гб данных через интерфейс-петлю в зависимости от версии ядра. Меньше — лучше. Источник тот же, тестовый стенд тот же.
что такое сетевой демон. Смотреть фото что такое сетевой демон. Смотреть картинку что такое сетевой демон. Картинка про что такое сетевой демон. Фото что такое сетевой демон

В любом случае, хотя у select() есть определенные ограничения (в частности, на количество файлов в одном запросе), использование этой функции и неблокирующего режима ввод-вывода — это способ в три десятка строк кода переложить всю работу на операционную систему и просто обрабатывать свои данные. В большинстве случаев, отвечающий за неблокирующий ввод-вывод поток будет потреблять всего пару процентов вычислительной мощности одного ядра. Выполняющие все вычисления внутренние потоки ядра операционной системы «съедят» в десятки раз больше.

Вернёмся к уменьшению количества потоков.
Итак, в демоне имеется большое количество объектов класса «соединение», и существует набор операций, которые нужно применить к каждому объекту-соединению, причем в нужном порядке.

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

В архитектуре с неблокирующим вводом-выводом создается поток под каждую операцию, которая последовательно применяется к разным объектам. Это немножко похоже на SIMD-инструкции типа MMX и SSE: одна инструкция применяется сразу к нескольким объектам. Чтобы выдерживать необходимую последовательность операций (то есть сначала вычислить результат, а потом его отправлять), в общей памяти процесса создаются очереди заданий между потоками. Обычно очереди создаются на базе кольцевых буферов, которые в данном случае можно реализовать «неблокирующим» образом.

В реальном сетевом сервисе между чтением запроса и отправкой результата будет стоять достаточно сложный разветвленный алгоритм обработки, возможно включающий вызов сервера приложений, СУБД или другие «тяжелые» операции, а также полный набор ветвлений, циклов, обработку ошибок на каждом шаге и т.п. Разбить всё это по заранее неизвестному количеству выполняющихся одновременно потоков, да еще так, чтобы нагрузка на процессорные ядра была примерно одинаковой — это высший уровень умений разработчика, требующий виртуозного владения всеми аспектами системного программирования. В большинстве случаев делают намного проще: заключают в отдельный поток всё, что находится между read() и write(), и запускают N=количество ядер копий этого потока. А затем изобретают костыли для входящих в клинч, конкурирующих за ресурсы, «убивающих» СУБД и т.д. параллельных потоков.

5. Асинхронный ввод-вывод

Если вы не понимаете отличие асинхронных функций от синхронных, то для простоты можете считать, что асинхронная функция выполняется параллельно и одновременно с вызвавшей её программой, например, на соседнем ядре. С одной стороны, вызвавшей программе не нужно ждать окончания вычислений и она может сделать что-то полезное. С другой стороны, когда результаты асинхронной функции готовы, нужно каким-то образом сообщить об этом программе-«заказчику». То, каким образом происходит это сообщение, реализовано в разных ОС очень по-разному.

Исторически одной из первых ОС с поддержкой асинхронного ввода-вывода стала Windows 2000.
Типичный use case был такой: однопоточное приложение (никаких многоядерных процессоров тогда не было) загружает, например, большой файл в течение десятков секунд. Вместо зависания интерфейса и «часиков», которые наблюдались бы при синхронном вызове read(), в асинхронном варианте основной поток программы не «зависнет», можно будет сделать красивый прогресс-бар, отображающий процесс загрузки, и кнопку «отмена».
Для реализации «прогресс-бара» в асинхронные функции ввода-вывода Windows передается специальная структура OVERLAPPED, в которой ОС отмечает текущее количество переданных байт. Программист может читать содержимое этой структуры в любой удобный ему момент — в основном цикле обработки сообщений, по таймеру и т.д. В этой же структуре в конце операции будет записан её итоговый результат (общее число переданных байт, код ошибки если он есть и т.п.).

Кроме этой структуры, в асинхронные функции ввода-вывода можно передать собственные функции обратного вызова (callback), принимающие указатель на OVERLAPPED, которые будет вызваны операционной системой по окончании операции.
Настоящий, честный асинхронный запуск callback-функции, через прерывание программы в любом месте, где бы она не находилась, не отличим от запуска второго потока выполнения программы на этом же ядре. Соответственно, нужно либо очень аккуратно писать callback-функции, либо применять все «многопоточные» правила, касающиеся блокировок доступа к общим данным, что, согласитесь, очень странно в однопоточном приложении. Чтобы избежать потенциальных ошибок в однопоточных приложениях, Windows ставит необработанные callback-и в очередь, а программист должен явно указать места в своей программе, где она может быть прервана для выполнения этих callback-ов (семейство функций WaitFor*Object).

Описанная выше схема асинхронного ввода-вывода является «родной» для ядра WindowsNT, то есть все остальные операции так или иначе реализуются через неё. Полное название — IOCP (Input/Output Completion Port). Считается, что эта схема позволяет добиться от железа теоретически максимальной производительности. Любые демоны, рассчитанные на серьезную работу под Windows, должны разрабатываться именно на основе IOCP. Подробнее см. введение в IOCP в MSDN.

В Linux вместо нормальной структуры OVERLAPPED есть некое слабое подобие aiocb, позволяющая определить только факт завершения операции, но не её текущий прогресс. Вместо определяемых пользователем callback-ов ядро использует сигналы UNIX (да-да, те, которые kill ). Сигналы приходят полностью асинхронно, со всеми вытекающими, но если вы не чувствуете себя гуру в деле написания реентерабельных функций, вы можете создать файл специального типа (signalfd) и читать из него информацию о пришедших сигналах обычными синхронными функциями ввода-вывода, в том числе неблокирующими. Подробнее см. man aio.h.

Использование асинхронного ввода-вывода не накладывает никаких ограничений на архитектуру демона, теоретически она может быть любой. Но, как правило, используются несколько рабочих потоков (по количеству процессорных ядер), между которыми равномерно распределяются обслуживаемые соединения. Для каждого соединения строится и программируется конечный автомат (Finite State Machine, FSM), появление событий (вызовов callback-функций и/или ошибок) переводит этот автомат из одного состояния в другое.

Резюме

Как мы видим, у каждого способа есть свои преимущества, недостатки и области применения. Если вам нужна безопасность — используйте процессы, если важна скорость работы при большой нагрузке — неблокирующий ввод-вывод, а если важна скорость разработки и понятность кода, то подойдет многопоточная архитектура. Асинхронный ввод-вывод — основной способ в Windows. В любом случае не стоит пытаться писать код для работы с вводом-выводом самостоятельно. В сети есть свободные готовые библиотеки для всех архитектур и операционных систем, вылизанные за десятки лет почти до блеска. Почти — потому что в вашем случае всё равно придется что-то подкручивать, подпиливать и донастраивать под ваши условия. Интернет — сложная штука, здесь не бывает универсальных решений на все случаи жизни.

Как бы то ни было, одним вводом-выводом демоны не обходятся, а во время обработки запросов случаются намного более сложные «затыки». Но это уже тема для другой статьи, если оно кому-либо интересно.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *