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?
А то, что отламывается любая динамическая линковка со своими библиотеками и фреймворками, поскольку процесс не знает, где их искать.
Для решения вопроса можно идти различными путями:
- Статические библиотеки.
- Runtime-линковка.
Основные факты о PrivilegedHelper:
- «рекомендованный» демон;
- устанавливается через SMJobBless;
- обычно используется как установщик / деинсталлятор;
- руководство по использованию.
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 Service | LoginItem | UserAgent |
|
|
|
Контроль демонов — 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.