launchd и launchctl: система запуска macOS

Рейтинг: 72.2% · 10 голосов
Подробный курс по macOS для разработчиков и админов: архитектура и Apple Silicon, терминал и zsh, Homebrew и пакеты, launchd и автоматизация, APFS и Time Machine, безопасность (SIP, Gatekeeper, TCC, FileVault), сеть, логи и диагностика, MDM и DDM, контейнеры и виртуализация, восстановление. По официальной документации Apple, актуально на 2026 (macOS 26 Tahoe).
Ответить
Аватара пользователя
Artem_MacAdmin
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

launchd и launchctl: система запуска macOS

Сообщение Artem_MacAdmin »

Оглавление курса (47)
  1. Что такое macOS: Unix под капотом и место в 2026
  2. Архитектура macOS: XNU, тома и Signed System Volume
  3. Apple Silicon: ARM, Rosetta 2 и отличия от Intel
  4. Установка, обновление и восстановление macOS
  5. Finder, рабочий стол и продуктивность
  6. Системные настройки и их управление из CLI (defaults)
  7. Пользователи и группы: учётные записи и управление
  8. Терминал и zsh: командная строка macOS
  9. CLI на macOS: BSD-команды и отличия от Linux
  10. Настройка zsh: .zshrc, prompt, плагины
  11. Файловая система и права: POSIX, ACL, атрибуты, флаги
  12. Homebrew: пакетный менеджер для macOS
  13. Homebrew глубже: tap, Brewfile, services, обслуживание
  14. Другие пакеты: MacPorts, mas, Nix и App Store
  15. Установка приложений: .app, .dmg, .pkg и Gatekeeper
  16. Управление приложениями и их данные
  17. launchd и launchctl: система запуска macOS (вы здесь)
  18. Свои службы и периодические задачи на launchd
  19. Автоматизация: Shortcuts, AppleScript и osascript
  20. Конфигурация как код: defaults, plist и профили
  21. APFS: снапшоты, клоны, контейнеры и шифрование
  22. Управление дисками: diskutil, образы и форматирование
  23. Time Machine и резервное копирование
  24. Внешние диски, форматы и сетевые тома
  25. Модель безопасности macOS: SIP и целостность системы
  26. Gatekeeper, нотаризация и защита от вредоносов
  27. Приватность и TCC: разрешения на доступ
  28. FileVault: полнодисковое шифрование
  29. Связка ключей и управление секретами
  30. Сетевой экран и защита сети
  31. Сеть в macOS: настройка и инструменты
  32. Диагностика сети на Mac
  33. Общий доступ: SSH, экран, файлы, удалённое управление
  34. Процессы и ресурсы: мониторинг производительности
  35. Логи macOS: unified logging и диагностика
  36. Производительность и энергия: powermetrics, диагностика
  37. Энергия, сон и обслуживание системы
  38. Профили конфигурации и основы MDM
  39. Управление парком Mac: MDM, DDM и zero-touch (2026)
  40. Среда разработчика на Mac: Xcode CLT, git, версии
  41. Контейнеры на Mac: Docker Desktop, OrbStack, Apple container
  42. Виртуализация: Virtualization.framework, UTM, Linux и Windows
  43. Режимы загрузки и восстановление (Apple Silicon)
  44. Сброс, NVRAM, Safe Mode и сброс настроек
  45. Траблшутинг macOS: дерево решений по симптомам
  46. Место на диске и обслуживание системы
  47. Сквозной проект: настройка Mac как у профи + куда расти
Приходил из Linux и искал systemctl? Его тут нет. Пытался прописать задачу в cron, а она молча не выполняется? Cron на macOS - рудимент, Apple держит его для совместимости, но сама им не пользуется. Открыл Активити Монитор, увидел загадочный процесс, который жрёт батарею после удаления приложения, и не понимаешь, кто его держит живым? Всё это - про одну подсистему. Имя ей launchd, а её ключ управления - launchctl. Если ты хочешь рулить автозапуском службы на mac, ловить мусор от приложений и понимать, что вообще крутится в фоне, без этой главы дальше никуда.

