Запуск и останов в Mac OS демонов UNIX


Если вы хорошо знакомы с тем, как в UNIX и Linux происходит запуск и останов демонов (daemons), то возможно, вас удивляет, почему в Mac OS X нет ничего, хотя бы отдаленно напоминающего каталог init.d. Хотя в Mac OS X 10.4 появилось замечательное и мощное средство для запуска и останова демонов, launchd, существуют веские причины продолжать использовать подход, применявшийся в Mac OS X 10.3 и более ранних версиях: StartupItems.Причина этого заключается в том, что многие программы UNIX, которые можно найти в Интернете (или, например, разработать самостоятельно), продолжают использовать старый подход, принятый в мире UNIX. При использовании этого метода вероятны конфликты с launchd. Например, man-страница launchd.plist(5) содержит явное предупреждение о возможности возникновения проблем при использовании launchd с приложениями, которые вызывают демонов или ведут себя точно так же, как демоны (например, запускают подпрограмму и завершают свою работу).

Более того, при использовании launchd нежелательно выполнять следующие действия:

  • Создавать идентификатор пользователя или группы (user ID или group ID).
  • Создавать рабочий каталог.
  • Выполнять смену корневого каталога — chroot(2).
  • Создавать сеансы и устанавливать идентификаторы группы — setsid(2).
  • Закрывать «висячие» дескрипторы файлов.
  • Перенаправлять stdio(3) на /dev/null.
  • Устанавливать лимиты на ресурсы с помощью setrusage(2).
  • Устанавливать приоритеты с помощью setpriority(2).
  • Игнорировать сигнал для запроса завершения процесса SIGTERM.

Многие демоны UNIX можно модифицировать таким образом, чтобы они корректно вели себя под launchd. Если вы внимательно ознакомитесь с исходным кодом Darwin, который доступен по адресу http://www.opensource.apple.com/darwinsource/, то вы обнаружите патчи под launchd для многих демонов UNIX, например, OpenSSH и cron. В листинге приводится фрагмент исходного кода Apple для файла cron.c, благодаря которому поведение демона не вступает в противоречие с launchd.

12345678// Фрагмент исходного кода Apple для файла cron.c// (патч для launchd)#ifdef __APPLE__/* Don’t daemonize when run by launchd */if (getppid() != 1 && daemon(1, 0) == -1) {#elseif (daemon(1, 0) == -1) {#endif

Вероятно, со временем вы станете свидетелями того, как в официальные релизы наиболее популярных пакетов Open Source будут включены патчи Apple. Поэтому если вы внимательно прочли man-страницы для launchd(8) и launchd.plist(5), а также абсолютно уверены в том, что ваш демон действительно будет «играть» по правилам, установленным Apple, то смело используйте launchd. Более подробную информацию о launchd можно найти в статье «Creating launchd Daemons and Agent», которую можно найти здесь: http://tinyurl.com/27l9mg.

Однако в использовании StartupItems есть и еще одно преимущество. Хотя launchd и «знает», как «убить» (kill) процесс, вы, тем не менее, не можете явно указать процедуру останова (shutdown routine), которая должна быть запущена при выключении питания системы. А вот StartupItems может решить этот вопрос.


Рассмотрим, например, сервер базы данных MySQL: чтобы запустить его, вы используете программу mysqld_safe, которая и выполняет запуск сервера базы данных MySQL. Однако чтобы остановить сервер базы данных, вы даете команду mysqladmin shutdown. Если вы для запуска и остановка сервера MySQL пользуетесь launchd, то останов произойдет довольно неэлегантно. А вот если вы воспользуетесь StartupItems, вы сможете определить, как именно должен происходить останов процесса.

Запускаемый элемент (startup item) управляется тремя компонентами: папкой (например, /Library/StartupItems/MyItem), одноименным с папкой командным сценарием (в данном случае — MyItem), и файлом списка свойств, который называется StartupParameters.plist. Командный сценарий и список свойств должны присутствовать на верхнем уровне иерархии папки запускаемого элемента. Кроме того, вы можете создать каталог Resources для хранения локализованных ресурсов (хотя делать это необязательно).

Таким образом, чтобы задать запускаемый элемент MySQL, действуя от имени учетной записи root, создайте каталог /Library/StartupItems/MySQL. Затем в этом каталоге создайте два файла: сценарий, исполняющийся при запуске MySQL, и список свойств с именем StartupParameters.plist. Сценарий, исполняющийся при запуске, должен быть исполняемым файлом.

12345// Создание запускаемого элемента MySQL$ sudo mkdir /Library/StartupItems/MySQL$ sudo touch /Library/StartupItems/MySQL/MySQL$ sudo touch /Library/StartupItems/MySQL/StartupParameters.plist$ sudo chmod +x /Library/StartupItems/MySQL/MySQL

После того как в соответствующие разделы этих файлов будет внесена необходимая и корректная информация, MySQL будет запускаться каждый раз при загрузке системы. Воспользуйтесь вашим предпочтительным текстовым редактором для внесения в эти файлы необходимой информации. Поскольку владельцем обоих файлов является root, вам потребуется пройти процедуру аутентификации для получения к ним доступа. Текстовые редакторы Smultron и TextMate позволяют вам аутентифицироваться и начать редактировать файлы. Если вы предпочитаете использовать vi из командной строки Terminal, вам потребуется воспользоваться командой sudo, введя из командной строки следующую команду.

1sudo vi /Library/ StartupItems/MySQL/MySQL.

Сценарий, исполняемый при запуске
Сценарий, исполняемый при запуске, должен представлять собой командный сценарий оболочки, содержащий функции StartService(), StopService() и RestartService(). Содержимое файла /Library/StartupItems/MySQL/MySQL показано в листинге ниже. Функциональный вызов в самом конце сценария инициирует функцию RunService() из файла /etc/rc.common (этот файл является частью Mac OS X), которая, в свою очередь, инициирует StartService(), StopService() или RestartService(), в зависимости от того, с каким аргументом инициировался скрипт — start, stop или restart.

123456789101112131415161718192021222324252627282930313233// Содержимое файла /Library/StartupItems/MySQL/MySQL// для запуска MySQL#!/bin/sh# Source common setup, including hostconfig.#. /etc/rc.commonStartService(){# Don’t start unless MySQL is enabled in /etc/hostconfigif [ «${MYSQL:=-NO-}» = «-YES-» ]; thenConsoleMessage «Starting MySQL»/usr/local/mysql/bin/mysqld_safe —user=mysql &fi}StopService(){ConsoleMessage «Stopping MySQL»# If you’ve set a root password within mysql, you may# need to add —password=password on the next line./usr/local/mysql/bin/mysqladmin shutdown}RestartService(){# Don’t restart unless MySQL is enabled in /etc/hostconfigif [ «${MYSQL:=-NO-}» = «-YES-» ]; thenConsoleMessage «Restarting MySQL»StopServiceStartServiceelseStopServicefi}RunService «$1»

Поскольку скрипт проверяет значение переменной окружения $MYSQL, он не будет производить никаких действий до тех пор, пока вы не активизируете MySQL в файле /etc/hostconfig. Чтобы сделать это, откройте файл /etc/hostconfig текстовым редактором и добавьте в него следующую строку:

1SQL=-YESMac

OS X не распознает никаких специальных связей между записями в файле hostconfig и пусковыми сценариями. Вместо этого сценарий запуска обращается к файлу /etc/rc.common, который, в свою очередь, ссылается на файл hostconfig. Директивы в файле hostconfig представляют собой просто переменные окружения, и сценарий запуска проверяет значения переменных, управляющих его поведением (в рассматриваемом примере — это переменная окружения $MYSQL).

Список свойств

Список свойств (StartupParameters.plist) может иметь формат XML или NeXT. Он содержит атрибуты, описывающие элемент, и определяют его место в последовательности запуска. Формат NeXT использует списки свойств в стиле NeXTSTEP (листинг 4.19).

1234567// Список свойств в формате NeXT{Description = «MySQL»;Provides = («MySQL»);Requires = («Network»);OrderPreference = «Late»;}

Список свойств формата XML следует правилам языка определения типа документа (Document Type Definition, DTD22) — PropertyList.dtd. Для редактирования этого списка свойств можно использовать любой предпочитаемый вами текстовый редактор или специализированное приложение Property List Editor (которое можно найти в папке /Developer/Applications/Utilities). Пример списка свойств в формате XML приведен в листинге.

1234567891011121314151617181920// Пример списка свойств в формате XML<?xml version=»1.0″ encoding=»UTF-8″?><!DOCTYPE plistSYSTEM «file://localhost/System/Library/DTDs/PropertyList.dtd»><plist version=»0.9″><dict><key>Description</key><string>MySQL</string><key>Provides</key><array><string>MySQL</string></array><key>Requires</key><array><string>Network</string></array><key>OrderPreference</key><string>Late</string></dict></plist>

Рассмотрим ключевые значения, которые можно использовать в списке свойств:

  • Description — это слово или фраза, содержащие описание элемента.
  • Provides — это массив сервисов, предоставляемых запускаемым элементом (например, Apache представляет собой Web-сервер). Эти сервисы должны быть глобально уникальными. В случае, если при запуске будут обнаружены два запускаемых элемента, предоставляющих один и тот же сервис, то запущен будет тот из них, который будет обнаружен первым.
  • Requires — это массив сервисов, от которых зависит запускаемый элемент. Он должен соответствовать атрибуту Provides того сервиса, от которого он зависит. Если требуемый сервис не может быть запущен, то не будет запущен и зависимый сервис.
  • Uses — данный атрибут аналогичен атрибуту Requires, но представляет собой более слабую ассоциацию. Если при запуске системы соответствующий сервис будет обнаружен, то он будет запущен. В противном случае попытка запуска зависимого сервиса все равно будет предпринята.
  • OrderPreference — атрибуты Requires и Uses подразумевают определенный порядок, в котором должны запускаться зависимые элементы после запуска сервисов, от которых они зависят. Вы можете указать следующие значения этого атрибута: First, Early, None (по умолчанию), Late или Last. Программа SystemStarter23 по возможности стремится удовлетворить эти предпочтения, но порядок зависимостей имеет приоритет.

Теперь вы сможете вручную запускать, перезапускать и останавливать MySQL из командной строки.

12345// Ручной запуск, перезапуск и останов MySQLс помощью SystemStarter$ sudo SystemStarter start MySQL$ sudo SystemStarter restart MySQL$ sudo SystemStarter stop MySQL