Техническое примечание TN2083

Демоны и агенты

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

Этот technote описывает наиболее распространенные проблемы, с которыми встречаются при разработке демонов и агентов на Mac OS X, и дает подробный совет при решении тех проблем.

Необходимо считать этот technote при разработке фоновой программы для Mac OS X. Необходимо также считать этот technote при разработке плагина, это размещается фоновой программой (например, фильтр CUPS), потому что плагин должен соблюдать правила среды узла.

Введение
Daemonomicon
Контексты выполнения
Многоуровневые платформы
Конструктивные соображения
Быть запущенным
Демон рекомендации IPC
Кодирование рекомендаций
Полезные советы
Дополнительные материалы для чтения
Старые системы и технология
История версии документа

Введение

Много проблем могут быть решены программами, работающими в фоновом режиме. Эти программы не имеют никакого графического интерфейса пользователя (GUI), и только взаимодействуют с пользователем косвенно. Например:

Этот technote обсуждает вопросы, связанные с записью программ, работающих в фоновом режиме. Это запускается с формального определения типов фоновых программ, которые можно записать (Daemonomicon). Это тогда продолжает обсуждать необычные контексты выполнения, с которыми встречаются фоновые программы (Контексты выполнения) и как Mac OS X использует многоуровневые платформы для решения вопросов, повышенных этими контекстами (Многоуровневые Платформы). Это также дает совет о том, как создать фоновую программу, начиная с некоторых общих рекомендаций по проектированию (Конструктивные соображения), сопровождаемые уведомлением относительно запуска фоновой программы (Запускаемый) и как связаться между различными компонентами программы (Демон Рекомендации IPC). Наконец, существуют некоторые определенные рекомендации кодирования (Кодирующий Рекомендации) и некоторые разные полезные советы (Полезные советы).

Прежде, чем считать этот technote, необходимо смотреть на официальную документацию для Системного Программирования Запуска и Многопользовательских Сред.

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

Примеры в этом technote от Mac OS X 10.5.

Daemonomicon

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

Фоновая программа является программой, работающей в фоновом режиме, не представляя значительного GUI. Эта категория подразделена на демонов (фоновые программы в масштабе всей системы) и агенты (которые работают от имени определенного пользователя). Следующие два раздела описывают эти подразделения подробно.

Демоны

Демон является программой, работающей в фоновом режиме как часть полной системы (т.е. это не связывается к определенному пользователю). Демон не может вывести на экран GUI; более в частности не позволяется соединиться с сервером окна. Веб-сервер является совершенным примером демона.

Исторически, Mac OS X имел много различных способов запустить демонов (для подробных данных, посмотрите Осуждаемый Daemonomicon). На существующих системах существует, только один рекомендует путь: launchd.

launchd демон сконфигурирован сложным файлом списка свойств. Этот файл позволяет демону быть запущенным на основе множества критериев (соединения с сокетами слушания, элементы, изменяемые в файловой системе, периодически, и т.д.). Для получения дополнительной информации см. launchd.plist страницу справочника.

Третье лицо launchd демон должно быть установлено путем добавления файла списка свойств к /Library/LaunchDaemons каталог.

Важно понять, что не все launchd демоны взаимодействуют с launchd таким же образом. Единственная функция объединения всех launchd демонов является файлом списка свойств. Это говорит launchd как взаимодействовать с демоном. Например:

  • Launchd-осведомленный launchd демон обычно запускается по требованию и должен явно зарегистрироваться с launchd. Для примера этого посмотрите 'Выбранный' Пример кода.

  • Если у Вас есть демон, ранее запущенный через inetd (или xinetd), можно выполнить его от launchd путем создания файла списка свойств с inetdCompatibility набор свойств соответственно. Это приводит к inetd-совместимому launchd демону. Для примера этого проверить /System/Library/LaunchDaemons/finger.plist.

  • Фактически любая программа может быть выполнена как launchd демон путем создания минимального файла списка свойств с OnDemand набор свойств ко лжи.

Следует иметь в виду, что, независимо от того, как это взаимодействует с launchd, launchd демон не должен сам daemonize; выполнение так подорвало бы launchdвозможность контролировать демона.

Для получения дополнительной информации о launchd демонах, считайте launchd документацию.

Агенты

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

  • должен быть экземпляр программы на сеанс входа в систему GUI

  • каждый агент имеет доступ к календарю того пользователя

  • когда определенные события имеют место, агент может запустить календарное приложение

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

Агенты получили множество различных имен за эти годы. Они включают приложения только для фона (BOAs), безликие приложения только для фона (FBAs) и элементы UI (допущение, что агент выводит на экран некоторый GUI, но не является полноценным приложением со строкой меню). Эти имена более или менее не важны этому обсуждению. То, что релевантно, и что отличает различные типы агентов, то, как запускается агент.

Элемент входа в систему

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

Можно установить элемент входа в систему с помощью интерфейса списков совместно используемого файла для Launch Services (см. LSSharedFileList.h заголовочный файл в подплатформе LaunchServices платформы CoreServices). Этот API доступен на Mac OS X 10.5 и позже. В более ранних системах можно установить элемент входа в систему, путем отправки событий Apple в System Events процесс. Пример кода 'LoginItemsAE' показывает один способ сделать это.

Глобальный элемент входа в систему

Глобальный элемент входа в систему является элементом входа в систему, запускающимся, когда любой пользователь входит в систему. Установка глобального элемента входа в систему примерно эквивалентна установке того элемента входа в систему для всех пользователей в системе: каждый раз пользователь входит в систему, loginwindow запуски, что элементы входа в систему пользователя и все глобальные элементы входа в систему.

В Mac OS X 10.5 и позже можно установить глобальный элемент входа в систему с помощью интерфейса списков совместно используемого файла для Launch Services.

Агент launchd

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

Третье лицо launchd агент должно быть установлено путем добавления файла списка свойств к ~/Library/LaunchAgents каталог (чтобы быть вызванным только для этого пользователя) или /Library/LaunchAgents каталог (чтобы быть вызванным для всех пользователей).

агенты launchd далее классифицируются их целевым типом сеанса, как показано в Таблице 1.

Табличные 1  Типы launchd агента

Имя

Тип сеанса

Примечания

GUI launchd агент

Вода

Имеет доступ ко всем службам GUI; во многом как элемент входа в систему.

не-GUI launchd агент

StandardIO

Выполнения только в сеансах входа в систему не-GUI (прежде всего, сеансах входа в систему SSH)

launchd агент в расчете на пользователя

Фон

Выполнения в контексте это - родитель всех контекстов для данного пользователя

предварительно войдите в систему launchd агент

LoginWindow

Выполнения в loginwindow контексте

Для выполнения агента в определенном типе сеанса используйте строки типа сеанса от Таблицы 1 как значение LimitLoadToSessionType свойство в файле списка свойств Вашего агента. Если Вы хотите работать больше чем в одном типе сеанса, можно установить LimitLoadToSessionType к массиву, где каждый элемент является строкой типа сеанса. Если Вы не указываете LimitLoadToSessionType свойство, launchd принимает значение Aqua.

Если Вы устанавливаете LimitLoadToSessionType к массиву, знать, что каждый экземпляр Вашего агента работает независимо. Например, если Вы устанавливаете свой агент для выполнения в LoginWindow и Aqua, система будет первый показ экземпляр Вашего агента в loginwindow контексте. Когда пользователь войдет в систему, тот экземпляр будет завершен, и второй экземпляр запустится в стандартном контексте GUI.

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

Древние демоны (и агенты)

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

Контексты выполнения

Большинство программистов Mac OS X знакомо с идентификатором пользователя (UID), связанный с процессом (обычно называемый владельцем процесса). В традиционной системе BSD этот UID управляет возможностями того процесса. Можно, более или менее, предположить, что два процесса с соответствием UIDs имеют те же возможности.

Это не истина на Mac OS X. Существуют другие элементы контекста процесса, значительно изменяющие его возможности. Так, например, демон, UID которого установлен в того из зарегистрированного пользователя консоли, не эквивалентен приложению, запущенному тем пользователем.

Следующие разделы описывают элементы контекста процесса, и как они влияют на фоновые программы.

UIDs и GIDs

UIDs процесса (его эффективное (EUID), реальный (RUID), и сохраненный (SUID) UIDs), являются самыми известными элементами контекста процесса. Эти UIDs управляют различными возможностями процесса, главным образом центрируемого на частях BSD системы (файловая система, сети, управление процессом BSD). Например, возможностью процесса открыть файл управляют его EUID и его возможность сигнализировать, что другим процессом управляют его EUID и EUID и RUID цели.

Процессы также имеют ряд группы IDs (GIDs), походящий на UIDs плюс список дополнительной группы IDs.

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

Основы начальной загрузки Маха

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

Для понимания то, как это работает можно выполнить launchctl с bslist параметр. Это перечисляет все службы, зарегистрированные в службе начальной загрузки. Перечисление 1 показывает пример своего вывода.

Перечисление 1  BootstrapDump от терминала

$ launchctl bslist
D  com.apple.rcd
A  com.apple.finder.ServiceProvider
A  com.apple.systemuiserver.ServiceProvider
A  com.apple.systemuiserver-ServicesPortName
A  com.apple.dockling.server
A  com.apple.SUISMessaging
A  com.apple.ipodserver
A  com.apple.dock.server
D  BezelUI
D  edu.mit.Kerberos.KerberosAgent.ipcService
[...]

Как Вы видите, существуют многочисленные службы, опубликованные через службу начальной загрузки. Фактически все они являются частными. Например, Вы, как ожидают, не отправите сообщения непосредственно в «com.apple.dock.server» службу; скорее Вы вызвали бы подпрограмму, экспортируемую платформой (возможно, SetApplicationDockTileImage) и, негласно, это обменивается сообщениями со службой для выполнения работы, которую Вы запросили.