Грубая аналогия для тех, кто из мира Linux: launchd - это systemd для macOS. Один процесс, который и init, и менеджер служб, и таймер, и сокет-активатор, и watchdog. Но устроен он иначе, со своими доменами и своим синтаксисом, и копировать привычки из systemd тут вредно.

launchd как PID 1: что это и почему вместо init/cron

После загрузки ядра XNU самый первый процесс в пространстве пользователя - это launchd, и у него PID 1. Проверь сам:

Код: Выделить всё

$ ps -p 1 -o pid,comm
  PID COMM
    1 /sbin/launchd
Раз он PID 1, на нём лежит вся ответственность прародителя: он усыновляет осиротевшие процессы, пожинает зомби и, главное, запускает и стережёт все системные службы. Появился launchd ещё в Mac OS X 10.4 Tiger в 2005 году и заменил собой целый зоопарк: классический BSD-шный init и rc-скрипты, демон-стартеры, inetd/xinetd для сетевых сервисов, at, и cron для расписаний. Идея была радикальной для своего времени: один декларативный супервизор вместо россыпи императивных скриптов.

Декларативный - ключевое слово. Ты не пишешь скрипт запуска. Ты описываешь службу в property list (plist) - что запустить, от чьего имени, когда и при каких условиях разбудить. launchd сам решает, когда стартовать и перезапускать. Это и есть фундаментальное отличие от cron: cron тупо дёргает команду по времени, даже если машина спала и момент пропущен. launchd умеет ленивый запуск (по сокету, по обращению к Mach-порту, по появлению файла), умеет держать службу живой (KeepAlive), умеет догонять пропущенный по сну интервал (StartCalendarInterval сработает после пробуждения). Поэтому Apple не развивает cron - launchd просто мощнее.

Изображение

LaunchDaemons против LaunchAgents: демоны и агенты

Главная развилка, в которой все путаются. Службы launchd бывают двух пород, и разница не косметическая - она про контекст исполнения и безопасность.

LaunchDaemons (демоны) - системные службы. Запускаются от root (или от пользователя, указанного ключом UserName) ещё до логина, при старте системы. У них нет доступа к графической сессии: показать окно, диалог или иконку в меню-баре демон не может в принципе. Это бэкенд - сетевые сервисы, БД, мониторинг, обновлятели. launchdaemons лежат в двух каталогах:
  • Код: Выделить всё

    /Library/LaunchDaemons
    - демоны от админа и стороннего софта;
  • Код: Выделить всё

    /System/Library/LaunchDaemons
    - демоны самой macOS (трогать нельзя, защищено SIP).
LaunchAgents (агенты) - службы в контексте конкретного залогиненного пользователя. Стартуют после входа в систему, работают от имени этого пользователя и имеют доступ к GUI: связке ключей, домашней папке, окнам. Всё, что рисует что-то на экране или живёт в твоей сессии, - это агент. launchagents mac ищутся в трёх местах:
  • Код: Выделить всё

    ~/Library/LaunchAgents
    - агенты, которые поставил сам пользователь (только для него);
  • Код: Выделить всё

    /Library/LaunchAgents
    - агенты от админа для всех пользователей;
  • Код: Выделить всё

    /System/Library/LaunchAgents
    - агенты macOS (под защитой SIP).
Запомни правило на пальцах: нужен GUI или связка ключей пользователя - это агент; нужно работать до логина и от root - это демон. Самая частая ошибка - засунуть в LaunchDaemons софт, который пытается нарисовать окно. Он либо молча не работает, либо валится. И обратное: положить в LaunchAgents то, что должно крутиться круглосуточно без логина, - не взлетит на сервере без активной сессии.

Минимальный plist агента выглядит так:

Код: Выделить всё

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>ru.cyberlake.backup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/backup.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key><integer>3</integer>
        <key>Minute</key><integer>0</integer>
    </dict>
    <key>StandardErrorPath</key>
    <string>/tmp/backup.err</string>
