← Linux: загрузка
12 мин · средне · Урок 5 из 7

systemd как init: targets и порядок

systemd как init: targets, unit-файлы, параллельная загрузка.

Кто такой PID 1

К этому моменту цепочка загрузки уже прошла: firmware -> загрузчик -> ядро -> initramfs. Ядро распаковало initramfs в rootfs на tmpfs (при включённом CONFIG_TMPFS), нашло настоящий корень, смонтировало его и передало управление первому пользовательскому процессу.

Этот процесс — systemd, получивший PID 1. systemd — демон, который управляет остальными демонами. Демон здесь означает фоновую службу без терминала: sshd, cron, сетевой стек. systemd их запускает, следит за ними и перезапускает при падении.

$ ps -p 1 -o pid,comm
  PID COMMAND
    1 systemd

Unit-файлы вместо скриптов

Инструкции инициализации у systemd лежат в декларативных unit-файлах, а не в shell-скриптах. Декларативный означает, что файл описывает желаемое состояние (что запустить, после чего, при каких условиях), а не последовательность команд, которую надо выполнить сверху вниз.

Один unit — одна сущность: служба, точка монтирования, сокет, таймер или цель. Посмотреть загруженные юниты можно так:

systemctl list-units --type=service   # активные службы
systemctl status sshd                  # состояние конкретной службы

Как устроен сам unit-файл, из каких секций собран и что значит каждое поле, подробно разбирается в уроке linux-admin/systemd-units. Здесь мы на этом не останавливаемся.

Targets: состояния системы

Целое состояние системы systemd описывает целью. Target — это именованная группа юнитов, которую нужно привести в активное состояние. Достижение цели значит, что все её зависимости запущены.

Несколько целей, которые встречаются постоянно:

TargetЧто означает
sysinit.targetбазовая инициализация: монтирование, swap, journald
basic.targetсокеты, таймеры, пути — фундамент для служб
multi-user.targetсерверный режим, службы без графики
graphical.targetвсё из multi-user плюс графическая сессия

Какая цель включается по умолчанию, задаётся отдельно:

$ systemctl get-default
multi-user.target

Список загруженных целей смотрят так:

$ systemctl list-units --type=target
UNIT                STATE   SUB    DESCRIPTION
basic.target        loaded  active Basic System
multi-user.target   loaded  active Multi-User System
sysinit.target      loaded  active System Initialization

Порядок задаётся графом, а не очередью

Вот ключевой момент. graphical.target тянет multi-user.target, тот тянет basic.target, а тот тянет sysinit.target. Эта цепочка зависимостей соблюдается: графическая сессия не поднимется раньше базовой инициализации.

Но это не строгая последовательная очередь. Загрузка сильно параллельна: systemd строит граф зависимостей и запускает всё, что уже может стартовать, одновременно. Порядок между двумя независимыми юнитами недетерминирован. Сегодня первым поднимется один, завтра другой.

$ systemctl list-dependencies graphical.target
graphical.target
* |-multi-user.target
* | |-basic.target
* | | |-sysinit.target

Отступ в выводе показывает зависимости, а не время старта. Юниты внутри одного уровня стартуют параллельно.

Две частые ловушки

Первая: «targets идут строго друг за другом, как ступеньки». Нет. Зависимость упорядочивает только связанные юниты. Всё остальное systemd запускает так параллельно, как позволяет граф. Насколько параллельно прошла загрузка, видно здесь:

$ systemd-analyze
Startup finished in 2.1s (kernel) + 4.8s (userspace) = 6.9s
graphical.target reached after 4.7s in userspace

Вторая: «target это то же самое, что runlevel, один в один». Похожая идея (уровень работы системы), но не совпадение. Runlevel был жёстким номером с фиксированным набором скриптов. Target — узел в графе зависимостей, целей может быть сколько угодно, и своя цель определяется через unit-файлы, а не через номер.

systemctl isolate multi-user.target   # переключить систему в серверный режим
systemctl isolate graphical.target     # поднять графическую сессию

Что важно запомнить

Первый пользовательский процесс — systemd, PID 1, демон над демонами.

Инструкции инициализации живут в декларативных unit-файлах, не в shell-скриптах.

Target — именованная группа юнитов; multi-user.target без графики, graphical.target с ней. Дефолт смотрят через systemctl get-default.

Цель достигается по графу зависимостей. Цепочка graphical -> multi-user -> basic -> sysinit соблюдается, но загрузка сильно параллельна и порядок независимых юнитов недетерминирован.

Target решает ту же задачу, что runlevel в старых init-системах, но устроен иначе: зависимости вместо номера уровня.

В следующем уроке — udev: как система обнаруживает устройства и наполняет /dev.

Прошли материал до конца? Отметьте урок пройденным.