Пространства имен начальной загрузки

Предыдущий пример поднимает интересный вопрос: который делает Прикрепление SetApplicationDockTileImage взаимодействовать с? Если существуют зарегистрированные многочисленные пользователи (через быстрое переключение между пользователями), то существуют многократные экземпляры выполнения Прикрепления, поэтому как делает SetApplicationDockTileImage знайте, который является правильным? Кроме того, как может все эти Прикрепления регистрировать то же имя службы («com.apple.dock.server»), не перенося конфликт имен?

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

Стоит отметить, что различие между GUI и не-GUI на сеанс загружает пространства имен. GUI на сеанс загружается, пространство имен инстанцирует инфраструктура GUI (loginwindow и WindowServer) когда пользователь регистрирует на пути GUI. Не-GUI на сеанс загружается, пространство имен создается, когда пользователь регистрирует на пути SSH. В то время как нет никакого принципиального различия между этими пространствами имен, основанными на GUI службами, как Прикрепление, только зарегистрируйте себя в GUI, на сеанс загружают пространства имен.

В Mac OS X 10.5 и позже эти пространства имен не-GUI, после того, как пользователь вошел в систему, переместил ниже пространства имен начальной загрузки в расчете на пользователя launchd PAM модуль (/usr/lib/pam/pam_launchd.so) под направлением SSH PAM конфигурационный файл (/etc/pam.d/sshd).

Иерархия пространства имен

Пространства имен начальной загрузки располагаются иерархически. Существует единственное глобальное пространство имен начальной загрузки. Ниже этого пространство имен начальной загрузки в расчете на пользователя для каждого пользователя в системе, и ниже каждого из тех пространство имен начальной загрузки на сеанс для каждого сеанса входа в систему. Можно распечатать карту пространств имен в системе с помощью BootstrapDump программа (загружаемый как Пример кода 'BootstrapDump') с MAP параметр, как проиллюстрировано Перечислением 2.

  Иерархия пространства имен начальной загрузки перечисления 2

$ sudo BootstrapDump MAP
Password:********
0x0
  launchd (root, 1/0)
  launchd (quinn, 82/1)
0x30b
  kextd (root, 7/1)
  DirectoryService (root, 8/1)
  notifyd (root, 9/1)
  configd (root, 10/1)
  [...]
  0x10f
    0x100b
      sshd (root, 443/1)
      sshd (quinn, 447/443)
      bash (quinn, 449/447)
      BootstrapDump (root, 477/449)
    0x20b
      loginwindow (root, 24/1)
      ARDAgent (root, 92/82)
      Spotlight (quinn, 94/82)
      UserEventAgent (quinn, 95/82)
      Dock (quinn, 97/82)
      AppleVNCServer (root, 98/92)
      pboard (quinn, 99/82)
      SystemUIServer (quinn, 100/82)
      Finder (quinn, 101/82)
      ATSServer (quinn, 102/82)

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

  • процессы с помощью глобального пространства имен могут только видеть службы в глобальном пространстве имен

  • процессы с помощью пространства имен в расчете на пользователя видят службы в том пространстве имен в расчете на пользователя и глобальном пространстве имен

  • процессы с помощью пространства имен на сеанс видят службы в том пространстве имен на сеанс, родительском пространстве имен в расчете на пользователя и глобальном пространстве имен

  • службы, зарегистрированные в пространстве имен на сеанс, могут только быть замечены процессами с помощью того пространства имен на сеанс

  • службы, зарегистрированные в пространстве имен в расчете на пользователя, могут быть замечены любым процессом в любом из сеансов того пользователя

  • службы, зарегистрированные в глобальном пространстве имен, могут быть замечены всеми процессами

Можно использовать launchctl bslist команда для просмотра пространства имен начальной загрузки путем указания на него на процесс с помощью того пространства имен. Например, Перечисление 3 показывает, как перечислить службы в глобальном пространстве имен путем предназначения kextd.

Перечисление 3  , Выводящее глобальное пространство имен начальной загрузки

$ # The killall goop in the following command is a sneaky way 
$ # to get the process ID for a process name.
$ #
$ pid=`sudo killall -s kextd | cut -f 3 -d ' '`
$ sudo launchctl bslist $pid
[...]
A  com.apple.KernelExtensionServer
D  com.apple.KerberosAutoConfig
D  com.apple.java.updateSharingD
D  com.apple.installdb.system
D  com.apple.IIDCAssistant
D  com.apple.system.hdiejectd
D  com.apple.gssd
A  com.apple.FSEvents
[...]
$ sudo launchctl bslist $pid | grep dock
$ # No results because Dock is not registered in the global namespace.

Так «com.apple. KernelExtensionServer» служба регистрируется в глобальном пространстве имен начальной загрузки и может быть замечен всеми процессами в системе. С другой стороны, «com.apple.dock.server» служба (от Перечисления 1) регистрируется в пространстве имен на сеанс и может только быть замечен процессами с помощью того пространства имен.

Так, правила помнить:

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

  • При разработке демона удостоверьтесь, что это наследовало ссылку на глобальное пространство имен начальной загрузки.

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

Если Вы не соблюдаете эти правила, вещи могли бы быть похожи, что они работают, но Вы столкнетесь с неясными проблемами по линии. Следующий раздел дает пример одной такой проблемы. Это - всего лишь один пример; существует много более потенциальных проблем. Единственный гарантируемый способ избежать таких проблем состоит в том, чтобы соблюсти эти правила, обсужденные далее в Многоуровневых Платформах.

Исследование пространства имен

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

  1. Монитор действия запуска (в /Applications/Utilities) от Средства поиска.

  2. Если это в настоящее время не показывает свое главное окно, выберите Activity Monitor из Меню окна для раскрытия того окна.

  3. Конфигурирование его для показа метра использования CPU в его значке панелей путем выбора Show CPU Usage из подменю Dock Icon меню View. Вы отметите, что Значок панелей теперь выводит на экран актуальное представление использования CPU, и что меню Dock работает правильно. Рисунок 1 показывает то, о чем я говорю.

  4. Теперь Монитор Действия выхода.

  5. Затем, откройте Окно терминала и SSH себе, как показано в Перечислении 4.



    Перечисление 4  , Соединяющееся с собой через ssh

    $ ssh localhost Password:******** Last login: Fri Sep 30 10:07:59 2005 Welcome to Darwin! $

    Когда Вы регистрируете на пути ssh, система создает новое пространство имен начальной загрузки на сеанс для Вашего сеанса. Так, что-либо, что Вы выполняете в этом Окне терминала, работает в различном пространстве имен начальной загрузки на сеанс из приложений GUI, которые Вы запускаете.

  6. Теперь выполненный Монитор Действия из этого Окна терминала, как показано в Перечислении 5.



    Перечисление 5  , Выполняющее Монитор Действия из сеанса входа в систему SSH

    $ /Applications/Utilities/Activity\ Monitor.app/Contents/MacOS/Activity\ Monitor

    Рисунок 2 показывает то, что Вы получаете. Вы заметите две аномальных вещи:

    • Мозаика Прикрепления метр CPU не выведена на экран.

    • Если Вы управляете, щелкают по мозаике Прикрепления, контекстное меню содержит «Приложение, Не Отвечающее» элемент.

    Эти проблемы происходят, потому что приложение работает в различном пространстве имен начальной загрузки на сеанс и неспособно искать службы, которыми оно должно управлять правильно (например, «com.apple.dock.server»).

  Присутствие Монитора Действия рисунка 1 в
  Мониторе действия DockFigure 2 запустилось от сеанса SSH

Сервер окна

Сервер окна (на Mac OS X 10.5 это /System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/Resources/WindowServer) единственная точка контакта для всех приложений. Это является центральным к реализации платформ GUI (AppKit и HIToolbox) и много других служб (например, Диспетчер процессов).

Большинство услуг, предоставленных сервером окна, реализовано с помощью сообщений Маха. Таким образом, для использования сервера окна надежно необходимо наследоваться, ссылка на допустимый GUI на сеанс загружают пространство имен. Это - ожидаемое последствие правил, данных ранее.

То, что неожиданно, однако, то, что приложения выполняют работу (несколько) при выполнении их от за пределами сеанса входа в систему GUI (т.е. если они наследовали ссылку на глобальное пространство имен начальной загрузки или некоторое другое пространство имен начальной загрузки не-GUI). Это вызвано тем, что сервер окна распространяет свои службы в глобальном пространстве имен начальной загрузки! Это известно как глобальная серверная служба окна.

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

Больше, чем сервер окна

Сервер окна не является единственной службой, это требуется для приложения функционировать правильно. Как описано ранее, служба Dock также требуется, и она только регистрируется в GUI, на сеанс загружают пространства имен. Существует много других служб как это.

Разрешение соединиться

Пользователь консоли является пользователем, сеанс входа в систему GUI которого использует консоль. Консольное устройство, /dev/console, принадлежит пользователю консоли.

Процесс может только использовать глобальную серверную службу окна, если ее EUID 0 (это работает как корень), или соответствует UID пользователя консоли. Всем другим пользователям запрещают использовать его.

Для демонстрации этого Вы можете SSH к своей собственной машине и попытке выполнить Монитор Действия от Вашей оболочки. Перечисление 6 показывает пример выполнения этого от Терминала. Первая попытка выполнить Команду контроля Действия работает, потому что это работает как тот же пользователь как Терминал. Вторая попытка перестала работать потому что проверочный пользователь (mrgumby) не соответствует пользователю консоли, и таким образом не может получить доступ к глобальной серверной службе окна.

Перечисление 6  , Получающее доступ к серверу окна от консоли и непользователей консоли