</dict>
</plist>
Три обязательных по смыслу вещи: Label - уникальный идентификатор службы (обратная доменная нотация - хороший тон), ProgramArguments (или Program) - что запускать, и условие запуска - тут StartCalendarInterval вместо cron-строки. RunAtLoad=true заставит стартовать сразу при загрузке plist, KeepAlive - держать живым и перезапускать после падения.

launchctl: современный синтаксис против устаревшего load/unload

Вот где живёт настоящая путаница, и где интернет завален устаревшими советами. Раньше всё делали через

Код: Выделить всё

launchctl load /path/to.plist
и

Код: Выделить всё

launchctl unload
. Эти команды объявлены устаревшими начиная с OS X 10.10 Yosemite. Они до сих пор работают как заглушки совместимости, но ведут себя непредсказуемо и игнорируют домены. На современной macOS (вплоть до Tahoe 26) пользуйся новым API, построенным вокруг понятия домен.

Домен - это пространство имён, в котором живёт служба. Их три, и они задают контекст:
  • system - привилегированный домен root, сюда попадают LaunchDaemons. Управление требует sudo. Таргет:

    Код: Выделить всё

    system/<Label>
    .
  • gui/<uid> - агенты в графической сессии пользователя. Активен, только пока пользователь залогинен в GUI. Таргет:

    Код: Выделить всё

    gui/501/<Label>
    .
  • user/<uid> - пользовательский домен без привязки к GUI-логину.
UID узнаёшь командой (у первого пользователя обычно 501). Теперь сами команды:
  • Код: Выделить всё

    launchctl bootstrap <домен> <путь.plist>
    - загрузить службу в домен (замена load). Для агента:

    Код: Выделить всё

    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ru.cyberlake.backup.plist
    . Для демона:

    Код: Выделить всё

    sudo launchctl bootstrap system /Library/LaunchDaemons/...
  • Код: Выделить всё

    launchctl bootout <домен>/<Label>
    - выгрузить (замена unload). Например

    Код: Выделить всё

    launchctl bootout gui/$(id -u)/ru.cyberlake.backup
  • Код: Выделить всё

    launchctl enable <таргет>
    / - включить/выключить службу. Это постоянное состояние, переживает перезагрузку; disable не даст службе стартовать даже после bootstrap.
  • Код: Выделить всё

    launchctl kickstart -k <таргет>
    - немедленно запустить службу; флаг -k сперва убивает текущий экземпляр и перезапускает (замена устаревших start/stop).
  • Код: Выделить всё

    launchctl print <таргет>
    - вывалить всю метаинформацию о службе или домене.
Тонкость: enable/disable и bootstrap/bootout - разные оси. bootout убирает службу из работающего домена прямо сейчас, но после ребута plist подхватится снова. disable пишет постоянную метку отключения. Чтобы заглушить службу навсегда, дизейбли её, а не просто бутаут. И ещё грабли: bootout с включённым SIP не сработает на службах из /System/Library - это by design.

Разведка: list и print, ловим фоновые процессы и мусор

Самое полезное в повседневке - смотреть, что вообще запущено. Базовый обзор:

Код: Выделить всё

$ launchctl list | head -5
PID	Status	Label
1234	0	ru.cyberlake.backup
-	0	com.apple.Spotlight
567	0	homebrew.mxcl.postgresql@16
Три колонки. PID - номер процесса прямо сейчас; прочерк значит, что служба загружена, но не выполняется в данный момент (ждёт триггера или это разовая задача). Status - код выхода последнего запуска: 0 - всё хорошо; ненулевое число - служба упала с таким кодом или была убита сигналом (тогда часто видишь отрицательное значение - номер сигнала со знаком минус). Label - имя. Если видишь службу с ненулевым статусом, который постоянно меняется, и PID то появляется, то нет - это флапающий сервис, который падает и перезапускается по KeepAlive. Классика для отлова битого автозапуска.

Для глубокого разбора - print по таргету:

Код: Выделить всё

