1 сентября 2010 г.

Система инициализации Systemd. Часть I

Наверное, все уже слышали о новой системе инициализации systemd, которая разрабатывается под опекой Red Hat и Novell. Я решил перевести описание работы этой системы от ее автора из его же блога. Сама статья оказалась слишком большой, поэтому выкладываю пока только ее первую часть. Вторую часть я выложу в течение пары дней. Ссылка на оригинал традиционно приведена в конце поста. Также традиционно, мои комментарии по тексту приведены курсивом.


Если вы в теме и умеете хорошо читать между строк, то уже знаете, о чем пойдет речь. Но, думаю, все равно вам будет интересна эта статья. Поэтому налейте себе чашечку кофе, садитесь и читайте о том, что грядет.
История долгая, а для тех, кто не хочет читать целиком, скажу вкратце: мы экспериментируем с новой системой инициализации, и это весело!
Здесь код. А вот сама история:


Роль процесса с PID 1

 

Каждая Unix-система имеет процесс со своим специальным идентификатором, равным 1. Этот процесс запускается ядром прежде всех остальных и является родительским процессом для всех процессов, которые не имеют собственного родителя. Благодаря этому он может делать много чего такого, что не могут остальные процессы. Например отвечает за такие вещи, как инициализация и поддержка работы пользовательского пространства в процессе загрузки системы.

Исторически на Linux в качестве такого процесса выступает старый добрый sysvinit, преклонный возраст которого сказывается все чаще и чаще. Многие пытались предложить ему замену, но реально прижился только Upstart (что интересно, один из вариантов перевода слова «upstart» - «выскочка» - прим. перев.), который уже нашел свое место во всех основных дистрибутивах.

Как уже отмечалось выше, главная обязанность init'а - максимально быстро инициализировать и сделать доступным пользовательское пространство. К сожалению, традиционная схема инициализации SysV делает это не так быстро, как хотелось бы.
Для организации быстрого и эффективного процесса загрузки системы решающее значение имеет следующее:
  • Запустить минимум необходимого.
  • Попытаться запустить параллельно всё остальное.

Что это означает на практике?
Запустить минимум необходимого означает запустить лишь самые необходимые сервисы либо отложить запуск каких-либо из них до тех пор, пока они реально не понадобятся. Иногда мы заранее знаем, какие сервисы нам понадобятся (syslog, системная шина D-Bus и т.п.), но так бывает не всегда. К примеру, демон bluetoothd не должен быть запущен то тех пор, пока не будет подключен Bluetooth-адаптер либо пока какое-то из приложений не захочет установить с ним соединение через интерфейс D-Bus. Аналогично для системы печати: если машина физически не подключена к принтеру, и/или никакие приложения не пытаются напечатать что-нибудь, то необходимости запускать демон печати, такой как CUPS, нет. Так же и для Avahi: если машина не подключена к сети, или пока никакое из приложений не захочет использовать его API, его также нет необходимости запускать. Это справедливо даже для SSH: пока кто-либо не захочет зайти на машину - в нем нет необходимости, его нужно запустить лишь тогда, когда кто-то захочет установить первое соединение (и это же относится ко многим машинам, где может быть запущен ssh, в тех случаях, когда к нему присоединяются раз месяц или реже).
Параллельный запуск всего остального означает, что если нужно что-либо запустить, то мы должны делать это не последовательно, а пытаться запустить все одновременно, чтобы максимально нагрузить имеющиеся ресурсы системы, сократив время ее запуска.

 

Динамическое изменение аппаратного и программного обеспечения