$ ssh ${USER}@localhost
Password:********
Last login: Wed Jun 20 11:49:23 2007
$ id
uid=502(quinn) gid=20(staff) groups=20(staff),81(_appserveradm),
104(com.apple.sharepoint.group.1),79(_appserverusr),80(admin),
101(com.apple.access_remote_ae),103(com.apple.access_ssh-disabled)
$ ls -l /dev/console
crw------- 1 quinn  staff    0,   0 Jun 20 11:50 /dev/console
$ # Launch Activity Monitor and then quit it.
$ /Applications/Utilities/Activity\ Monitor.app/Contents/MacOS/\
Activity\ Monitor 
$ logout
Connection to localhost closed.
$ ssh mrgumby@localhost
Password:********
Last login: Wed Jun 20 11:49:23 2007
$ id
uid=503(mrgumby) gid=20(staff) groups=20(staff),105(com.apple.sharepoint.group.2),
104(com.apple.sharepoint.group.1)
$ ls -l /dev/console 
crw------- 1 quinn  quinn    0,   0 Oct  3 21:31 /dev/console
$ # Activity Monitor fails to launch at all.
$ /Applications/Utilities/ctivity\ Monitor.app/Contents/MacOS/Act
_RegisterApplication(), FAILED TO establish the default connection to \
the WindowServer, _CGSDefaultConnection() is NULL.
2007-06-20 11:54:31.798 Activity Monitor[863:10b] An uncaught exception 
was raised
[...]
Trace/BPT trap
$ logout
Connection to localhost closed.

Это ограничение делает его очень трудно для надежного использования глобальной службы окна потому что:

  • Практика стандартной защиты - то, что демоны не должны работать как корень; скорее они должны быть выполнены преданным пользователем (т.е. wombatd демон выполняется специализированным _wombat пользователь).

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

    Так, при решении проблемы путем выполнения, поскольку корень является безопасностью нет - нет.

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

Жизненный цикл сервера окна

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

Вы видите это в действии с помощью программы от Перечисления 7. Эта программа следит за развитием приоритетного приложения с помощью Событий Углерода, который требует, чтобы он соединился с сервером окна.

Перечисление 7  простая программа, соединяющаяся с сервером окна

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include <Carbon/Carbon.h>

static OSStatus EventHandlerProc(
    EventHandlerCallRef inHandlerCallRef, 
    EventRef            inEvent, 
    void *              inUserData)
{
    fprintf(stderr, "  Switch\n");
    return noErr;
}

int main(int argc, char **argv)
{
    static const EventTypeSpec kEvents[] = { 
        { kEventClassApplication, kEventAppFrontSwitched} 
    };
    OSStatus err;

    err = InstallEventHandler(
        GetApplicationEventTarget(), 
        NewEventHandlerUPP( EventHandlerProc ), 
        sizeof(kEvents) / sizeof(kEvents[0]), 
        kEvents, 
        NULL, 
        NULL
    );
    assert(err == noErr);

    while (true) {
        fprintf(stderr, "Running runloop\n");
        RunApplicationEventLoop();
    }

    return EXIT_SUCCESS;
}

Для тестирования этого необходимо войти в систему с помощью SSH от различной машины (потому что Терминал поддержит выход из системы GUI, если будет процесс, работающий в одном из его окон). Перечисление 8 показывает то, на что мог бы быть похожим этот сеанс.

  Смерть перечисления 8 из-за выхода из системы GUI

$ ssh victim.local. Password:******** Last login: Thu Jun 21 09:05:00 2007 victim$ ./AppSwitchMonitor  Running runloop   Switch   Switch   Switch Running runloop Killed victim$ echo $? 137

Для тиражирования этого самостоятельно сделайте следующий.

  1. Журнал в консоль машины жертвы (victim.local. в этом примере).

  2. От другой машины, SSH к машине жертвы и выполненный AppSwitchMonitor программа. Это будет работать навсегда, распечатывая сообщение когда приоритетные изменения приложений.

  3. Используя GUI, выйти из системы.

  4. Необходимо заметить задержку журнала. Это loginwindow ожидание AppSwitchMonitor ответить на 'quit' Событие Apple. AppSwitchMonitor действительно получает это событие, вызывающее возврат из RunApplicationEventLoop. Однако цикл с условием продолжения вокруг RunApplicationEventLoop сохраняет его выполнением.

  5. AppSwitchMonitor программа в конечном счете умирает. Печать статуса выхода показывает, что это умерло из-за a SIGKILL сигнал (можно декодировать этот статус выхода с помощью макросов в <sys/wait.h>; см. страницу справочника ожидания для подробных данных).

Эта программа уничтожается, потому что сервер окна отслеживает процессы, использующие его службы. Когда Вы выходите из системы, система (фактически loginwindow) попытки выйти из них. Для каждого процесса GUI это отправляет a 'quit' Событие Apple к процессу. Если какой-либо процесс GUI отказывается выходить, loginwindow останавливает выход из системы и выводит на экран сообщение пользователю.

Ситуация для процессов не-GUI немного отличается: loginwindow первые попытки выйти из процесса с помощью a 'quit' Событие Apple; если это перестало работать, это завершает программу путем отправки ему a SIGKILL сигнал. Нет никакого способа поймать или проигнорировать этот сигнал.

Результат этого - то, что, если Ваш процесс соединяется с сервером окна, он не останется в силе после нормального выхода из системы.

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

Проблемы доверия перед входом в систему

Если, в Mac OS X 10.5 и позже, Вы видите сообщение как показанный в Перечислении 9, Вы могли бы по ошибке думать, что решение состоит в том, чтобы заставить систему 'доверять' Вашему приложению, возможно через подписывание кода.

  Сообщение доверия перечисления 9 перед входом в систему

Untrusted apps are not allowed to connect to or launch Window Server before login.

Однако это не имеет место (r. 5544764). Это сообщение действительно говорит Вам, то, что Вы пытаетесь соединиться с сервером окна от неправильного контекста. Вы видите это сообщение, при попытке соединиться с глобальной серверной службой окна от за пределами контекста перед входом в систему, прежде чем пользователь вошел в систему; обычно это означает, что Вы пытаетесь использовать сервер окна от демона.

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

Вместо этого необходимо решить эту проблему путем изменения кода для выполнения в корректном контексте. Если необходимо соединиться с сервером окна в контексте перед входом в систему, создайте предварительный вход в систему launchd агент. Для примера этого посмотрите Пример кода 'PreLoginAgents'.

Контекст защиты

Контекст защиты является другой частью контекста выполнения, связанного с процессом. Контекстом защиты явно управляет сервер безопасности Mac OS X (securityd). Контекст защиты почти всегда следует за пространством имен начальной загрузки: т.е. существует единственный глобальный контекст защиты (также известен как корневой контекст защиты), контексты защиты в расчете на пользователя (для launchd агентов в расчете на пользователя) и контекст защиты на сеанс для каждого сеанса входа в систему.

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

С другой стороны, контекст защиты имеет один хороший атрибут: можно получить полезную информацию о контексте с помощью SessionGetInfo подпрограмма (от <Security/AuthSession.h>). Этот стандартный возврат два полезных данных.

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

Кроме того, SessionGetInfo ряд возвратов отмечает, которые описывают текущий контекст защиты. Это:

  • sessionIsRoot — набор, если это - глобальный контекст защиты

  • sessionHasGraphicAccess — набор, если программы, работающие в этом контексте, могут получить доступ к серверу окна

  • sessionHasTTY — набор, если программы, работающие в этом контексте, могут получить доступ к терминалу (/dev/tty)

  • sessionIsRemote — набор, если этот контекст является быть выполненным по сети

Сводка контекста выполнения

Таблица 2 показывает, как контекст выполнения Вашего демона или агента затронут механизмом, используемым для запуска ее.

Табличная 2  перекрестная ссылка контекста

Тип программы

UID

Пространство имен начальной загрузки

Контекст защиты

демон launchd

как сконфигурировано [1]

как сконфигурировано [2]

как сконфигурировано [3]

элемент входа в систему

пользователь

GUI на сеанс

на сеанс

глобальный элемент входа в систему

пользователь

GUI на сеанс

на сеанс

GUI launchd агент [4]

пользователь

GUI на сеанс

на сеанс

не-GUI launchd агент [4]

пользователь

не-GUI на сеанс

на сеанс

launchd агент в расчете на пользователя [4]

пользователь

в расчете на пользователя

в расчете на пользователя

предварительно войдите в систему launchd агент [4]

корень

предварительный вход в систему [5]

предварительный вход в систему [5]

Примечания:

  1. Сконфигурированное использование UserName свойство в файле списка свойств; значения по умолчанию к root если не указан атрибут.

  2. Использует глобальное пространство имен начальной загрузки если SessionCreate свойство указано в файле списка свойств, когда демон работает в его собственном пространстве имен начальной загрузки на сеанс.

  3. Использует глобальный контекст защиты если SessionCreate свойство указано в файле списка свойств, когда демон работает в его собственном контексте защиты на сеанс.

  4. До Mac OS X 10.5 не было никакого способа управлять целевым типом сеанса launchd агента; агенты launchd выполнялись в расчете на пользователя в непредсказуемом контексте. Посмотрите Агенты для подробных данных.

  5. Как только пользователь вошел в систему, это пространство имен начальной загрузки перед входом в систему и контекст защиты становятся пространством имен начальной загрузки и контекстом защиты сеанса входа в систему зарегистрированного пользователя.

Пример контекста выполнения

Рисунок 3 является графическим примером этой информации. Каждое поле является процессом. Текстом после имени процесса является любой UID того процесса (текст в круглых скобках) или примечание, которое можно искать ниже (текст в квадратных скобках) или оба. Направленная строка представляет родительское/дочернее отношение между двумя процессами. Каждое синее поле представляет пространство имен начальной загрузки в расчете на пользователя. Каждое красное поле представляет пространство имен начальной загрузки на сеанс. Элементы, которые не находятся ни в каком теневом поле, находятся в глобальном пространстве имен начальной загрузки. Существует два сеанса входа в систему GUI (пользователь зарегистрированное первое и затем быстрый пользователь, переключенный на пользователя Б) и два сеанса входа в систему не-GUI (пользователь А и пользователь К).

  Отношения Процесса рисунка 3