$ launchctl print gui/$(id -u)/ru.cyberlake.backup
gui/501/ru.cyberlake.backup = {
	active count = 1
	state = running
	program = /usr/local/bin/backup.sh
	last exit code = 0
	...
}
Тут видно реальный путь к бинарю, состояние, код последнего выхода, ограничения по памяти, переменные окружения. Когда служба ведёт себя странно, print - первый инструмент. Важно: формат вывода print Apple официально не стабилизирует, парсить его скриптами - плохая идея, читай глазами.

А теперь главное практическое применение для обычного пользователя. Удалил приложение перетаскиванием в корзину, а в фоне что-то осталось? Виноваты как раз агенты и демоны, которые установщик разбросал по каталогам и не убрал. Найди мусор так:

Код: Выделить всё

$ ls -1 ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons
/Library/LaunchDaemons:
com.adobe.acc.installer.v2.plist
com.someapp.updater.plist
Видишь plist от софта, который давно снёс? Это и есть фоновый зомби. Выгрузи и удали:

Код: Выделить всё

sudo launchctl bootout system/com.someapp.updater
, затем

Код: Выделить всё

sudo rm /Library/LaunchDaemons/com.someapp.updater.plist
. После этого процесс перестанет воскресать. Это самый частый рецепт от "после удаления приложения что-то жрёт CPU".

Связь с brew services: что Homebrew делает под капотом

Если ставишь сервисы через Homebrew (на Apple Silicon - из

Код: Выделить всё

/opt/homebrew
), то

Код: Выделить всё

brew services
- это просто удобная обёртка над launchctl. Никакой магии: brew генерирует plist с меткой вида

Код: Выделить всё

homebrew.mxcl.<formula>
и скармливает его launchd.

Код: Выделить всё

$ brew services start postgresql@16
==> Successfully started `postgresql@16` (label: homebrew.mxcl.postgresql@16)
$ brew services list
Name              Status  User       File
postgresql@16     started khovanskiy ~/Library/LaunchAgents/homebrew.mxcl.postgresql@16.plist
Логика выбора домена у brew умная:

Код: Выделить всё

brew services start foo
без sudo кладёт plist в

Код: Выделить всё

~/Library/LaunchAgents
и грузит в домен gui/<uid> - сервис живёт, пока ты залогинен. А

Код: Выделить всё

sudo brew services start foo
положит plist в

Код: Выделить всё

/Library/LaunchDaemons
и поднимет в домене system - сервис стартует при загрузке машины ещё до логина, что и нужно на сервере. Под капотом brew зовёт ровно те команды, что ты учил выше: bootstrap, enable, иногда kickstart. Знаменитая ошибка

Код: Выделить всё

launchctl enable gui/501/homebrew.mxcl.mysql exited with 112
- это как раз сбой нижнего слоя launchctl, который brew честно прокидывает наверх. Понимая launchd, ты дебажишь brew services руками, а не гадаешь.

Типичные грабли
  • Льёшь старые load/unload. Работает через раз и игнорирует домены. Перейди на bootstrap/bootout.
  • Перепутал агент и демон. Демон с GUI не работает; агент без активного логина не стартует на сервере. Сначала реши, нужен ли GUI.
  • bootout вместо disable. Выгрузил, а после ребута служба вернулась. Для постоянного отключения нужен disable.
  • Права и владелец plist. Демон в /Library/LaunchDaemons должен принадлежать root:wheel с правами 644 и не быть group/world-writable, иначе launchd откажется его грузить из соображений безопасности. Чини:

    Код: Выделить всё

    sudo chown root:wheel ...; sudo chmod 644 ...
  • Забыл про SIP. Всё в /System/Library защищено System Integrity Protection. bootout и rm там не пройдут, и это правильно.
  • Используешь cron и удивляешься. На ноутбуке, который спит, cron пропускает события. launchd с StartCalendarInterval догонит пропущенное после пробуждения.
