OS Daemonology: виды, преимущества, подводные камни

Демоны, агенты, хелперы — да кто они все такие?!

Меня зовут Владимир. Я занимаюсь macOS-разработкой уже около 6 лет. За это время работал «от и до» — от дизайна окошек и кастомных кнопок до системного программирования, секьюрити и написания Kernel-модулей.

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

Статья будет полезна любым технически направленным специалистам; всем, кто хочет понимать, как работают приложения и сама ОС (причем не только macOS, но и прочие *OS) «под капотом»; и конечно, тем, кто хочет знать, как и когда использовать тех или иных демонов при разработке своих приложений.

Что мы видим и в чем правда?

Как обычный пользователь видит операционную систему со своей стороны?

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

При этом очевидно, что для работы как этих приложений, так и всей системы должно быть что-то еще.

Демоны внутри!

Демоны?

В разговорной речи обычно можно услышать термин «демон», что хоть и неправильно, но используется повсеместно. Стоит помнить, что Daemon != Demon. Согласно исторической справке, daemon — это некое создание, порожденное богами для выполнение различного рода работ, которыми сами боги заниматься не хотят. Эта терминология впервые была использована в так называемом демоне Максвелла.

Так кто же такие все эти daemons и зачем они нужны?

Это в первую очередь OS support, а также мониторинг, функции серверной части приложений, task managers. Они выполняются в background и (обычно) лишены какого-либо GUI.

Сам daemon в системе операционных систем *OS представлен конфигом (.plist) с описанием демона плюс непосредственно исполняемым файлом.

Конфигурационный файл .plist описывает и задает поведение демона.

Например:

  • Label — идентификатор демона:
    com.example.mydaemond
  • ProgramArguments — аргументы командной строки, используемые для старта демона:
    /bin/rm, -rf, “/tmp/trashfiles”
    /Library/Application Support/CoolApp/com.coolapp.monitord

Еще есть большое количество прочих аргументов, таких как:

  • KeepAlive (launchd перезапустит процесс, если процесс будет завершен);
  • RunAtLoad (launchd запустит процесс на старте (системы/логин-сессии));
  • WorkingDirectory (рабочая директория процесса);
  • WatchPaths (пути файловой системы, за которыми будет следить демон).

Note: вы можете подробнее прочесть об этих и прочих атрибутах файла конфигурации в мануале к launchd: man launchd.plist.

Также можно обратиться к официальной статье Apple.

launchd — самый первый демон

В *OS-системах есть «особый» демон, имя которому — launchd.

launchd — это аналог init UNIX-подобных систем. launchd всегда имеет pid 1 (pid 0 — это сам Kernel).

Основные задачи launchd:

  • инициализация системы;
  • запуск/перезапуск процессов;
  • поддержка демонов;
  • поддержка XPC.

Занятный факт: у launchd есть персональный Twitter.

*OS Daemonology в деталях

Note: это описание приводится на примере macOS как системы с открытым доступом к файловой системе. При этом устройство прочих ОС семейства Apple (iOS, watchOS...) аналогичное.

Apple предлагает классифицировать всех демонов по следующим категориям:

XPC Service

Пожалуй, XPC Service — это самый пропиаренный и задокументированный вид демонов в macOS.

Apple сами рекомендуют использовать XPC Service в приложениях. Их даже можно встраивать во фреймворки.

Основные факты о XPC Service:

  • App Store friendly;
  • специальная цель в Xcode;
  • работает как текущий пользователь;
  • GUI-less;
  • stateless (может быть уничтожен при запуске на IDLE);
  • относится к конкретному приложению;
  • живет не дольше, чем родительское приложение;
  • может быть установлен в изолированном приложении.

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

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

Важные заметки о XPC Service:

  • не может быть пользователем root;
  • не может использовать какие-либо функции графического интерфейса (Windows, уведомления, ...);
  • может быть non-sandboxed, даже если основное приложение (конечно, не в AppStore).

UserAgent

Довольно популярной разновидностью демонов является UserAgent. Он представляет собой самостоятельный background-процесс, запущенный от текущего пользователя. Обычно существует 1 процесс UserAgent для каждого конкретного пользователя системы (для каждой логин-сессии).

Основные факты о UserAgent:

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

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

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

Note: установить UserAgent в систему можно только из НЕ-Sandbox-приложения.

Vanilla daemon

Daemon как daemon (в классическом понимании) — это системно-глобальный процесс, запущенный от root. Daemon существует один для всей системы, а также может стартовать в момент загрузки ОС и до процедуры аутентификации пользователя.

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

  • выполнение привилегированных операций (от root);
  • хранение глобального состояния независимо от логин-сессий;
  • координация множества UserAgent;
  • установка KEXT.

Основные факты о Daemon:

  • независимое приложение;
  • запускается как root;
  • может иметь состояние;
  • может запускаться при загрузке OS;
  • singleton;
  • GUI-less.

Note: Apple НЕ рекомендует использовать демонов, предлагая заменить их UserAgent или в крайнем случае PrivilegedHelper из соображений безопасности. Если следовать этой рекомендации, то риск компрометации пользовательских данных и системы в целом снижается.

PrivilegedHelper

PrivilegedHelper можно назвать «урезанным» демоном.

В Apple говорят, что если уж прямо невмоготу использовать root, то тогда хотя бы используйте PrivilegedHelper вместо обычных скриптов от root.