Современные системы (в частности операционные системы общего назначения) являются весьма динамичными в плане своей конфигурации и использования: они мобильны, запускают и останавливают различные приложения, к ним подключают всевозможное оборудование. Система инициализации, отвечающая за работу сервисов, должна обнаруживать изменения в аппаратном и программном обеспечении,  запускать (а иногда и останавливать) сервисы, поскольку они необходимы для запуска программ и/или для обеспечения поддержки определенного оборудования.
Большинство современных систем, которые пытаются распараллелить процесс запуска, по-прежнему пытаются синхронизировать запуск различных демонов: например, Avahi для работы требуется D-Bus, поэтому сначала запускается D-Bus и только потом - Avahi.  Аналогично для других служб: для libvirtd и X11 необходим HAL (да, я знаю, что на Fedora 13 службы игнорируют устаревший HAL), поэтому сначала запускается HAL, а уж потом libvirtd и X11. А поскольку для libvirtd нужен Avahi, он ждет еще и запуска Avahi. К тому же всем упомянутым службам нужен Syslog, они все ждут его запуска. И так далее.

 

Параллелизация запуска сервисов, зависящих от сокетов (socket service)


Такого рода синхронизация запуска служб приводит к тому, что значительная часть запуска системы проводится последовательно. Правда было бы здорово избавится от описанных требований такой синхронизации? На самом деле, нет ничего невозможного! Для этого мы должны понять, что именно демоны требуют друг от друга и почему их запуск откладывается. Что касается традиционных демонов Unix, на этот вопрос есть только один ответ: они ждут, пока сокет другого демона станет доступен для соединения. Обычно это сокет AF_UNIX в файловой системе либо это может быть AF_INET [6]. К примеру, клиенты D-Bus ждут возможности подключения к /var/run/dbus/system_bus_socket, клиенты syslog - /dev/log, клиенты CUPS - /var/run/cups/cups.sock, NFS-клиенты  ожидают /var/run/rpcbind.sock и открытия порта portmapper и т. д. Только подумайте - это единственное, чего они ждут.
Таким образом, если причина задержки только в этом, если мы сможем добиться того, чтобы перечисленные сокеты стали доступны ранее запуска соответствующих демонов, мы сможем серьезно ускорить процесс запуска системы и запустить намного больше процессов одновременно. Как же этого добиться? На самом деле в Unix-подобных операционных системах все довольно просто: мы можем создать слушающие сокеты, задолго до реального запуска демона, а затем (когда он запустится) просто передать ему сокет посредством в exec(). Таким образом, мы можем создать все нужные нам сокеты для всех демонов на первом шаге системы инициализации, а затем на втором шаге просто запустить все демоны одновременно. Если одна служба нужна другой - ничего страшного не случится, даже если требуемая служба еще не запущена: единственное что произойдет - требуемое соединение встанет в очередь на ожидание и клиент будет ждать этого соединения (будет блокирован запросом). Но заблокирован будет только один клиент и только одним запросом. Кроме того, чтобы обеспечить необходимую параллелизацию запуска, теперь совсем не обязательно конфигурировать зависимости между службами, ведь мы запускаем все сокеты одновременно и запущенный сервис будет уверен в том, что он сможет присоединиться к нужному ему сокету.
Это ключевой момент, и без него все остальное понять будет нельзя, поэтому я попробую пояснить все то же самое, но другими словами и с примерами. Допустим, вы запускаете демон syslog и его клиенты одновременно. При этом все сообщения его клиентов будут добавлены в буфер сокета /dev/log.  Пока этот буфер не заполнится, клиентам не придется ждать, и они смогут запуститься. Как только syslog завершит процесс своего запуска, он обработает накопившуюся очередь сообщений. Другой пример: мы запускаем D-Bus и ее несколько клиентов одновременно. Если отправляется синхронный запрос (запрос, требующий мгновенного ответа) и поскольку ожидается ответ, клиент будет блокирован ожиданием, но только этот конкретный клиент и только до тех пор, пока D-Bus поймает этот запрос и обработает его.
По сути, буферы сокетов ядра работают на нас, помогая нам максимально распараллелить запуск служб, причем обработка последовательности запросов и их синхронизация выполняется ядром системы без какой-либо надобности вмешиваться в этот процесс из пользовательского пространства! И если все сокеты становятся доступными прежде реального запуска демонов, управление зависимостями также становится излишним (или, по крайней мере, вторичным): если демону нужен другой демон, он просто подключится к нему. И если целевой демон уже запущен, то первый сразу же обратится к последнему. А если нет, то обратившийся демон не будет ждать его запуска, если только он не отправил синхронного запроса. И даже если этот другой демон вообще не запущен, это может быть сделано автоматически. С точки зрения первого демона в описанных случаях нет никакой разницы, следовательно управление зависимостями становится не нужно или, по крайней мере, вторично. Процесс запуска происходит с оптимальной параллелизацией запуска демонов либо с их запуском по требованию. Кроме того, такой путь обеспечивает большую надежность, так как сокеты остаются доступны независимо от фактического состояния демона, который может иногда быть недоступным (например из-за своего падения). В самом деле, вы можете легко написать демон, который будет запускаться, останавливаться (или падать) и запускаться снова и т. п., причем клиенты не почувствуют остановок демона и ни один запрос не потеряется.
Ну что ж, теперь самое время для паузы - налейте себе еще кофе и будьте уверены, что дальше будет еще интереснее.
Только сначала давайте проясним несколько вещей: это какая-то новая идея? Нет, определенно нет. Я назову вам одну из самых известных систем, работающих как описано: это launchd от Apple - на MacOS прослушивание сокетов всех демонов осуществляется launchd. Сами сервисы могут все запускаться одновременно, и для них не надо настраивать никаких зависимостей. Это на самом деле действительно интересная идея, и именно в ней кроется причина того, почему MacOS удается обеспечить фантастическую скорость загрузки. Я настоятельно рекомендую одно видео , где разработчики launchd объясняют, как она работает. К сожалению, предложенная идея так и не получила распространения вне лагеря поклонников Apple.
Хотя она, на самом деле, даже старше, чем launchd. До нее хорошо известный почтенный inetd работал аналогичным образом: сокеты централизованно создавались демоном, который запускал необходимый сервис, передавая ему дескриптор файла сокета посредством exec(). Однако центральной идеей inetd, конечно же, были не локальные, а интернет-сервисы (хотя более поздняя реализация поддерживала и сокеты AF_UNIX). Также он не являлся инструментом для распараллеливания процесса загрузки системы или какого-либо управления зависимостями между сервисами. Для TCP-сокетов inetd, в первую очередь, использовался таким образом, что для каждого входящего соединения новый экземпляр порождался новый экземпляр демона. Это означает, что для каждого соединения порождался и инициализировался новый процесс, что не очень хорошо для высокопроизводительных серверов. Тем не менее, с самого своего рождения inetd также поддерживает и другой режим, где демон порождался первым соединением и потом тот же экземпляр принимал последующие соединения (опция wait/nowait в файле inetd.conf , которая, к сожалению, плохо документирована). Запуск демона для каждого соединения, вероятно, создал для inetd репутацию медленного сервера, что не совсем справедливо.