Мини-лаба (повтори руками)
  • Узнай свой UID: . Запомни число (обычно 501).
  • Посмотри, что грузится у тебя как агент:

    Код: Выделить всё

    ls -la ~/Library/LaunchAgents
    . Пусто - нормально для чистой системы.
  • Сделай тестовый агент, который раз в минуту пишет в лог. Создай

    Код: Выделить всё

    ~/Library/LaunchAgents/ru.cyberlake.lab.plist
    с Label ru.cyberlake.lab, ProgramArguments из

    Код: Выделить всё

    /bin/sh -c 'date >> /tmp/lab.log'
    , StartInterval 60 и RunAtLoad true.
  • Загрузи:

    Код: Выделить всё

    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ru.cyberlake.lab.plist
  • Проверь, что он в списке:

    Код: Выделить всё

    launchctl list | grep cyberlake
    и разбери метаданные:

    Код: Выделить всё

    launchctl print gui/$(id -u)/ru.cyberlake.lab
  • Дёрни вручную, не дожидаясь минуты:

    Код: Выделить всё

    launchctl kickstart -k gui/$(id -u)/ru.cyberlake.lab
    , затем

    Код: Выделить всё

    cat /tmp/lab.log
    - увидишь строку с датой.
  • Убери за собой:

    Код: Выделить всё

    launchctl bootout gui/$(id -u)/ru.cyberlake.lab
    и удали plist с логом.
Контрольные вопросы
  • Тебе нужен сервис, который поднимается при включении сервера ещё до логина и работает от root. Это агент или демон, и в какой каталог положишь plist?
  • Чем по смыслу отличается

    Код: Выделить всё

    launchctl bootout
    от

    Код: Выделить всё

    launchctl disable
    ? Какая из команд переживёт перезагрузку?
  • В выводе

    Код: Выделить всё

    launchctl list
    у службы прочерк в колонке PID и статус 0. Что это значит - служба сломана?
  • Что реально делает

    Код: Выделить всё

    sudo brew services start
    под капотом и чем отличается от того же без sudo?
Итог

launchd - это PID 1 и единый менеджер служб macOS, заменивший init, inetd и cron сразу. Службы бывают двух пород: демоны (LaunchDaemons, root, до логина, без GUI) и агенты (LaunchAgents, пользователь, после логина, с доступом к GUI), и лежат они в системных, общесистемных и пользовательских каталогах Library. Управляй ими современным launchctl - bootstrap/bootout/enable/disable/kickstart/print с явными доменами system, gui/uid и user/uid, а не древними load/unload. Умение читать launchctl list и print плюс ревизия каталогов LaunchAgents/LaunchDaemons - это твой главный навык против фоновых зомби, оставленных удалённым софтом. А brew services - всего лишь дружелюбная обёртка над всем этим, и теперь ты видишь, что под ней.
👍3 ❤️4 🔥3 😄 🤔2
Аватара пользователя
simtur
Сообщения: 1
Зарегистрирован: 14 май 2026, 19:40

Re: launchd и launchctl: система запуска macOS

Сообщение simtur »

Вот теперь понял, почему мой апдейтер от давно удалённой проги воскресал - plist висел в /Library/LaunchDaemons. Сделал bootout + rm, и тишина. Спасибо!
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
jamie10
Сообщения: 1
Зарегистрирован: 27 май 2026, 13:35

Re: launchd и launchctl: система запуска macOS

Сообщение jamie10 »

А можно вопрос про разницу gui/uid и user/uid? Когда что выбирать, если логин всегда есть? Из урока понял что gui привязан к графической сессии, но не уловил кейс для чистого user домена.
👍1 ❤️ 🔥1 😄 🤔1
Ответить
← Предыдущая глава
Управление приложениями и их данные
Следующая глава →
Свои службы и периодические задачи на launchd

Все главы курса «macOS для профи: администрирование, терминал и Homebrew»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Что такое macOS и Unix под капотомlaunchd и автозагрузка на MacАвтоматизация на macOS

Вернуться в «macOS для профи: администрирование, терминал и Homebrew»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость