Этот выпуск «Linux под капотом» про PID 1: как мы пришли от SysV init к systemd, и почему от этого зависит половина типичных “не стартует после ребута”. Буду считать, что у тебя Debian/Ubuntu и systemd как основной init. Если у тебя OpenRC/runit — смысл про “первый процесс” тот же, но команды и конфиги будут другими.
Важно: если ты видишь, что строки в код-блоках “склеиваются” в одну строку, это почти всегда минификация HTML (кэш-плагин/оптимизатор). Для страниц со статьями лучше отключить HTML-minify или исключить обработку содержимого внутри <pre>.
Контекст и зачем это знать
Когда “сломался Linux”, обычно сломалось ожидание: сеть ещё не поднялась, диск ещё не примонтирован, сокет ещё не слушает, конфиг не появился, права не те, окружение другое. Сервис может быть исправен, но стартовать не там и не тогда.
В Linux есть один процесс, который важнее остальных: PID 1. Он стартует первым в userspace и дальше управляет запуском сервисов и переходом системы в рабочее состояние. Исторически это был init из SysV-модели. Сейчас у большинства дистрибутивов PID 1 — systemd.
Если ты понимаешь, как работает PID 1, ты перестаёшь “перезагружать ещё раз на удачу”. Вместо этого ты быстро отвечаешь на вопросы: кто кого тянет, кто чего ждёт, и на каком шаге система застряла.
Как это устроено
SysV init — это линейная модель. Есть скрипты в /etc/init.d. На разных стадиях загрузки выполняются симлинки из каталогов вида /etc/rcS.d и /etc/rc?.d, а порядок задаётся именами ссылок (S01…, S02…, S99…).
Плюс такой схемы — простота: “вот список, вот порядок”. Минус — слабое описание зависимостей. “Мне нужна сеть” превращается в “надеюсь, что сетевой скрипт успел раньше”. Чем больше сервисов, тем больше скрытых ожиданий и случайностей.
systemd — это менеджер юнитов. Юнит — ini-файл, который описывает объект: service, socket, timer, mount, device, target и другие. Юниты пакетов обычно лежат в /lib/systemd/system, локальные — в /etc/systemd/system. Правки без ломания пакетов делаются через drop-in overrides: /etc/systemd/system/имя.service.d/override.conf.
Самая частая ошибка в логике systemd: путать “зависимость” и “порядок”. Wants=/Requires= отвечают за “подтянуть/требовать”. After=/Before= отвечают только за “кто раньше/кто позже”, но сами по себе никого не запускают. Если сервис должен и подтянуть зависимость, и подождать её — часто нужны обе группы директив.
Targets — это ориентиры состояния, которые заменили runlevel’ы. Это не “режимы по магии”, а юниты, которые собирают набор зависимостей. Например, multi-user.target обычно означает “система в рабочем режиме без GUI”, а graphical.target — “с GUI”.
journal (journald) — журнал событий, который удобно читать по загрузкам и по конкретным юнитам. Вместо “ищи в /var/log и гадай” ты смотришь историю ровно того сервиса, который интересует.
Как посмотреть это на живой системе
- Проверь PID 1 через ps. Одна команда, без “хвостов”.
ps -p 1 -o pid,comm,args
- Если хочется вообще не зависеть от ps, посмотри имя процесса PID 1 через
/proc.
cat /proc/1/comm
- Посмотри, с какими аргументами запущен PID 1 (тоже через
/proc).
tr '\0' ' ' < /proc/1/cmdline; echo
- Проверь, на что указывает
/sbin/init.
readlink -f /sbin/init
- Проверь, какой бинарник реально запущен у PID 1.
readlink -f /proc/1/exe
- Посмотри целевой target по умолчанию (аналог “куда система должна прийти после загрузки”).
systemctl get-default
- Посмотри активные targets прямо сейчас.
systemctl list-units --type=target --state=active
- Проверь, какие юниты в ошибке (быстрый “что горит”).
systemctl --failed
- Открой предупреждения и ошибки текущей загрузки.
journalctl -b -0 -p warning..alert --no-pager
- Разбери конкретный сервис: статус и последние строки лога (пример: ssh).
systemctl status ssh.service --no-pager
- Посмотри фактическую конфигурацию юнита, включая overrides.
systemctl cat ssh.service
- Проверь зависимости сервиса и обратные зависимости (кто его тянет при старте).
systemctl list-dependencies ssh.service
systemctl list-dependencies --reverse ssh.service
Типовые проблемы и симптомы
Сервис запускается руками, но не стартует при загрузке. Частая причина — окружение. В интерактивной shell у тебя один PATH и переменные, а у systemd среда строже. В итоге ExecStart падает из-за “не нашёл бинарник”, “не видит конфиг”, “нет прав”.
Добавили After=, но ничего не поменялось. Это классика: After= не подтягивает зависимости. Он только задаёт порядок, если оба юнита запускаются. Для “подтянуть” нужны Wants= или Requires=.
“Сеть есть, но не для сервиса”. Некоторые приложениям нужна не настройка интерфейса, а реально доступная сеть. Тут всплывают ожидания вокруг network-online.target и то, кто в системе отвечает за “online”.
Зависание на “A start job is running…”. Обычно ждут монтирование (mount unit), устройство (device unit) или таймаут. Это диагностируется через journal по текущей загрузке: видно, что именно ждут и сколько осталось.
И отдельная боль, которую легко поймать на практике: две команды случайно запускаются как одна. Shell честно передаёт “вторую команду” как аргументы первой, и ошибки выглядят как будто сломалась утилита. Лекарство простое: команды в инструкции показывать отдельными блоками и запускать отдельными строками.
Мини-лабораторка
Сделаем лабораторку без демонов и вечных циклов: таймер systemd, который раз в 2 минуты пишет строку в journal через logger. Получится связка timer → service → journal, и ты увидишь её в диагностике так же, как видишь реальные сервисы.
- Создай файл юнита сервиса
/etc/systemd/system/kapotom-hello.serviceчерез sudoedit.
sudoedit /etc/systemd/system/kapotom-hello.service
Вставь в файл содержимое и сохрани.
[Unit] Description=Linux pod kapotom: hello to journal [Service] Type=oneshot ExecStart=/usr/bin/logger -t kapotom-hello hello_from_systemd_timer
- Создай файл таймера
/etc/systemd/system/kapotom-hello.timerчерез sudoedit.
sudoedit /etc/systemd/system/kapotom-hello.timer
Вставь содержимое таймера и сохрани.
[Unit] Description=Linux pod kapotom: periodic hello [Timer] OnBootSec=30s OnUnitActiveSec=2min Unit=kapotom-hello.service [Install] WantedBy=timers.target
- Перечитай конфигурацию systemd и включи таймер.
sudo systemctl daemon-reload sudo systemctl enable --now kapotom-hello.timer
- Проверь, когда таймер сработает и что он активен.
systemctl status kapotom-hello.timer --no-pager
systemctl list-timers --all
- Посмотри записи в журнале по тегу и по юниту.
journalctl -t kapotom-hello -b --no-pager -n 50
journalctl -u kapotom-hello.service -b --no-pager -n 50
- Удали лабораторку аккуратно: выключи таймер, удали файлы, перечитай конфигурацию.
sudo systemctl disable --now kapotom-hello.timer sudo rm -f /etc/systemd/system/kapotom-hello.service /etc/systemd/system/kapotom-hello.timer
sudo systemctl daemon-reload
Источники и куда копать дальше
- systemd(1): роль PID 1 и общая модель
- systemd.unit: Wants/Requires и After/Before
- systemctl: управление юнитами и диагностика
- journalctl: журнал по загрузкам и по юнитам
- systemd.timer: таймеры
- man7: proc(5) как источник правды о процессах
- ArchWiki: systemd (практика и кейсы)
Что попробовать руками
- Собери “паспорт” PID 1 на своей системе.
ps -p 1 -o pid,comm,args
readlink -f /sbin/init
- Сделай то же самое через
/proc, чтобы не зависеть от утилит.
cat /proc/1/comm
tr '\0' ' ' < /proc/1/cmdline; echo
readlink -f /proc/1/exe
- Возьми один важный для тебя сервис и разложи его “по полочкам”: конфиг, зависимости, логи. Для примера можно nginx или docker.
systemctl status nginx.service --no-pager
systemctl cat nginx.service
systemctl list-dependencies nginx.service
journalctl -u nginx.service -b --no-pager -n 120
- Если что-то не стартует, начни с двух быстрых вопросов: “что упало” и “что в журнале этой загрузки”.
systemctl --failed
journalctl -b -0 -p warning..alert --no-pager -n 200