Параллелизация запуска служб, зависящих от D-Bus (d-bus services)


Современные демоны на Linux, как правило, предоставляют услуги посредством D-Bus, а не простого сокета AF_UNIX. Таким образом, появляется вопрос - а можем ли мы применить для них ту же логику распараллеливания запуска сервисов, что и для для традиционных сокетов? Да можем! В D-Bus уже есть все необходимое для этого: с помощью активации посредством D-Bus служба может быть запущена при первом же обращении к ней. Такой способ запуска дает нам минимальную возможность синхронизации на запрос, которая нам нужна для одновременного запуска служб-потребителей и служб-поставщиков: если мы хотим запустить Avahi одновременно с CUPS (CUPS использует Avahi для поиска mDNS/DNS-SD принтеров), то так и следует сделать, и если CUPS запустится раньше, то с помощью логики активации сервисов посредством шины мы заставляем D-Bus создать очередь запросов, пока Avahi запускается.

Резюме: активация демонов посредством сокетов и шины D-Bus позволяет нам запустить все демоны параллельно, без какой-либо синхронизации. Это позволяет нам получить «ленивые» сервисы: если сервис используется редко, можно просто загрузить его по потребности, вместо того, чтобы запускать его во время загрузки.
И если это не круто, тогда я не знаю, что круто!


Параллелизация заданий, имеющих отношение к файловой системе

Если вы посмотрите на графики визуализации загрузочного процесса текущих дистрибутивов, то увидите множество точек, требующих синхронных операций: самое важное - это задания, связанные с файловыми системами. Обычно при загрузке системы много времени тратится на ожидание инициализации всех устройств, перечисленных в файле /etc/fstab, затем на проверку файловых систем, инициализацию квот. Только после того как это все закончится, мы сможем продолжить процесс загрузки.
Можем ли мы как-то решить эту проблему? Оказывается, можем! Harald Hoyer выдвинул идею использования для этого старой доброй autofs. Системный вызов connect() показывает, что одна служба заинтересована в другой, аналогичным образом вызов open() (или подобный ему) показывает, что служба заинтересована в конкретном файле или файловой системе. Таким образом, чтобы улучшить параллелизацию запуска, можем заставить программы ждать, пока файловая система будет готова (смонтирована). Добиваемся мы этого следующим образом - мы создаем точки монтирования autofs, а затем, когда завершатся разные проверки целостности, инициализируются квоты и т.п., мы заменим ее реальной файловой системой. Когда работает autofs, попытки обращения к ней будут поставлены в очередь ядром системы и обратившийся процесс будет заблокирован (но  - только этот демон, и только эта конкретная попытка доступа). И таким образом мы сможем начинать запускать наших демонов  задолго до того, как наши файловые системы станут доступны, без каких-либо потерь файлов и с максимальной параллелизацией.
Распараллеливание заданий, связанных с файловой системой и с запуском сервисов, не имеет смысла для корневой системы, поскольку именно там хранятся бинарники всех сервисов. Однако по отношению к файловым системам, таким как, например, /home, которые обычно намного больше, а иногда даже зашифрованы, возможно даже находятся на удаленном компьютере, такой подход может серьезно оптимизировать время загрузки системы. И уж конечно стоит отметить, что такие виртуальные файловые системы, как procfs или sysfs, никогда не должны  монтироваться с помощью autofs.
Я не удивлюсь, если кто-то из читателей может посчитать, что интеграция autofs с системой инициализации ухудшит стабильность и что это решение вообще какое-то странное, страшное и уродливое. Однако, неоднократно опробовав это решение, я могу вам сказать, что оно совершенно правильное. Использование autofs позволяет, нам создать точку монтирования без необходимости обеспечить наличие самой файловой системы. На практике это влечет за собой только задержку доступа к ФС. Если приложение пытается получить доступ к файловой системе, находящейся под опекой autofs и мы очень долго не заменяем ее реальной файловой системой, оно будет находиться в состоянии interruptible sleep. Отметим также, что в любой момент, если точка монтирования так и не становится доступной к концу запуска (например, если fsck не удалось удачно проверить файловую систему), мы можем просто попросить autofs вернуть приложению код ошибки (например ENOENT). Поэтому я полагаю, что хоть на первый взгляд включение autofs в систему инициализации может показаться чересчур смелым, наш экспериментальный код показал, что эта идея работает на удивление хорошо на практике.

Сокращение количества вызываемых во время запуска системы скриптов
 
Еще один момент, который вытекает из логики загрузки MacOS: shell-скрипты - зло.
Shell-скрипты имеют свои преимущества и недостатки. Их можно быстро писать и отлаживать, но они медленно работают. Классическая логика загрузки sysvinit построена вокруг скриптов. Будь то /bin/bash или любая другая оболочка (написанная, чтобы сделать работу скриптов быстрой), в конце концов этот подход все равно упирается в медлительность работы. В моей системе скрипты, находящиеся в /etc/init.d/, вызывают grep по крайней мере 77 раз, awk - 92 раза, cut - 23 и sed - 74. Каждый раз, когда вызываются эти команды (или какие-то другие), порождаются новые процессы, производится поиск библиотек и т.д. А затем, когда запущенный процесс выполняет какую-то простейшую операцию с текстовыми строками - он завершается. Естественно, что это все работает ужасно медленно. Ни один другой язык, кроме оболочки, не работает, как описано. Кроме того, работа скриптов сильно зависит от различных переменных среды и тому подобного, причем это все трудно контролировать.
Итак, давайте избавимся от скриптов в процессе загрузки! Прежде чем мы сможем это сделать, нам нужно выяснить, как они используются сейчас: по большому счету картина такова - большую часть времени они заняты рутинными операциями. Большинство сценариев производит элементарные операции и вызов сервисов, поэтому эти операции можно переписать на С, либо отдельные исполняемые файлы, либо сами демоны, либо сделать конкретную операцию частью системы инициализации.
Маловероятно, что мы в ближайшее время полностью сможем избавиться от скриптов при старте системы. Переписывание их на C занимает много времени, в ряде случаев игра вообще не стоит свеч, а иногда, наоборот, без скриптов вообще трудно обойтись. Но мы, безусловно, можем уменьшить их влияние на процесс загрузки.
Хороший показатель для измерения количества вызова скриптов в течение запуска системы - это PID процесса, который он получает сразу после запуска системы. Загрузитесь, войдите в систему, откройте терминал и введите echo $$. Попробуйте сделать это на вашей Linux-системе, а затем сравните результат с MacOS! (Подсказка: на Linux PID будет порядка 1823; на MacOS примерный PID - 154).

Слежение за процессами


Центральная часть системы инициализации - это выполнение обязанностей няни по отношению к сервисам: она должна следить за ними. Перезапускать их, если они завершают свою работу. Если они падают, нужно собирать всю необходимую информацию о них и сохранить ее для администратора, предоставлять ее аварийным системам сбора информации (crash dump systems) и системам ведения журнала (например syslog) и/или системе аудита.
Также она должна позволять полностью завершать сервис со всеми своими потомками. Это, наверное, проще сказать, чем сделать. Традиционно в Unix процесс, который дважды вызывает fork(), может избежать контроля своего родителя, и родитель ничего не узнает об отношении нового процесса к тому, что он запустил. Например: неправильно написанный CGI скрипт, который вызван двойным fork(), не прерывает свою работы при завершении Apache. Кроме того, вы даже не сможете выяснить его отношение к Apache, если не знаете его имя и назначение.
Итак, как же мы сможем уследить за процессами, чтобы они не убежали от нашей няни, и  контролировать их как единое целое, даже если они форкаются огромное количество раз (в оригинале gazilion times :) - прим. перев.) ?
Разные люди предлагают различные решения. Скажу вкратце, что есть подходы, основанные на использовании интерфейса ptrace или netlink (интерфейс ядра, который позволяет получить сообщение netlink каждый раз, когда любой процесс в системе вызывает fork() или exit()), которые подвергались критике за топорность, неповоротливость и плохую масштабируемость.
А что же мы можем предложить? В ядре уже достаточно долго существует такая вещь, как Control Groups (aka "cgroups"). В основном они позволяют создавать иерархию групп процессов, которая непосредственно доступна через виртуальную файловую систему. Названия групп  - это обычно названия директорий в этой файловой системе. Если процесс, принадлежащий к определенной группе, вызывает fork(), его потомок становится членом той же группы. Если он не имеет привилегированного статуса, он никак не сможет этого избежать. Первоначально cgroups были добавлены в ядро для организации контейнеров: различные подсистемы ядра могут устанавливать лимиты на ресурсы (ограничение использования процессора и/или памяти) для определенных групп. Традиционные ограничения ресурсов (как реализовано setrlimit()) устанавливаются (в основном) только для каждого процесса. cgroups, с другой стороны, позволяют устанавливать ограничение на целые группы процессов. cgroups полезны также для обеспечения соблюдения ограничений в других случаях. Вы можете использовать его, например, для ограничений общего объема памяти или ресурсов процессора для Apache и всех его дочерних процессов. Поэтому неправильный CGI-скрипт не сможет сбежать от установленных ограничений ресурсов путем вызова fork().
В дополнение к своим функциям по созданию контейнеров и использованию для ограничения ресурсов cgroups являются очень полезными как средство для слежения за демонами: членство в cgroup надежно наследуется дочерними процессами и избежать этого невозможно. Существует система уведомлений, которая ставит в известность главный процесс, когда cgroup становится пустой. Вы можете найти cgroups для процесса путем чтения файла /proc/$PID/cgroup, следовательно, это очень хороший выбор средства для отслеживания процессов.

Контроль за средой исполнения процесса

Хорошая няня должна не только следить за демонами и контролировать их, но и создавать для них хорошее и безопасное окружение. Это означает, что мы не ограничиваемся очевидными параметрами, такими как настройка ограничений ресурсов для процесса посредством setrlimit(), идентификаторы пользователя и групп. Ядро Linux дает пользователям и администраторам достаточно контроля над процессами (некоторые из них в настоящее время используются редко).
Для каждого процесса нам нужно устанавливать контроль над использованием процессора, планировщика ввода-вывода, набор функциональных ограничений, привязку к процессору и, конечно же, дополнительные ограничения посредством cgroup. Самое главное здесь - это высокоуровневый контроль, такой как, например, монтирование файловой системы в режиме только чтения при вызове mount с опцией bind. Таким образом, можно запускать отдельные демоны так, что все (или некоторые) файловые системы будут ему доступны только для чтения. Это можно использовать для контроля за тем, что делают отдельные демоны, нечто вроде бюджетного варианта SELinux (хотя это, конечно, не призвано заменить SELinux).
Наконец, ведение логов является важной составной частью запуска сервисов: в идеале каждый бит информации, выдаваемый сервисом, должен записываться в журнал.  Следовательно, система инициализации должна обеспечить процесс ведения логов для демонов, с которыми она работает, подключая стандартный вывод (stdout) и вывод ошибок (stderr) к демону syslog. А иногда даже к /dev/kmsg, который во многих случаях очень полезная замена syslog (те, кто делает встроенные системы, прислушайтесь, пожалуйста, к этому!).

Upstart, внимание, марш!

Во-первых, я бы хотел подчеркнуть, что мне на самом деле нравится код Upstart, он очень хорошо комментирован и его легко изучать. Другим проектам в этом отношении еще учиться и учиться (в том числе и моим собственным). В то же время я не могу согласится с общим подходом, который реализован в Upstart. Но сначала несколько слов о проекте.
Upstart не разделяет код с sysvinit, а его функциональные возможности перекрывают возможности sysvinit, и обеспечивает совместимость с какой-то частью хорошо известных скриптов SysV. Его основная особенность - управление сервисами на основе событий: запуск и остановка процессов связаны с "событием", которое происходит в системе, где "событием" может быть все что угодно: доступность сетевых интерфейсов, запуск какого-то ПО и т.п.
Upstart обрабатывает последовательность запуска служб посредством событий: если срабатывает событие syslog-started - оно используется как индикатор для запуска D-Bus, т. к. шина теперь сможет использовать syslog. А затем, когда срабатывает событие dbus-started, запускается NetworkManager, после чего он может использовать D-Bus, и так далее.
Можно сказать, что логическое дерево зависимостей, которое существует и хорошо понимается администратором или разработчиком, транслируется в систему событий и набор правил, описывающих реакции на события: каждое логическое «для a нужно b» становится «запустить а, когда нужно b» плюс «остановить а, когда останавливается b». Это своего рода упрощение, особенно для кода самого Upstart. Тем не менее я утверждаю, что такое упрощение на самом деле вредно. Прежде всего, система логических зависимостей никуда не девается, и тот, кто пишет файлы для Upstart, теперь должен транслировать эти правила в формате «события/действия» (два правила для каждой зависимости). Таким образом, вместо того, чтобы позволить компьютеру выяснить, что нужно делать на основе зависимостей, пользователь должен вручную перевести зависимости в простое правило «событие - реакция на него». Кроме того, поскольку информация о зависимостях не может быть декодирована, она не доступна во время работы системы, фактически это означает, что у администратора, который пытается разобраться, почему что-то произошло, нет на это никаких шансов.
Кроме того, в случае Upstart логика работы с зависимостями переворачивается с ног на голову. Вместо того чтобы свести к минимуму объем работы (что является признаком и целью хорошей системы инициализации), на самом деле происходит увеличение количества работы, выполняемой во время операций. Другими словами, вместо того, чтобы имея четко поставленную цель, проделать все необходимые шаги сразу, она делает сначала один шаг, а потом делает все остальные шаги, которые к ней ведут. Или еще проще: то, что пользователь запустил D-Bus, ни в коей мере не означает, что NetworkManager должен быть также запущен (но это то, что будет делать Upstart). А должно быть с точностью до наоборот: когда пользователь обращается к NetworkManager, это признак того, что D-Bus также должна быть запущена (это, безусловно, именно то, что ожидают большинство пользователей, не так ли?). Хорошая система инициализации должна запускать только то, что нужно, и по требованию. Либо по потребности, либо максимально параллелизуя запуск. Однако она не должна запускать больше, чем необходимо, в частности не все из установленного, для чего может понадобится этот сервис
(Честно говоря, и это только мое мнение, без претензии на истинность, что тут автор пытается притянуть за уши аргументы против Upstart. Тем более, что приведенные автором примеры относительно D-Bus и NetworkManager не соответствуют реальности. Прим. перев.).
Наконец я не вижу реальной ценности от логики, основанной на событиях. Мне кажется, что большинство событий, обрабатываемых Upstart, фактически не мгновенны, а имеют некоторую продолжительность: сервис запускается, работает и останавливается. Устройство подключается, подключено и отключается. Точка монтирования находится в состоянии монтирования, полностью смонтирована, или же она размонтирована. Кабель питания подключен, система работает от сети переменного тока, кабель питания отключен. Лишь немногие из событий системы инициализации должны обрабатываться точно в срок, большая часть из них - это старт, запуск и остановка. Информации об этом также нет в Upstart.
Мне известно, что некоторые проблемы, на которые я указал выше, в некоторой степени решаются последними изменениями в конфигурационных правилах Upstart, такими как, например, start on (local-filesystems and net-device-up IFACE=lo). Тем не менее, я считаю, что это выглядит как попытка исправить систему (автор считает, что такое решение - «костыль» - прим. перев.), основной дизайн которой является некорректной.
Кроме того, Upstart вполне подходит на роль няни для демонов, хотя что-то она делает сомнительными способами (см. выше).
Есть и другие системы инициализации, кроме sysvinit, Upstart и launchd. В большинстве из них подходы намного серьезнее, чем в Upstart или sysvinit. Наиболее интересной является Solaris SMF, которая поддерживает надлежащую обработку зависимостей между службами. Тем не менее, во многих отношениях она чрезмерно усложнена и, скажем так, несколько академична, чрезмерно использует XML и новые термины для известных вещей. Она также сильно завязана на специфичных для Solaris функциями, такими как contract system.



9 комментариев:

  1. Отлично!
    Большое спасибо за перевод. Сам не осилил прочитать на английском.

    ОтветитьУдалить
  2. Не за что. Наверное, завтра выложу вторую часть статьи.

    ОтветитьУдалить
  3. Спасибо огромное, за ваш труд.

    ОтветитьУдалить
  4. спасибо большое!

    ОтветитьУдалить
  5. Перевод оличный, статья очень интересная и читается на 1 дыхании! Кстати, небольшая опечаточка: "ему сокет ему"

    ОтветитьУдалить
  6. Опечатку исправил, Вам тоже спасибо!
    Всем тоже спасибо, за положительные отзывы, которые говорят о том, что мои публикации хоть кто-то читает и они интересны :). Надеюсь, что редкое наличие свободного времени никак не скажется на качестве будущих переводов ;).

    P.S. Очень надеялся получить инвайт на Хабр этой публикацией, но, видимо, не судьба. :)

    ОтветитьУдалить
  7. "Она также сильно завязана на специфичных для Solaris функциями, такими как contract system."
    "специфичных функциями"?
    Я думаю это должно звучать так:
    "Она также сильно завязана на специфичных для Solaris функциях, таких как contract system."

    ОтветитьУдалить
  8. Большое спасибо за данную статью

    ОтветитьУдалить