Завершение работы системы

TL;DR: Shutdown — это загрузка наоборот. init просит процессы завершиться, неответивших убивает SIGTERM → SIGKILL, затем демонтирует файловые системы, перемонтирует корневую в read-only, записывает буферы на диск (sync) и вызывает reboot(2) для выключения или перезагрузки.

Зачем это знать

Некорректное завершение (выдернуть питание, убить PID 1) — это потерянные данные в буферах, повреждённые файловые системы и сервисы, которые не сохранили состояние. Понимание процесса помогает:

  • Диагностировать зависание при shutdown (какой сервис не отвечает на SIGTERM)
  • Настраивать graceful shutdown для своих приложений
  • Понимать почему kill -9 — крайняя мера, а не первая

Последовательность завершения

Независимо от реализации init (systemd, SysVinit), процедура выполняется в следующем порядке:

  1. init просит каждый процесс полностью завершиться
  2. Если процесс не отвечает через некоторое время, init принудительно завершит его, сначала применив сигнал TERM
  3. Если сигнал TERM не работает, init использует сигнал KILL для любых оставшихся активными процессов
  4. Система блокирует системные файлы на своих местах и выполняет остальные приготовления к завершению работы
  5. Система демонтирует все файловые системы, кроме корневой
  6. Система заново монтирует корневую файловую систему в режиме только для чтения
  7. Система записывает все буферизованные данные в файловую систему с помощью программы sync
  8. На последнем шаге система указывает ядру перезагрузиться или остановиться с помощью системного вызова reboot(2). Это может быть сделано посредством init или вспомогательной программы, например reboot, halt или poweroff

В systemd это означает активацию юнитов выключения (poweroff.target, reboot.target). В System V — изменение уровня выполнения на 0 (остановка) или 6 (перезагрузка).

Ключевые этапы

1. Команда пользователя → init

# Все три способа эквивалентны:
sudo shutdown -h now          # классическая команда
sudo systemctl poweroff       # через systemctl (systemd)
sudo poweroff                 # обёртка

shutdown сообщает init о начале процесса завершения работы. В systemd это активация юнитов выключения, в System V — смена runlevel.

Командаsystemd targetSysV runlevelРезультат
poweroffpoweroff.target0Остановка + выключение питания
rebootreboot.target6Остановка + перезагрузка
halthalt.target0Остановка CPU, питание не выключается

2–3. Корректное завершение → SIGTERM → SIGKILL

init просит каждый процесс корректно завершиться. Если процесс не отвечает — отправляет SIGTERM. Если SIGTERM не помогает — SIGKILL.

В systemd это реализовано так: для каждого сервиса выполняется ExecStop= из unit-файла, а если не задан — отправляется SIGTERM. Таймаут ожидания между SIGTERM и SIGKILL определяется TimeoutStopSec (по умолчанию DefaultTimeoutStopSec=90s в /etc/systemd/system.conf).

# Пример: сервис с кастомным graceful shutdown
[Service]
ExecStart=/usr/bin/myapp
ExecStop=/usr/bin/myapp --graceful-stop  # кастомная остановка
TimeoutStopSec=30                        # ждать 30с вместо дефолтных 90

Важно: Приложение, которое перехватывает SIGTERM и корректно завершается (закрывает соединения к БД, дописывает логи) — это graceful shutdown. Приложение, которое игнорирует SIGTERM — получит SIGKILL через таймаут и потеряет несохранённые данные. SIGKILL процесс не может перехватить.

4–7. Файловые системы и sync

После уничтожения всех пользовательских процессов:

  1. Система блокирует системные файлы и выполняет приготовления к завершению
  2. Демонтирует все файловые системы, кроме корневой
  3. Корневая ФС перемонтируется в режиме read-only
  4. sync — запись всех буферизованных данных на диск

Именно поэтому «выдернуть питание» опасно: данные в буферах ядра, ещё не записанные на диск, будут потеряны. Journaling-ФС (ext4, XFS) минимизируют повреждения, но гарантируют целостность только метаданных — не пользовательских данных.

8. Системный вызов reboot(2)

На последнем шаге система указывает ядру перезагрузиться или остановиться с помощью системного вызова reboot(2). Это может быть сделано самим init или вспомогательной программой (reboot, halt, poweroff).

shutdown с задержкой и уведомлением

# Выключить через 10 минут с сообщением пользователям
sudo shutdown -h +10 "Server going down for maintenance"
 
# Перезагрузка в конкретное время
sudo shutdown -r 23:00 "Scheduled reboot at 23:00"
 
# Отменить запланированный shutdown
sudo shutdown -c "Maintenance postponed"

shutdown с задержкой отправляет wall-сообщение всем залогиненным пользователям и создаёт файл /run/nologin, который запрещает новые логины (кроме root).

Как это работает на практике

Graceful shutdown для Node.js приложения

// Перехват SIGTERM — корректное завершение
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully...');
 
  // 1. Перестать принимать новые соединения
  server.close();
 
  // 2. Дождаться завершения текущих запросов
  await activeConnections.drain();
 
  // 3. Закрыть подключение к БД
  await db.disconnect();
 
  // 4. Выход с кодом 0 (успех)
  process.exit(0);
});

Systemd unit с корректным shutdown

[Service]
ExecStart=/usr/bin/node /opt/app/server.js
# KillSignal по умолчанию SIGTERM — его и перехватываем
TimeoutStopSec=30         # даём 30с на graceful shutdown
KillMode=mixed            # SIGTERM главному процессу,
                          # SIGKILL дочерним после таймаута

KillMode определяет кому отправляется сигнал:

KillModeПоведение
control-group (default)Сигнал всем процессам в cgroup
mixedSIGTERM главному, SIGKILL остальным после таймаута
processСигнал только главному процессу
nonesystemd не убивает процессы (опасно)

Подводные камни

Важно: Если сервер «зависает при shutdown» — причина почти всегда в сервисе с длинным TimeoutStopSec или процессе, игнорирующем SIGTERM.

ПроблемаСимптомРешение
Shutdown висит долго«A stop job is running for…»Найти сервис: systemctl list-jobs. Уменьшить TimeoutStopSec в unit-файле или исправить обработку SIGTERM в приложении
Данные теряются при перезагрузкеПриложение не обрабатывает SIGTERMДобавить обработчик сигнала + graceful shutdown
ФС повреждена после сбоя питанияfsck при загрузкеИспользовать journaling FS (ext4/XFS), UPS
halt не выключает машинуЭкран зависает на «System halted»Использовать poweroff вместо halthalt не отправляет ACPI сигнал выключения
NFS/сетевые ФС не размонтируютсяShutdown зависает на umount_netdev в fstab → systemd размонтирует до остановки сети

Связанные материалы