Сигналы — углублённо
Сигналы — асинхронные уведомления, доставляемые ядром процессу. SIGTERM перехватывается и запрашивает корректное завершение; SIGKILL не перехватывается и принудительно завершает. Процесс в D-состоянии игнорирует даже SIGKILL.
Ты уже знаешь kill -9 PID из курса по CLI. Но опытные операторы тянутся к -9 последними, а не первыми — потому что SIGKILL обходит shutdown-хуки, оставляет временные файлы нетронутыми и может повредить незаконченные записи. Правильный сигнал зависит от того, что ты хочешь от процесса. SIGTERM вежливо просит остановиться; SIGKILL принуждает. SIGHUP говорит демону перечитать конфигурацию. SIGSTOP замораживает процесс без убийства. А когда нажимаешь Ctrl-C в терминале, сигнал идёт ко всей группе процессов — не только к одному PID.
После этого урока ты сможешь объяснить разницу между SIGTERM и SIGKILL, объяснить почему SIGKILL нельзя перехватить, знать почему D-состояние игнорирует SIGKILL, использовать SIGHUP для перезагрузки демона без перезапуска, объяснить группы процессов и сессии, и понимать почему Ctrl-C убивает весь конвейер.
Сигналы — асинхронные уведомления: ядро прерывает процесс и доставляет их.
Сигнал — небольшое целое число, посылаемое ядром (или другим процессом с нужными правами) целевому процессу. Ядро прерывает цель на границе инструкции и вызывает зарегистрированный обработчик. Если обработчик не установлен — выполняется действие по умолчанию (для большинства сигналов: завершить процесс).
# Список всех сигналов:
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) → продолжить (возобновляет остановленный процесс)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 внутреннеПочему 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 или точке вытеснения. В непрерываемом сне процесс
# ВНУТРИ критической секции ядра. Доставка сигнала оставила бы структуры
# данных ядра в несогласованном состоянии. Безопасность важнее живости.Группы процессов и сессии: почему 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-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.