open atlas
↑ К треку
Linux: операционная система LIN · 08 · 02

Сигналы — углублённо

Сигналы — асинхронные уведомления, доставляемые ядром процессу. SIGTERM перехватывается и запрашивает корректное завершение; SIGKILL не перехватывается и принудительно завершает. Процесс в D-состоянии игнорирует даже SIGKILL.

LIN Middle ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

Ты уже знаешь kill -9 PID из курса по CLI. Но опытные операторы тянутся к -9 последними, а не первыми — потому что SIGKILL обходит shutdown-хуки, оставляет временные файлы нетронутыми и может повредить незаконченные записи. Правильный сигнал зависит от того, что ты хочешь от процесса. SIGTERM вежливо просит остановиться; SIGKILL принуждает. SIGHUP говорит демону перечитать конфигурацию. SIGSTOP замораживает процесс без убийства. А когда нажимаешь Ctrl-C в терминале, сигнал идёт ко всей группе процессов — не только к одному PID.

Цель

После этого урока ты сможешь объяснить разницу между SIGTERM и SIGKILL, объяснить почему SIGKILL нельзя перехватить, знать почему D-состояние игнорирует SIGKILL, использовать SIGHUP для перезагрузки демона без перезапуска, объяснить группы процессов и сессии, и понимать почему Ctrl-C убивает весь конвейер.

1

Сигналы — асинхронные уведомления: ядро прерывает процесс и доставляет их.

Сигнал — небольшое целое число, посылаемое ядром (или другим процессом с нужными правами) целевому процессу. Ядро прерывает цель на границе инструкции и вызывает зарегистрированный обработчик. Если обработчик не установлен — выполняется действие по умолчанию (для большинства сигналов: завершить процесс).

# Список всех сигналов:
kill -l
# 1) SIGHUP    2) SIGINT    3) SIGQUIT   9) SIGKILL  15) SIGTERM
# 18) SIGCONT  19) SIGSTOP  20) SIGTSTP ...

# Сигнал по номеру или имени — эквивалентно:
kill -15 1234      # SIGTERM по номеру
kill -TERM 1234    # SIGTERM по имени
kill -SIGTERM 1234 # SIGTERM с префиксом SIG

# Действия по умолчанию:
# SIGTERM (15) → завершить
# SIGINT  (2)  → завершить (от Ctrl-C)
# SIGHUP  (1)  → завершить (демоны часто перехватывают как "перезагрузить")
# SIGKILL (9)  → завершить (НЕЛЬЗЯ перехватить/игнорировать — ядро)
# SIGSTOP (19) → остановить (НЕЛЬЗЯ перехватить/игнорировать — ядро)
# SIGCONT (18) → продолжить (возобновляет остановленный процесс)
2

SIGTERM против SIGKILL: правильный инструмент для задачи.

# SIGTERM (15): вежливый запрос на завершение
# Процесс МОЖЕТ:
# - Перехватить и выполнить код очистки
# - Сбросить буферы, закрыть соединения, удалить временные файлы
# - Игнорировать (плохо написанные процессы иногда делают это)
#
# Паттерн оператора: отправить SIGTERM, подождать, затем SIGKILL если нужно:
kill -TERM 1234
sleep 10
kill -0 1234 2>/dev/null && kill -KILL 1234
# kill -0 не отправляет сигнал — только проверяет существование процесса

# SIGKILL (9): принудительное завершение — без исключений
# Ядро принудительно убирает процесс из планировщика.
# Обработчики сигналов процесса НИКОГДА не вызываются.
# Никакой очистки, никакого сброса буферов, никакого корректного завершения.
kill -KILL 1234    # или: kill -9 1234

# SIGHUP (1): демоны трактуют как "перезагрузить конфигурацию"
# Применение новой конфигурации nginx без потери соединений:
sudo kill -HUP $(cat /var/run/nginx.pid)
# nginx перечитывает конфигурацию, плавно сливает старых воркеров, запускает новых

# Или через systemctl:
sudo systemctl reload nginx   # отправляет SIGHUP внутренне
3

Почему D-состояние игнорирует даже SIGKILL.

SIGKILL — единственный сигнал, который ядро гарантированно не может игнорировать — кроме случаев, когда процесс в D-состоянии (непрерываемый сон). В D-состоянии процесс находится внутри непреемптивной операции ядра — обычно ожидает блочного I/O, NFS-операции или блокировки ядра.

# Найти D-состояние процессы:
ps aux | awk '$8 ~ /D/'

# Если процесс застрял в D-состоянии минутами — проблема в подсистеме I/O:
# 1. Проверить зависшие монтирования:
mount | grep nfs
df -h  # если df зависает — NFS-монтирование не отвечает

# 2. Проверить логи ядра на I/O-ошибки:
sudo dmesg | tail -30 | grep -E 'error|timeout|hung|reset'
# "task ... blocked for more than 120 seconds" → застрявший I/O