Примечания:

  1. WindowServer выполнения с EUID _windowserver (88) и RUID root (0).

  2. loginwindow выполнения с EUID зарегистрированного пользователя и RUID root (0).

  3. ftpd выполнения с EUID зарегистрированного пользователя и RUID root (0). В настоящее время ftpd выполнения в глобальном контексте защиты, но это может измениться в будущих системах; это будет затем действовать больше как sshd, и создайте контекст защиты в расчете на пользователя для каждого сеанса FTP.

Поскольку Вы смотрите на рисунок 3, рассматриваете всевозможные типы процесса, который он показывает.

  • Существует глобальный экземпляр launchd (PID 1). Существует также экземпляр launchd для каждого пользователя.

  • Существует единственный экземпляр WindowServer процесс, находящийся в глобальном контексте защиты. Однако это знает обо всех сеансах входа в систему GUI и может зарегистрировать свои службы в каждом из них.

  • Первая инстанция loginwindow, тот, связанный с сеансом входа в систему пользователя А, является дочерним элементом глобальной переменной launchd. Второй экземпляр, создаваемый, когда пользователь быстрый пользователь переключился на пользователя Б, является дочерним элементом сервера окна.

  • Каждый экземпляр в расчете на пользователя launchd управляет контекстом для всех сеансов входа в систему того пользователя.

  • Сервер области монтажа (pboard) типичный GUI launchd агент.

  • StdIOAgent гипотетический не-GUI launchd агент. Это включено для иллюстрирования, где этот тип агента появился бы в схеме. Mac OS X в настоящее время не устанавливает не-GUI launchd агенты по умолчанию.

  • CCacheServer launchd агент в расчете на пользователя, поддерживающий кэш учетных данных Kerberos для определенного пользователя.

  • ReportCrash программой является GUI launchd агент с говорящими специальными значениями в его списке свойств launchd выполнять его в ответ на катастрофические отказы.

  • sshd launchd демон с SessionCreate набор свойств, что означает, что он работает в его собственном пространстве имен начальной загрузки. Как только пользователь входит в систему, launchd PAM модуль сеанса (/usr/lib/pam/pam_launchd.so, как сконфигурировано /etc/pam.d/sshd) перемещает пространство имен начальной загрузки в в надлежащем пространстве имен начальной загрузки в расчете на пользователя.

  • configd заурядный launchd демон.

  • ftpd launchd демон, запускающийся, когда кто-то соединяется с портом TCP, указанным в его файле списка свойств.

Многоуровневые платформы

Большая часть функциональности Mac OS X реализована большими системными платформами. Многие из этих платформ используют основанные на Махе службы, что они ищут использование службы начальной загрузки. Это может вызвать все виды проблем при вызове их из программы, ссылающейся на неправильное пространство имен начальной загрузки.

Решение Apple этой проблемы разделяет на уровни: мы делим наши платформы на уровни и решаем для каждого уровня, поддерживает ли тот уровень операции в глобальном пространстве имен начальной загрузки. Основное правило состоит в том, что все в CoreServices и ниже (включая Систему, IOKit, Конфигурацию системы, Основу) должно работать в любом пространстве имен начальной загрузки (это безопасные от демона платформы), тогда как все выше CoreServices (включая ApplicationServices, Углерод и AppKit) требует, чтобы GUI на сеанс загрузил пространство имен.

Единственная муха в этой мази - то, что некоторые платформы должным образом не разделены на уровни. QuickTime является совершенным примером этого. Из-за его традиционного наследия Mac OS QuickTime ясно не разделен на уровни в платформы 'приложения' и 'ядро'. Скорее существует одна большая платформа и не задокументированный, какие биты работают от демона. Единственный способ быть сейфом на сто процентов не состоит в том, чтобы использовать платформы как они, но это не опция для многих разработчиков. Можно, однако, минимизировать риск путем ограничения набора подпрограмм, которые Вы вызываете. Проживание Опасно описывает эту идею более подробно.

Таким образом, конкретные рекомендации:

Проживание опасно

Если Ваш демон использует платформы, которые не безопасны от демона, можно столкнуться со множеством проблем.

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

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

  • Если платформа не перестала работать во время загрузки, можно все еще встретиться с проблемами, как Вы вызываете различные подпрограммы от той платформы.

    • Подпрограмма могла бы перестать работать мягко. Например, подпрограмма могла бы перестать работать тихо или распечатать сообщение к stderr, или, возможно, возвратите значимый код ошибки.

    • Подпрограмма могла бы перестать работать враждебно. Например, платформам GUI довольно свойственно вызвать аварийное прекращение работы, если они выполняются демоном!

    • Подпрограмма могла бы работать даже при том, что ее платформа не официально безопасна от демона.

    • Подпрограмма могла бы вести себя по-другому в зависимости от ее входных параметров. Например, подпрограмма распаковки изображения могла бы работать на некоторые типы изображений и сбоя для других.

  • Поведение любой данной платформы и подпрограммы в той платформе, могут измениться от от выпуска к выпуску.

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

Если необходимо вызвать платформу, это не безопасно от демона от демона, необходимо запустить путем регистрации ошибки, описывающей, что Вы делаете и почему. Apple рассмотрит Ваш ввод, поскольку это разрабатывает будущее системное программное обеспечение.

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

Наконец, необходимо протестировать демона на большом разнообразии платформ. И удостоверьтесь, что Вы тестируете с предрелизными сборками Mac OS X, которые доступны через программу отбора программного обеспечения разработчика Apple.

Перекрестная ссылка платформы

Таблица 3 подводит итог, какие платформы безопасны от демона.

Таблица 3  безопасные от демона платформы

Платформа

В безопасности демон?

Платформа

В безопасности демон?

Ускориться

да

InstantMessage

нет

AddressBook

нет

InterfaceBuilder

нет

AGL

нет

IOBluetooth

нет

AppKit

нет

IOBluetoothUI

нет

AppKitScripting

нет

IOKit

да

AppleScriptKit

нет

JavaEmbedding

нет

AppleShareClient

нет

JavaFrameEmbedding

нет

AppleShareClientCore

да

JavaScriptCore

нет

AppleTalk

да

JavaVM

да [3]

ApplicationServices

нет

Kerberos

да [4]

AudioToolbox

да

LatentSemanticMapping

нет

AudioUnit

да

LDAP

да

Automator

нет

Сообщение

нет

CalendarStore

нет

OpenAL

да

Углерод

нет

OpenGL

нет

Какао

нет

OSAKit

нет

Сотрудничество

нет

PCSC

да

CoreAudio

да

PreferencePanes

нет

CoreAudioKit

нет

PubSub

нет

CoreData

да [1]

Python

да [5]

CoreFoundation

да

QTKit

нет

CoreMIDI

да

Кварц

нет

CoreMIDIServer

да [2]

QuartzCore

нет

CoreServices

да

QuickLook

нет

CoreVideo

нет

QuickTime

нет

CPlusTest

да

Ruby

да

DirectoryService

да

RubyCocoa

нет

DiscRecording

нет

ScreenSaver

нет

DiscRecordingUI

нет

Сценарии

да [1]

DiskArbitration

да

ScriptingBridge

да

DrawSprocket

нет

Безопасность

да

DVComponentGlue

нет

SecurityFoundation

да

DVDPlayback

нет

SecurityInterface

нет

EGL

нет

SyncServices

нет

ExceptionHandling

да [1]

Система

да

ForceFeedback

нет

SystemConfiguration

да

Основа

да

Tcl

да

FWAUserLib

да

Tk

нет

НАСЫЩЕНИЕ

нет

TWAIN

нет

ICADevices

нет

vecLib

да

InputMethodKit

нет

WebKit

нет

InstallerPlugins

нет

XgridFoundation

да

Примечания:

  1. Эта платформа была безопасна от демона с тех пор, по крайней мере, Mac OS X 10.4. Однако предыдущая версия этого technote задокументировала его как не являющийся в безопасности демоном.

  2. Это не платформа, которую Вы обычно вызывали бы сами.

  3. Необходимо определить свойство Java java.awt.headless как true, который заставляет Java выдавать исключение, если Вы выполняете код, соединяющийся с сервером окна. Посмотрите Технические Вопросы и ответы QA1328, 'Серверные процессы и Прикрепление'.

  4. С ограничениями; запишите в Developer Technical Support (DTS) для подробных данных.

  5. Базовая функциональность Python является демоном, который не безопасные, но определенные модули. Таким образом, в зависимости от модулей Вы используете, Ваша программа Python может не быть в безопасности демоном.

Эта таблица приводит платформы с Mac OS X 10.5. Если платформа не перечислена здесь, лучше предполагать, что это не безопасно от демона.

Если Вы интересуетесь состоянием подплатформы, и та подплатформа не перечислена здесь, необходимо искать состояние зонтика платформы. Например, можно сказать, что платформа OSServices безопасна, потому что ее платформа зонтика, CoreServices, безопасна.

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

Конструктивные соображения

Этот раздел перечисляет некоторые важные вопросы, которые необходимо рассмотреть при разработке фоновой программы.

Действительно ли это необходимо?

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

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

Риски монолита

Общий вопрос мы входим в DTS, «Как я могу запустить приложение GUI от своего демона?» Ответ - то, что Вы не можете. Это - прямое следствие многоуровневой архитектуры Mac OS X: демон не может запустить приложение GUI, потому что демон работает в неправильном контексте. Даже если это могло бы выбрать, в каком контексте запустить приложение GUI, который это выберет? И что происходит, если компьютер находится в окне входа в систему, означающем, что нет никаких допустимых контекстов GUI?

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

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

Клиент-серверная Модель

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

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