По своим свойствам это такой же Daemon, но при этом существует ряд особенностей:

  • должен быть установлен исключительно с помощью API SMJobBless;
  • при установке копируется в /Library/PrivilegedHelperTools.

Казалось бы, что такого в копировании в /Library/PrivilegedHelperTools?

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

Для решения вопроса можно идти различными путями:

  1. Статические библиотеки.
  2. Runtime-линковка.

Основные факты о PrivilegedHelper:

Note: PrivilegedHelper НЕ может быть установлен из Sandboxed-приложения.

LoginItem

LoginItem является совместимым с App Store (и урезанным) вариантом UserAgent. По сути, это обычный UserAgent, который устанавливается с помощью легального API, предоставляемого Apple.

Основные факты о LoginItem:

  • пользовательский агент с «ограниченным» доступом;;
  • устанавливается через SMLoginItemSetEnabled;
  • может быть установлен в изолированном приложении;
  • автоматически запускается при входе пользователя в систему;
  • руководство по использованию.

Note: это НЕ тот LoginItem, который можно увидеть в Settings.

Где обитают демоны?

Как говорилось ранее, демон — это пара из файла конфигурации и самой исполняемой программы. Где разместить сам исполняемый файл, не имеет ровным счетом никакого значения, так как это просто путь в секции Program/ProgramArguments.

Файлы конфигурации размещаются: ПутьПример

Daemons:

/Library/LaunchDaemons

/Library/LaunchDaemons

/System/Library/LaunchDaemons

Global (all-users) Agents:

/Library/LaunchAgents

/Library/LaunchAgents

/System/Library/LaunchAgents

User-specific Agents:

~/Library/LaunchAgents

/Users/JohnDoe/LaunchAgents

/Users/JaneDoe/LaunchAgents

...

~/LaunchAgents

Отличие глобальных UserAgent от user-specific в том, что глобальные агенты будут ассоциированы со всеми пользователями ОС. При этом user-specific-агенты ассоциированы только с конкретным пользователем.

Note: чтобы поместить агента в «глобальные», требуются права root.

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

Мы рассмотрели основные актуальные на сегодняшний день виды демонов в семействе систем *OS. Часто задаваемый вопрос — «Зачем мне XPC Service, если я могу просто запустить еще один поток?».

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

EXC_BAD_ACCESS, crash-resistance

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

В случае с «классическим» монолитным подходом к приложению пользователь теряет все и сразу. Однако если вынести части кода по отдельным сервисам, то самое худшее, что нас ждет, — это потеря только одной фичи (при этом с показом нефатальной ошибки). Конечно, такой подход несколько усложняет архитектуру системы. Но что такое небольшая сложность перед такими огромными преимуществами, как crash safety?

Security

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

Ограниченное использование root

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

Личные демоны

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

При выборе между XPC Service, UserAgent и LoginItem предлагаю руководствоваться следующими соображениями и практическим опытом.

XPC ServiceLoginItemUserAgent

  • AppStore-friendly;
  • всегда упакованный;
  • может быть встроен во фреймворки;
  • нет графического интерфейса;
  • stateless.

  • AppStore-friendly;
  • может иметь графический интерфейс;
  • всегда упакованный;
  • нативная установка API;
  • SMLoginItemSetEnabled;
  • сложности при обновлении / переустановке;
  • неочевидные способы коммуникаций;
  • недокументированное поведение при запуске / повторном запуске.

  • может иметь графический интерфейс;
  • пакет или исполняемый файл;
  • гибкий launchd.plist;
  • процедура установки с помощью инструмента «launchctl»;
  • для установки требуется non-sandboxed app.

Контроль демонов — launchctl

Если XPC Services, LoginItem, PrivilegedHelper будут (должны) работать «из коробки», то, чтобы завести полноценного UserAgent или Daemon, придется уже работать с системными тулзами.

macOS предоставляет тулзу launchctl, посредством которой можно:

  • получить список всех «демонических» проявлений, зарегистрированных для каждого пользователя;
  • загрузить/выгрузить демона;
  • включить/выключить демона и т. д.

Examples

List agents for current user: launchctl list
Load agent: launchctl bootstrap gui/501 /Library/LaunchAgents/com.company.launchagent.plist
Unload agent: launchctl bootout gui/501 /Library/LaunchAgents/com.company.launchagent.plist
where <user’s UID> is user id (uid_t)

List daemons: sudo launchctl list
Load daemon: sudo launchctl bootstrap system /Library/LaunchDaemons/com.company.launchdaemon.plist
Unload daemon: sudo launchctl bootout system /Library/LaunchDaemons/com.company.launchdaemon.plist

Более полная документация по launchctl находится по ссылкам: Launchctl 2.0 syntax и cheatsheet.md.

Это только начало

Изначально я планировал разбить эту тему на две статьи: одну — о разновидностях демонов в *OS плюс описание XPC как механизма межпроцессного взаимодействия, вторую — о low-level-реализации XPC. Однако, как обычно, материала вышло больше, чем предполагалось. Потому об XPC речь будет идти в отдельной статье.

Подписывайтесь и следите за интересными статьями и code sample: GitHub и Twitter.

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному1
LinkedIn



7 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.
но и прочие *OS

 Мммм... Я правильно понимаю что звездочка заменяет любой символ и под прочие попадает так же и DOS?

Статтю написали після дуже достойної події по MacOS-розробці. Дякую за організацію!

Спасибо, «приходьте ще» =)

Спасибо, занятно.

Годнота, что нетипично для технических статей на DOU. Спасибо.

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

Підписатись на коментарі