# 3. Исправление — устранить I/O-проблему (отмонтировать NFS, заменить диск),
#    а НЕ отправлять больше сигналов.
#
# Причина иммунитета: ядро доставляет сигналы в безопасных точках —
# при возврате из syscall или точке вытеснения. В непрерываемом сне процесс
# ВНУТРИ критической секции ядра. Доставка сигнала оставила бы структуры
# данных ядра в несогласованном состоянии. Безопасность важнее живости.
4

Группы процессов и сессии: почему Ctrl-C убивает весь конвейер.

Процессы организованы в группы процессов (связанные процессы, обычно конвейер) и сессии (все процессы от одного входа в терминал). Терминал посылает сигналы группе процессов переднего плана — не одному процессу.

# Запускаем двухэтапный конвейер:
sleep 300 | cat -

# В другом терминале находим группу процессов:
ps -o pid,pgid,sid,comm | grep -E 'sleep|cat'
# PID   PGID   SID    COMM
# 5001  5001   4999   sleep    ← pgid=pid: sleep — лидер группы
# 5002  5001   4999   cat      ← тот же pgid: та же группа

# PGID 5001 — группа переднего плана терминала.
# Ctrl-C посылает SIGINT ВСЕМ процессам с PGID 5001 — и sleep, и cat.
# Поэтому умирает весь конвейер, а не только первый этап.

# Сессии:
# Сессия (SID) группирует все процессы от одного входа в терминал.
# Лидер сессии — оболочка. При закрытии терминала SIGHUP отправляется
# группе переднего плана. Поэтому фоновые процессы умирают при закрытии SSH.
# 'nohup' или 'disown' отвязывает процесс от SIGHUP сессии:
nohup long_running_job &   # игнорирует SIGHUP при закрытии терминала
Разбор примера

Корректное завершение сервиса с резервным таймаутом.

# Цель: долгоживущий Python-конвейер данных, PID 8901
# Он пишет в базу данных и должен сбросить лог транзакций при выходе.

# Шаг 1: пробуем SIGTERM — просим корректно завершиться
sudo kill -TERM 8901

# Шаг 2: ждём до 30 секунд
for i in $(seq 1 30); do
  kill -0 8901 2>/dev/null || { echo "Процесс завершился корректно через ${i}s"; exit 0; }
  sleep 1
done

# Шаг 3: если всё ещё работает — эскалируем
if kill -0 8901 2>/dev/null; then
  echo "Процесс не завершился корректно — отправляем SIGKILL"
  sudo kill -KILL 8901
  echo "Принудительно. Проверь временные файлы и незавершённые транзакции БД."
fi

# Проверка:
kill -0 8901 2>/dev/null && echo "ещё работает" || echo "завершён"

# systemd делает именно это: посылает SIGTERM, ждёт TimeoutStopSec (по умолчанию 90s),
# затем посылает SIGKILL. Таймаут настраивается в unit-файле:
# [Service]
# TimeoutStopSec=30
Почему это работает

На macOS те же сигналы ведут себя идентично — POSIX стандартизирует базовый набор. Ключевое отличие macOS: демоны управляются launchd, а не systemd. launchd тоже посылает сигналы: при launchctl unload отправляет SIGTERM, затем SIGKILL. Механизм сигналов (прерывание ядром, диспетчеризация обработчиков, непехватываемый SIGKILL) идентичен, поскольку и macOS, и Linux следуют POSIX-модели сигналов. Иммунитет D-состояния тоже есть на macOS — процессы, ожидающие I/O, так же защищены от прерывания сигналами внутри ядра.

Частая ошибка

kill -9 родителя оставляет детей зомби. Если ты kill -9 процесс с детьми, дети немедленно усыновляются PID 1 (systemd). Но дети, которые уже завершились и ожидали wait(), становятся зомби — осиротевшими зомби под опекой PID 1. systemd быстро их подберёт. Реальная проблема: если родитель управлял постоянными соединениями (прокси, база данных) и ты kill -9’ил его — дочерние воркеры могут работать без надзора, удерживая соединения. pkill -TERM -P <ppid> посылает SIGTERM всем детям родителя перед его убийством — паттерн корректного завершения родитель-затем-дети.

Проверь себя
Викторина

Ты нажимаешь Ctrl-C в терминале где запущено: find / -name '*.log' | gzip > all.gz. Какие процессы получат сигнал и какой именно?

Итог

Сигналы — асинхронные уведомления ядра. SIGTERM (15) перехватывается — хорошо написанный процесс выполняет очистку и корректно завершается. SIGKILL (9) не перехватывается — ядро принудительно удаляет процесс, без очистки, без обработчика. SIGHUP (1) изначально означал отключение терминала; демоны условились трактовать его как «перезагрузить конфигурацию» — стандартный способ применить изменения без перезапуска. SIGSTOP (19) замораживает процесс; SIGCONT (18) возобновляет. Исключение D-состояния: процесс в непрерываемом сне игнорирует даже SIGKILL. Группы процессов делают Ctrl-C полезным: терминал посылает SIGINT каждому процессу группы переднего плана одновременно — поэтому Ctrl-C убивает весь многоэтапный конвейер. Паттерн оператора: сначала SIGTERM, подождать, затем SIGKILL как последнее средство.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources1
expand
  1. 01

Trademarks belong to their respective owners. Editorial reference only.