В этом проекте сервер не должен пытаться обнаружить или соединиться с клиентами. Скорее клиенты должны соединиться с сервером. Этот подход является хорошим соответствием для общего межпроцессного взаимодействия (IPC) APIs. Например, при использовании сокетов домена UNIX для демона просто послушать на единственном сокете домена UNIX и сделать, чтобы клиенты соединились с тем сокетом. Для сервера также просто обработать клиент, разъединяющийся (или потому что это вышло или потому что это неожиданно умерло). Если Вы делаете вещи наоборот, вещи начинают становиться грязными.

Быть запущенным

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

Рисунок 4  , Запускающий
daemonFigure 5
  , Запускающий агент

Поскольку этот алгоритм показывает, Apple рекомендует, если это возможно, реализовать фоновые программы с помощью launchd. Причины этого:

Демон рекомендации IPC

Большинство демонов использует своего рода межпроцессное взаимодействие (IPC) для передачи между демоном и его клиентами. Если Вы пишете демону, одно из Ваших первых проектных решений должно быть механизмом IPC для использования. В этом разделе описываются ловушки использования Маха основанный на сообщении IPC в Вашем демоне, и почему необходимо рассмотреть сокеты домена UNIX как альтернативу.

Мах, продуманный вредный

Мах APIs представляет самый низкий интерфейс уровня ядру. Также, они наиболее вероятны измениться, поскольку развивается система. Apple последовательно рекомендовал, чтобы сторонние разработчики избежали их. Это применяется к демонам и агентам, как это делает ко всему остальному.

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

Общая рекомендация Apple для предотвращения использования Маха, APIs включает уведомление, что лучше использовать высокоуровневые обертки. Например, вместо того, чтобы отправить и получить сообщения Маха с помощью Маха APIs непосредственно, лучше использовать высокоуровневый CFMessagePort API. Это - хорошее уведомление в целом; но, если Вы запишете демону или агенту, то Вы все еще столкнетесь с проблемой пространства имен начальной загрузки. Под покрытиями, CFMessagePortCreateLocal регистрирует службу в службе начальной загрузки, и CFMessagePortCreateRemote использует службу начальной загрузки для поиска зарегистрированной службы по имени. Так, даже если Вы избегаете использования сообщений Маха непосредственно при использовании API, это разделено на уровни поверх сообщений Маха, все еще необходимо волноваться о пространствах имен начальной загрузки.

Высокоуровневый IPC APIs, который, наиболее вероятно, собьет Вас с толку:

  • CFMessagePort — В этом API все именованные порты сообщения реализуют использование службы начальной загрузки.

  • Distributed Objects (DO) — Названные соединения DO (такие как Вы зарегистрировали бы использование -[NSConnection registerName:]), реализованы с помощью службы начальной загрузки.

  • События Apple — события Apple реализованы с точки зрения сообщений Маха. В то время как возможно использовать события Apple в демоне (использующий подпрограммы от <AE/AEMach.h>, это - все еще хитрое осуществление.

В целом, проще просто избежать сообщений Маха полностью. Mac OS X обеспечивает многочисленные альтернативные механизмы IPC. Мой любимый является сокетами домена UNIX.

Сокеты домена UNIX являются Вашим другом

Сокеты домена UNIX несколько походят на сокеты TCP/IP, за исключением того, что коммуникация всегда локальна для компьютера. Вы получаете доступ к сокетам домена UNIX с помощью тех же сокетов BSD API, который Вы использовали бы для сокетов TCP/IP. Главной разницей является формат адреса. Для сокетов TCP/IP, структура адреса (то, чему Вы передаете bind, connect, и т.д.), (struct sockaddr_in), который содержит IP-адрес и номер порта. Для сокетов домена UNIX структура адреса (struct sockaddr_un), который содержит путь.

Когда сервер связывает с сокетом домена UNIX, система создает объект файловой системы, представляющий сокет. Например, сокет домена UNIX демона PPP /var/run/pppconfd. Когда Вы смотрите на это с ls -l (см. Перечисление 10), Вы будете видеть, что первый символ перечисления', указывая, что этот объект является сокетом.

Перечисление 10  , Смотрящее на сокет домена PPP's UNIX

$ ls -l /var/run/pppconfd  srwxrwxrwx  1 root  daemon  0  9 May 12:57 /var/run/pppconfd

Как только сервер работает, клиент может соединиться с ним путем простой передачи этого пути к connect вызвать. Как только соединение существует, коммуникационные доходы, как оно было бы для сокетов TCP/IP.

Для узнавания больше о сокетах домена UNIX консультируйтесь с любой стандартной ссылкой UNIX. Я особенно рекомендую Сетевое программирование UNIX Стивенсом и др.

Для примера использования сокетов домена UNIX в клиент-серверной среде посмотрите Пример кода 'CFLocalServer'.

Преимущества сокетов домена UNIX

При реализации демона сокеты домена UNIX API предлагает много преимуществ перед другими механизмами IPC.

  • По сравнению с сокетом TCP это гарантирует, что только обрабатывает работу Вашей локальной машины, может соединиться с Вашим сервером. Можно также получить эту гарантию с TCP, но она требует дополнительной работы.

  • По сравнению с событиями Apple это работает над всеми версиями Mac OS X (существуют проблемы с помощью событий Apple от демона до Mac OS X 10.2). Кроме того, это - API с установлением соединения, таким образом, сервер автоматически узнает о смерти клиента, и для сервера просто асинхронно уведомить клиент.

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

  • Это может быть легко интегрировано в любую серверную архитектуру, включая тех на основе потоков, runloop (использующий CFSocket), избранный цикл или kqueues.

  • Это - известный POSIX API с многочисленными источниками хорошей документации.

  • Исходный код на основе кода сокетов домена UNIX является переносимым на другие платформы POSIX.

  • Это может быть приятно интегрировано в launchd демона. В частности, когда клиент соединяется с сокетом домена UNIX, начиная с Mac OS X 10.4.6 для launchd демона возможно быть запущенным по требованию.

  • Это поддерживает передачу дескриптора, упрощающую для Вас фактору Ваша программа в привилегированные и непривилегированные компоненты.

Наиболее успешная практика

При использовании сокетов домена UNIX имейте в виду следующие моменты.

  • Сокет домена UNIX появляется как элемент в файловой системе. Клиент и сервер обычно твердый код путь к этому сокету. Необходимо использовать путь к надлежащему каталогу (как /var/tmp) и затем дайте сокету уникальное имя в том каталоге. Например, Пример кода 'CFLocalServer' использует путь /var/tmp/com.apple.dts.CFLocalServer/Socket.

  • Используйте путь сокета, это является надлежащим объему связи. Например, демон может использовать постоянный путь сокета, потому что все клиенты соединяются с тем же демоном. С другой стороны, агент должен встроить некоторый идентификатор объема в путь. Например, если все клиенты агента находятся в том же сеансе входа в систему, он должен включать идентификатор сеанса в путь. Или если агент предназначается, чтобы использоваться всеми процессами определенного пользователя, он мог встроить идентификатор пользователя в путь (или, еще лучше, UUID пользователя).

  • При создании сокета домена UNIX необходимо стараться избежать проблем безопасности, вызванных условиями состязания файловой системы. Пример кода 'CFLocalServer' показывает один подход для этого; загрузите выборку и посмотрите на SafeBindUnixDomainSocket подпрограмма в Server.c.

  • Также, если Ваш демон работает как корень, можно вставить сокет домена UNIX /var/run. Этот каталог только перезаписываем привилегированными процессами, который избегает вышеупомянутых проблем безопасности.

  • Можно подтвердить идентификационные данные программы в другом конце сокета с помощью опции сокета LOCAL_PEERCRED, представленной в Mac OS X 10.4.

Перекрестная архитектура IPC

Независимо от какого механизма IPC Вы используете, необходимо будет иметь дело с перекрестными архитектурными проблемами. Например, у данного демона могут быть клиенты, имеющие различное:

  • порядок байтов (порядок байтов) — наиболее распространенный случай этого является демоном Intel с клиентом PowerPC (выполняемый, используя Розетту).

  • размер указателя — Например, 32-разрядный демон с 64-разрядными клиентами, или наоборот.

Необходимо разработать механизм IPC для разрешения с этими проблемами. Общая стратегия включает:

  • используйте основной текстом протокол — Например, Вы могли бы использовать CFPropertyList, это было сглажено к XML.

  • определите порядок байтов и придерживайтесь его — Например, Вы могли потребовать, чтобы вся связь была в сетевом порядке байтов (обратный порядок байтов) и потребовала, чтобы клиенты с прямым порядком байтов подкачали порядок байтов.

  • используйте собственный порядок байтов и потребуйте, чтобы код совместимости подкачал

  • включайте индикацию относительно порядка байтов с сообщением (обычно называемый меткой порядка байтов)

  • не передавайте указатели по проводу (выполнение также - является немного странным так или иначе),

  • при передаче размера определите его в архитектуре нейтральные условия — Например, используйте uint32_t или uint64_t вместо size_t.

Также потребуйте, чтобы все клиенты имели ту же архитектуру.

Кодирование рекомендаций

Следующие разделы перечисляют различные рекомендации кодирования для фоновых программ.

Запуск по требованию

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

Основная стратегия запуска по требованию:

  • Вы устанавливаете launchd конфигурационный файл, указывающий критерии, под которыми Вы должны быть запущены.

  • launchd считает этот файл и ожидает Ваших критериев запуска, которые будут удовлетворены.

  • Когда Ваши критерии запуска удовлетворены, launchd запускает Вашу программу, если это некоторый способ получить информацию о событии, инициировавшем запуск.

Критерии запуска, поддерживаемые launchd перечислены в Таблице 4. См. launchd.plist для подробных данных.

Табличные 4  критерии запуска

Критерий

Ключ списка свойств

Представленный

изменитесь на объект файловой системы

WatchPaths

10.4

непустой каталог

QueueDirectories

10.4

файловая система монтируется

StartOnMount

10.5

периодический

StartInterval

10.4

стенное время

StartCalendarInterval

10.4

Сообщение [1] Маха

MachServices

10.5

соединение с потоковым сокетом [2]

Сокеты

10.4

трафик к порту дейтаграммы [3]

Сокеты

10.4

Примечания:

  1. launchd создаст порт Маха и зарегистрирует его в надлежащем пространстве имен начальной загрузки. Если какие-либо сообщения поступают в тот порт, это запускает Вашу фоновую программу.

  2. Обычно это - или сокет TCP или сокет домена UNIX. Запуск по требованию на основе соединения с сокетом домена UNIX не был выполним до Mac OS X 10.4.6. В частности, SockPathMode ключ списка свойств не поддерживался на PowerPC до Mac OS X 10.4.6 (r. 4252903).

  3. Обычно это - сокет UDP.

По умолчанию launchd предполагает, что Ваша фоновая программа хочет работать по требованию. Этим поведением управляют KeepAlive свойство на Mac OS X 10.5 и позже, и теперь осуждаемый OnDemand свойство на Mac OS X 10.4.x. Таблица 5 показывает более общие способы поведения и свойства, что необходимо привести в порядок для получения того поведения.

Табличное 5  поведение Запуска по сравнению с launchd свойствами

Минимальная система

Желаемое поведение

Свойства

10.4

выполненный, когда загружено и никогда выход

OnDemand = ложь

10.4

выполненный просто по требованию

OnDemand = истина (значение по умолчанию), RunAtLoad = ложь (значение по умолчанию)

10.4

выполненный один раз, когда загружено и отсюда по требованию

OnDemand = истина (значение по умолчанию), RunAtLoad = истина

10.5

выполненный, когда загружено и никогда выход

KeepAlive = истина

10.5

выполненный просто по требованию

KeepAlive = ложь (значение по умолчанию), RunAtLoad = ложь (значение по умолчанию)

10.5

выполненный один раз, когда загружено и отсюда по требованию

KeepAlive = ложь (значение по умолчанию), RunAtLoad = истина

10.5

выполненный по требованию, пока Вы выходите успешно

KeepAlive = {SuccessfulExit = истина}, RunAtLoad = ложь (значение по умолчанию)

10.5

выполненный по требованию, пока определенный путь существует

KeepAlive = {PathState = {/example/path = истина}}, RunAtLoad = ложь (значение по умолчанию)

Как Вы видите, KeepAlive свойство имеет намного больше гибкости, чем OnDemand свойство, которое это заменяет.

Состояние зарегистрированного пользователя демонов

Для демона не возможно действовать от имени пользователя с 100%-й точностью. В то время как это могло бы походить на спорный оператор, фактически довольно просто доказать. Например, рассмотрите что-то как простое как доступ к предпочтительному файлу в корневом каталоге пользователя. Для демона не возможно надежно сделать это. Если у пользователя будет корневой каталог AFP, или их корневой каталог защищен FileVault, то объем, содержащий корневой каталог, будет только смонтирован, когда будет зарегистрирован пользователь. Кроме того, не возможно смонтироваться что объем без учетных данных безопасности пользователя (обычно их пароль). Так, если демон попытается получить пользовательскую настройку, когда пользователь не будет зарегистрирован, то она перестанет работать.

В некоторых случаях полезно исполнить роль пользователя, по крайней мере до проверки полномочий, сделанной подсистемой BSD ядра. Однопоточный демон может сделать это использование seteuid и setegid. Они устанавливают эффективного пользователя и группу ID процесса в целом. Если Ваш демон будет использовать многократные потоки для обрабатывания запросов от различных пользователей, это вызовет проблемы. В этом случае можно установить эффективного пользователя и группу ID использования потока pthread_setugid_np. Это было представлено в Mac OS X 10.4.

Журналирование

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

Системный журнал Apple

Apple System Log (ASL) является средством журналирования, представленным в Mac OS X 10.4. Это позволяет Вам создавать гибкие, структурированные записи в журнале. Это также позволяет Вам писать инструменты управления, запрашивающие эти журналы.

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

Системный журнал

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

Для получения информации о системном журнале см. страницы справочника для syslogd, системный журнал и программы регистратора; для syslog.conf конфигурационного файла; и для системного журнала API.

Журналирование С printf

Если Вы создаете launchd демона или launchd агент, launchd упрощает регистрировать использование printf. В частности:

  • Можно предоставить определенное место назначения для stdout и stderr путем установки StandardOutPath и StandardErrorPath свойства в списке свойств Вашей программы.

  • Если Вы не предоставляете эти свойства, поведение в специфичном для системы. До Mac OS X 10.5, Ваша программа stdout и stderr будет подключен с /dev/null. В Mac OS X 10.5 и позже, launchd получит любой вывод к этим потокам и перенаправит его к ASL.

Если Вы не launchd демон или launchd агент, ситуация более сложна.

  • Для демонов, которые не являются launchd демонами, stdout и stderr обычно подключаются с /dev/null. Если Вы хотите зарегистрировать использование printf, необходимо будет перенаправить их соответственно.

  • Для приложений, работающих на Mac OS X 10.5 и позже, launchd перенаправит stdout и stderr к ASL (очень, как это делает для launchd агентов).

  • Для приложений, работающих на системах до Mac OS X 10.5, stdout и stderr пойдите непосредственно в консольный журнал. Для получения информации о консольном журнале посмотрите Техническое примечание TN2124, 'Волшебство Отладки Mac OS X'.

Соображения безопасности демона

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

  • Если возможно, создайте агент, а не демона. Это ограничит объем любых проблем безопасности.

  • Если Ваш демон использует сеть, не доверяйте данным, которые Вы получаете от сети. Выполнение так могло бы позволить удаленному атакующему ниспровергать Вашего демона.

  • При установке демона удостоверьтесь, что Вы устанавливаете полномочия файловой системы правильно. Apple рекомендует, чтобы демоны принадлежали root, имейте группу владения wheel, и используйте полномочия 755 (rwxr-xr-x) для исполнимых программ и каталогов, и 644 (rw-r - r-) для файлов. Кроме того, каждый каталог от Вашего демона до корневого каталога должен принадлежать корню и только перезаписываемый владельцем (или владел корнем и липкий). Если Вы не делаете этого правильно, неадминистраторский пользователь мог бы быть в состоянии передать их полномочия путем изменения демона (или перестановки его в стороне).

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

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

  • Не упустите другие проблемы коллективной безопасности, особенно переполнение буфера. Природа демонов делает эти проблемы более беспокоящими.

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

Агенты и быстрое переключение между пользователями

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

  • работает ли это в GUI или сеансе входа в систему не-GUI

  • активен ли ее сеанс входа в систему GUI (т.е. с помощью консоли)

  • и т.д.

Для подробной информации об этом посмотрите Многопользовательские Среды.

Process Manager and Launch Services

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

Рассмотрите подпрограмму Диспетчера процессов GetNextProcess. Многочисленные программы, и от Apple и от третьих лиц, используют эту подпрограмму для отображения списка запущенных приложений. Это не было бы целесообразно для этого списка показывать приложения, работающие в других сеансах входа в систему GUI. Таким образом эта подпрограмма использует пространство имен начальной загрузки в качестве неявного параметра для определения сеанса входа в систему, и следовательно списка запущенных приложений для возврата.

Launch Services, разделенная на уровни поверх Диспетчера процессов, имела подобные проблемы.

Таким образом и Process Manager and Launch Services была, исторически, расположена в платформе ApplicationServices и не была доступна демонам.

Apple распознает, что это ограничение довольно неудобно и работает для улучшения вещей. Mac OS X 10.5 представляет первый шаг на том пути. В частности конкретная реализация Диспетчера процессов переместилась от сервера окна до демона CoreServices (coreservicesd). С этим изменением теперь возможно создать безопасные от демона системные службы, имеющие дело с процессами Диспетчера процессов.

Первый бенефициарий этого изменения в Launch Services. В Mac OS X 10,5 Launch Services были перемещены вниз на уровень Core Services (это - теперь подплатформа платформы CoreServices). Таким образом это теперь безопасно от демона.

Это повышает вопрос того, что происходит, когда Вы используете Launch Services для запуска приложения от демона. Поскольку демон работает в глобальном пространстве имен начальной загрузки, Launch Services не может использовать пространство имен начальной загрузки для получения сеанса входа в систему. Скорее Launch Services использует EUID обработки вызовов. Поведение следующие:

  • Если EUID обработки вызовов является нулем, приложение запускается в контексте в настоящее время активного сеанса входа в систему GUI. Если нет никакого в настоящее время активного сеанса входа в систему GUI (никто не зарегистрирован, или у зарегистрированного пользователя есть быстрый пользователь, переключенный на окно входа в систему), поведение является неуказанным (r. 5321293).

  • Если EUID обработки вызовов не является нулем, и это соответствует, тот из пользователя зарегистрировал на пути GUI, приложение запускается в контексте сеанса входа в систему GUI того пользователя.

  • Если EUID обработки вызовов не является нулем, но это не соответствует, тот из пользователя зарегистрировал на пути GUI, поведение является неуказанным (r. 5321281).

К сожалению, в то время как конкретная реализация Диспетчера процессов теперь на уровне CoreServices, сам Диспетчер процессов остается в платформе ApplicationServices. Таким образом все еще не возможно использовать Диспетчер процессов от демона.

Полезные советы

В этом разделе описываются различные полезные советы, которые Вы могли бы счесть полезным при разработке фоновой программы.

Запуск демона

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

  • Вы не хотите вынуждать пользователя к перезагрузке.

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

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

  • Если у Вас есть launchd демон, можно запустить его путем выполнения launchctl. launchctl работы путем отправки сообщения в launchd, то, чтобы просить, чтобы он запустил демона от Вашего имени, таким образом, Ваш демон является дочерним элементом launchd и наследовал корректный контекст.

  • Иначе, можно запустить демона в глобальном пространстве имен начальной загрузки с помощью инструмента StartupItemContext. Этот инструмент не совершенен (r. 4283301), но это работает в большинстве ситуаций.

  • Наконец, если Ваш демон только использует безопасные от демона платформы, и это не использует или регистрирует службы обмена сообщениями Маха прямо или косвенно, необходимо быть в состоянии просто запустить его. Это будет работать в неправильном контексте, но это, вероятно, не вызовет серьезные проблемы.

Отладка запуска

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

  • Можно добавить параметр командной строки (традиционно, это - «-d» для «отладки»), который заставляет программу работать как стандартный инструмент (например, элемент запуска не был бы сам daemonize). Это позволяет Вам отлаживать его непосредственно от GDB.

  • Если Вы - разработчик launchd демон или агент, можно добавить WaitForDebugger свойство к Вашему файлу списка свойств. Истинное значение вызовет launchd для запуска программы в состоянии ожидания после чего можно присоединить с отладчиком.

    Это свойство поддерживается в Mac OS X 10.5 и позже.

  • Если предыдущие опции не помогают, можно добавить системный вызов паузы кода запуска, как проиллюстрировано Перечислением 11. Это останавливает программу, пока сигнал не поступает и, как это оказывается, присоединение к процессу с GDB отправляет сигнал в процесс и разблокирует его.

Перечисление 11  , Приостанавливающееся при запуске

if (1) {     fprintf(         stderr,          "Process %ld waiting for debugger.\n",          (long) getpid()     );     pause(); }

Отладка использования сервера окна жулика

Если Ваш демон умирает с сообщением как показанный в Перечислении 6, и Вы не можете думать ни о какой причине, почему это должно присоединять к серверу окна, существует множество вещей, которые можно сделать для отладки проблемы. Первое должно установить INIT_Processes переменная окружения. Когда Вы видите получающееся сообщение в системном журнале, Вы можете присоединенный с GDB и делаете след для наблюдения то, что инициировало соединение. Для получения дополнительной информации об этой переменной окружения, посмотрите Техническое примечание TN2124, 'Волшебство Отладки Mac OS X'.

Наблюдайте свои журналы!

При разработке фоновой программы это - хорошая идея обычно контролировать ее файл журнала. Вы были бы поражены, как часто неожиданное сообщение журнала позволит Вам быстро изолировать неясную проблему. Это может быть столь же просто как регистрирующий использующий ASL или printf и выполняющий Консольное приложение в фоновом режиме.

Если Вы пишете демону, это запускается во время запуска (прежде чем можно будет запустить Консольное приложение или даже информацию SSH машина), можно быть в состоянии видеть ее сообщения журнала удержанием команды-V при запуске (для «многословной начальной загрузки»). Для постоянного включения многословных начальных загрузок добавьте «-v» параметр Вашему boot-args Переменная NVRAM, как показано в Перечислении 12.

Перечисление 12  , Разрешающее многословные начальные загрузки

$ sudo nvram boot-args="-v" Password:********

Пространства имен начальной загрузки: еще одна вещь

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

Существует один глюк, как бы то ни было. Когда система создает пространство имен начальной загрузки, она связывает его с процессом, создавшим ее. Когда тот процесс выходит, пространство имен деактивировано. Например, если Вы запускаете демона из сеанса входа в систему SSH и затем выходите из системы, процесс, создавший пространство имен начальной загрузки (эффективно это sshd) деактивированы выходы и пространство имен начальной загрузки, которое это создало, который является пространством имен, которое наследовал Ваш демон.

Деактивированное пространство имен начальной загрузки позволяет Вам искать службы, но оно не позволяет Вам регистрировать новые службы. Любая попытка зарегистрировать службу в деактивировать пространстве имен перестанет работать с ошибкой BOOTSTRAP_NOT_PRIVILEGED (1100). Точно так же любая высокоуровневая обертка, регистрирующая службу, перестанет работать. Например, CFMessagePortCreateLocal распечатает сообщение об ошибке и возврат NULL при вызове его после того, как было деактивировано пространство имен.

Более ранние версии Mac OS X (до Mac OS X 10.2) не деактивировали пространства имен начальной загрузки; скорее когда процесс, создавший пространство имен выходы, служба начальной загрузки, сразу уничтожит пространство имен. Любой процесс, все еще ссылающийся на то пространство имен, оказался бы без любого пространства имен начальной загрузки (технически, их портом начальной загрузки теперь будет Мах мертвое имя).

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

Тщательный с тем ветвлением, Юджином

Запуск в Mac OS X 10.5 launchd когда тот процесс выходит, работы для сборки «мусора» любые дочерние процессы launchd демона или агента обрабатывают. В частности, когда launchd демон или выходы агента, launchd отправит a SIGTERM связанной группе процесса.

Это может вызвать проблемы при разработке launchd демона, создающего дочерний процесс для выполнения некоторой другой программы (традиционным ветвлением и исполнительной комбинацией, или через posix_spawn). Дочерний процесс наследует свою группу процесса ID от Вашего демона. Если Ваш демон выйдет то перед дочерним процессом дочерний процесс получит a SIGTERM потому что это находится в той же группе процесса как Ваш демон.

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

  • выполните 'дочерний элемент' через launchd — При создании 'дочернего элемента' отдельным launchd заданием все будет Просто Work™.

  • создайте новый сеанс для дочернего элемента — Если необходимо продолжать создавать дочерний процесс вручную, выполните дочерний процесс в новом сеансе (и, следовательно, в новой группе процесса) путем вызова setsid.

  • используйте AbandonProcessGroup свойство — Если Вы добавляете это свойство к файлу списка свойств своей программы, launchd не попытается собрать «мусор» Ваши дочерние процессы.

Традиционно Вы изолировали бы дочерний процесс от демона при наличии дочернего демона вызова. И, если бы Вы сделали это, то дочерний элемент действительно закончил бы в отдельной группе процесса и не был бы собран «мусор» launchd. Следует иметь в виду, однако, что daemon подпрограмма официально осуждается в Mac OS X 10.5 и позже.

Дополнительные материалы для чтения

Старые системы и технология

Объем этого technote отражает действительность Mac OS X 10.5. В этом разделе описываются технологию от более ранних систем, теперь осуждающуюся или не поддерживающуюся и обсуждающую другие темы, не применяющиеся на современные системы.

Осуждаемый Daemonomicon

Много механизмов раньше запускались, фоновые программы теперь осуждаются или не поддерживаются. В этом разделе описываются эти механизмы.

Элемент запуска

Элемент запуска является демоном, запущенным во время запуска программой SystemStarter (который запускается launchd на Mac OS X 10.4 и позже, и непосредственно от /etc/rc.local в более ранних системах). Сторонний элемент запуска должен быть установлен в /Library/StartupItems каталог. Для получения дополнительной информации об элементах запуска, посмотрите, что Системный Запуск Программирует Темы.

Начиная с Mac OS X 10.4, элементы запуска осуждаются в пользу launchd демонов.

Демон mach_init

mach_init демон запускается процессом инициализации Маха (mach_init на Mac OS X 10.3.x и ранее, launchd на Mac OS X 10.4 и позже). mach_init демон установлен путем размещения файла списка свойств в /etc/mach_init.d каталог. Apple не поддерживает стороннюю разработку mach_init демонов.

Начиная с Mac OS X 10.5, mach_init демоны осуждаются в пользу launchd демонов; launchd демон может теперь указать список имен службы Маха для регистрации как часть ее файла списка свойств.

inetd и xinetd Демон

демоны inetd и xinetd демоны запускаются Интернетом супер сервер (первоначально inetd и позже xinetd и теперь launchd). inetd демон установлен путем добавления строки к /etc/inetd.conf. xinetd демон установлен путем добавления конфигурационного файла к /etc/xinetd.d каталог.

xinetd был начат с Mac OS X 10.2. Где xinetd доступно, необходимо предпочесть его inetd потому что проще сконфигурировать.

Начиная с Mac OS X 10.4, inetd и xinetd демоны осуждаются в пользу launchd демонов; launchd демон может указать список портов TCP и UDP для слушания на как часть его файла списка свойств.

Начиная с Mac OS X 10.5, больше не поддерживаются inetd и xinetd демоны. Однако просто выполнить существующий inetd или xinetd демона путем создания и установки простого launchd файла списка свойств (приводящий к inetd-совместимому launchd демону). Для примера этого проверить /System/Library/LaunchDaemons/finger.plist.

Для получения дополнительной информации о inetd и xinetd демонах, посмотрите Сетевое программирование UNIX и xinetd веб-сайт.

Системный элемент входа в систему

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

Системные элементы входа в систему осуждаются на Mac OS X 10.5 и позже (r. 5316827). Необходимо, вместо этого, использовать предварительный вход в систему launchd агент.

Если необходимо разработать системный элемент входа в систему для использования в системах до Mac OS X 10.5, свяжитесь с Developer Technical Support (DTS) для подробных данных.

Агент mach_init

mach_init агент походит на mach_init демона, за исключением того, что он работает в контексте определенного пользователя. Это запускается (косвенно) loginwindow как часть процесса входа в систему пользователя GUI. mach_init агент установлен путем размещения файла списка свойств в /etc/mach_init_per_user.d каталог. Apple не поддерживает стороннюю разработку mach_init агентов.

Начиная с Mac OS X 10.5, mach_init агенты осуждаются в пользу launchd агентов; launchd агент может теперь указать список имен службы Маха для регистрации как часть ее файла списка свойств.

Сводка контекста выполнения для осуждаемых технологий

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

Табличная 6  перекрестная ссылка контекста для осуждаемых механизмов

Тип программы

UID

Пространство имен начальной загрузки

Контекст защиты

элемент запуска

корень

глобальная переменная

глобальная переменная

демон mach_init

корень

глобальная переменная

глобальная переменная

демон inetd

как сконфигурировано [1]

глобальная переменная

глобальная переменная

демон xinetd

как сконфигурировано [2]

как сконфигурировано [3]

как сконфигурировано [4]

системный элемент входа в систему

корень

предварительный вход в систему

предварительный вход в систему

агент mach_init

пользователь

GUI на сеанс

на сеанс

Примечания:

  1. Сконфигурированное использование пятого столбца записи демона в inet.conf.

  2. Сконфигурированное использование user атрибут в конфигурационном файле; значения по умолчанию к root если не указан атрибут.

  3. Использует глобальное пространство имен начальной загрузки если session_create атрибут указан в конфигурационном файле, когда демон работает в его собственном пространстве имен начальной загрузки на сеанс.

  4. Использует глобальный контекст защиты если session_create атрибут указан в конфигурационном файле, когда демон работает в его собственном контексте защиты на сеанс.

Пример контекста выполнения на Mac OS 10.4

Рисунок 6 является графическим примером контекстов выполнения на Mac OS X 10.4. Прежде, чем консультироваться с этим числом, необходимо считать актуальную информацию в Примере Контекста выполнения. Это объясняет полную тягу схемы. Текст в этом разделе концентрируется на различиях между этой схемой и эквивалентной схемой для современной системы.

В этой схеме существует три пространства имен начальной загрузки на сеанс, два для сеансов входа в систему GUI (пользователи А и Б) и один для сеанса входа в систему SSH (пользователь А). Они представлены красными полями. Элементы, которые не находятся ни в каком красном поле, находятся в глобальном пространстве имен начальной загрузки.

  Отношения Процесса рисунка 6 в Mac OS X 10.4

Примечания:

  1. WindowServer выполнения с EUID windowserver (88) и RUID root (0).

  2. loginwindow выполнения с EUID зарегистрированного пользователя и RUID root (0).

  3. ftpd выполнения с EUID зарегистрированного пользователя и RUID root (0).

Поскольку Вы смотрите на рисунок 6, рассматриваете всевозможные типы процесса, который он показывает.

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

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

  • Первая инстанция loginwindow, тот, связанный с сеансом входа в систему пользователя А, является дочерним элементом launchd. Второй экземпляр, создаваемый, когда пользователь быстрый пользователь переключился на пользователя Б, является дочерним элементом сервера окна.

  • Каждый экземпляр loginwindow управляет контекстом для его связанного сеанса входа в систему.

  • loginwindow также выполнения pbs (сервер области монтажа) непосредственно.

  • Crash Reporter приложение является фактически mach_init агентом. Отметьте, как его родительский процесс launchd, но это работает в пространстве имен начальной загрузки соответствующего пользователя.

  • sshd launchd демон с SessionCreate набор свойств, что означает, что он работает в его собственном сеансе входа в систему не-GUI

  • lookupd mach_init демон.

  • mds (демон Центра внимания), элемент запуска.

  • ftpd launchd демон.

Различия между этим и Mac OS X 10.5 включают:

  • В Mac OS X 10.4 Вы редко видите экземпляры в расчете на пользователя launchd.

  • В Mac OS X 10.4, все приложения являются дочерними элементами сервера окна. В Mac OS X 10.5, та ответственность была принята экземплярами в расчете на пользователя launchd.

  • В Mac OS X 10.4 нет никаких пространств имен начальной загрузки в расчете на пользователя. Таким образом не возможно зарегистрировать службу, как сервер кэширования учетных данных Kerberos (CCacheServer) на Mac OS X 10.5, который доступен всем сеансам входа в систему определенного пользователя.

  • В Mac OS X 10.4 сервер области монтажа (pbs) был запущен непосредственно loginwindow. В Mac OS X 10.5, сервер области монтажа (pboard) стандарт launchd агент.

  • В Mac OS X 10,4 launchd агентов запускаются в расчете на пользователя без отношения к типу сеанса входа в систему пользователя. Это сильно ограничивает их полноценность. Фактически, Mac OS X 10.4 делает нет смысла в launchd агентах. Напротив, на Mac OS X 10.5 launchd агент может указать, какие типы сеансов входа в систему, которые это поддерживает, и launchd агенты, используются экстенсивно системой.

  • Механизм тот, который приложение для создания отчетов катастрофического отказа (Crash Reporter на 10,4, ReportCrash на 10,5), запускается, изменился, заставив создание отчетов катастрофического отказа обработать более простой и более надежный.

Будучи запущенным до Mac OS X 10.5

До Mac OS X 10.5, launchd было значительно менее мощным, чем это сегодня. Таким образом много методов, показанных в числах в том, чтобы быть запущенным, не доступны. Если необходимо поддерживать более старые системы, необходимо консультироваться с рисунком 7 и рисунком 8 при выборе лучшего подхода к запуску программы.

Рисунок 7  , Запускающий демона до Mac OS X
10.5Figure
8  Запусков агента до Mac OS X 10.5

Устаревшие методы программирования

Этот раздел покрывает некоторые методы программирования, которые только интересны, если Вы разрабатываете осуждаемую фоновую программу.

Daemonization

Если Вы пишете демону, Вы, возможно, должны изолировать свой процесс от среды, которую он наследовал от ее родителя. Этот шаг известен как daemonization. Для подробных данных основных элементов посмотрите Усовершенствованное Программирование В Среде UNIX; также, посмотрите Тщательный С Тем Ветвлением, Юджином.

Необходим ли daemonization, зависит от того, как Вы были запущены. Таблица 7 показывает это для каждого типа демона.

Таблица 7  Daemonization требуется?

Тип демона

Daemonize

элемент запуска

должен

демон mach_init

не должен

демон inetd

не должен

демон xinetd

не должен

демон launchd

не должен

Если Вам нужно к daemonize, можно сделать настолько использующий подпрограмму демона.

Daemonization и Bootstrap Namespaces

Исторически daemon подпрограмма не изменяла пространство имен начальной загрузки программы вызова. Это вызвало многочисленные проблемы и требовало создания инструмента StartupItemContext. Для получения дополнительной информации об этой проблеме, посмотрите Запуск Демона.

Начиная с Mac OS X 10.5 daemon подпрограмма действительно переключает программу вызова в глобальное пространство имен начальной загрузки (r. 5185932). Однако это имеет, в свою очередь, вызвал некоторые проблемы совместимости (r. 5499553). Для предотвращения этих проблем, соблюдающие правила теперь применяются:

  • Если Ваша программа создается с Mac OS X 10,5 SDK (или позже), и Вы ставите цель развертывания к Mac OS X 10.5 (или позже), daemon переключит Вашу программу на глобальное пространство имен начальной загрузки.

  • Иначе, daemon ведет себя, как это всегда имеет.

Платформы Daemonizing

Если Вы вызываете ветвление, но не вызываете руководителя, много платформ Mac OS X не работают надежно. Единственное исключение является Системной платформой и, даже там, стандарт POSIX помещает серьезные ограничения на то, что можно сделать между a fork и exec.

Под покрытиями подпрограмма демона вызывает ветвление. Таким образом, если Вы вызываете daemon и используйте платформы выше Системных платформ, можно столкнуться с проблемами. Критически, платформы, которые являются иначе в безопасности демоном, как Базовая Основа, не безопасно вызвать от этого состояния.

  Основа Ядра перечисления 13, жалующаяся на ветвление без руководителя

The process has forked and you cannot use this CoreFoundation \
functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_\
COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.

Существует два общих решения этой проблемы:

  • примите launchd — launchd демон не вызывает daemon, и таким образом не страдает от этой проблемы. Это - предпочтительное решение.

  • руководитель самостоятельно — Если Вы не можете принять launchd (возможно, Ваш продукт должен поддерживать Mac OS X 10.3.x), можно решить эту проблему exec'ing сами. Перечисление 14 показывает основную идею. Следует иметь в виду, что это - просто минимальная выборка — настоящий демон фактически обработал бы ошибки!

Перечисление 14  Используя руководителя для предотвращения проблем платформы после daemonization

#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <syslog.h>
#include <mach-o/dyld.h>

extern char ** environ;

int main(int argc, char **argv)
{
    if ( (argc >= 2) && (strcmp(argv[1], "daemon") == 0) ) {
        optind = 2;

        // ... process any post-daemonization arguments ...

        // ... run as a daemon ...
    } else {
        char **     args;
        char        execPath[PATH_MAX];
        uint32_t    execPathSize;

        // ... process any pre-daemonization arguments ...

        // Calculate our new arguments, dropping any arguments that 
        // have already been processed (that is, before optind) and 
        // inserting the special flag that tells us that we've 
        // already daemonized.
        //
        // Note that we allocate and copy one extra argument so that 
        // args, like argv, is terminated by a NULL.
        // 
        // We get the real path to our executable using 
        // _NSGetExecutablePath because argv[0] might be a relative 
        // path, and daemon chdirs to the root directory. In a real 
        // product you could probably substitute a hard-wired absolute 
        // path.

        execPathSize = sizeof(execPath);
        (void) _NSGetExecutablePath(execPath, &execPathSize);

        args = malloc((argc - optind + 1) * sizeof(char *));
        args[0] = execPath;
        args[1] = "daemon";
        memcpy(
            &args[2], 
            &argv[optind], 
            (argc - optind + 1) * sizeof(char *)
        );

        // Daemonize ourself.

        (void) daemon(0, 0);

        // exec ourself.

        (void) execve(execPath, args, environ);
    }

    return EXIT_SUCCESS;
}


История версии документа


ДатаПримечания
05.11.2007

Основное обновление для Mac OS X 10,5 Leopard.

03.01.2006

Новый документ, описывающий наиболее распространенные проблемы, с которыми встречаются с демонами и агентами, и предлагающий подробные решения.