Сигналы и kill: как правильно убивать процессы
«Убить процесс» — это упрощение. На самом деле в Linux вы не убиваете процесс напрямую. Вы посылаете ему сигнал, а процесс уже решает, как реагировать. Большинство сигналов можно обработать, перехватить, проигнорировать. Только два сигнала — SIGKILL и SIGSTOP — нельзя ни перехватить, ни проигнорировать.
Этот урок про то, какие сигналы существуют, что они значат, и почему правило хорошего тона — никогда не делать kill -9 сразу, без попытки graceful shutdown.
Что такое сигнал
Сигналы как механизм IPC: обработчики, маскирование, async-safetyСигнал — это асинхронное уведомление, которое одно процесс шлёт другому через ядро. Это форма IPC (Inter-Process Communication) — простая, ограниченная, но универсальная.
Когда процесс A шлёт сигнал процессу B:
- Процесс A вызывает syscall
kill(pid, signum). - Ядро проверяет права (можно ли A слать сигнал B — зависит от UID).
- Ядро ставит signum в pending-маску B.
- При следующем выходе из ядра в user-space B — выполняется обработчик сигнала.
От kill до выполнения обработчика — что происходит
Главные сигналы для DE
Сигналов в Linux примерно 30 (зависит от архитектуры). Не нужно учить все. DE нужно знать 6-7 главных:
Семь сигналов, покрывающих 95% сценариев
Список всех сигналов:
kill -l
# 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
# 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
# 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
# ...
kill — основная команда
# По умолчанию kill = SIGTERM (15)
kill 1234
# Явно SIGTERM
kill -15 1234
kill -TERM 1234
kill -s SIGTERM 1234 # все три варианта эквивалентны
# SIGKILL (последняя надежда)
kill -9 1234
kill -KILL 1234
kill -s SIGKILL 1234
# SIGHUP — reload (для nginx, postgres)
kill -1 1234
kill -HUP 1234
Можно прибить несколько PID разом:
kill -TERM 1234 5678 9012
И группу процессов (negative PID = group):
# PGID 5000 и все его процессы
kill -TERM -5000
SIGTERM vs SIGKILL — главное правило
Правило: всегда сначала SIGTERM, потом — через паузу — SIGKILL.
Реальный паттерн:
# 1. Просьба остановиться
kill -TERM 1234
# 2. Дай процессу 5-10 секунд на cleanup
sleep 10
# 3. Если ещё жив — жёсткий kill
if kill -0 1234 2>/dev/null; then
kill -KILL 1234
fikill -0 PID — это «проверь, существует ли процесс, не посылая сигнал». Полезно для проверки.
Почему так? Что теряет SIGKILL
SIGTERM даёт процессу шанс сделать cleanup:
- Закрыть открытые сокеты — иначе на той стороне болтаются полу-закрытые соединения.
- Flush буферов записи на диск — иначе теряются последние записанные данные (например, последние строки лога).
- Снять блокировки — освободить mutex-ы, lock-файлы.
- Записать exit-status в служебные файлы — например, Airflow worker записывает task instance state в БД при graceful shutdown.
SIGKILL ничего из этого не даёт. Ядро просто грубо завершает процесс. Результат:
- Полупустые буферы на диске -> возможна потеря последних строк логов.
- Lock-файлы в
/var/run/*.pidостаются на диске -> следующий запуск думает, что процесс ещё жив, и отказывается стартовать. - Половинчатые transaction в БД -> можно повредить данные.
Реальные примеры:
kill -9 postgres— Postgres начинает recovery при следующем старте (replay WAL). Это безопасно, но долго.kill -9 airflow-worker— Task instance остаётся в running состоянии в БД, scheduler думает что таск выполняется, новые попытки запуска не идут. Нужно вручную чистить.kill -9 mysql— MySQL может потерять последние commits с buffered storage engine.
Никогда не делайте pkill -9 <название> сразу. Сначала pkill -TERM <название>, ждёте, потом если ещё жив — pkill -KILL.
SIGHUP — традиция reload-а
Исторически SIGHUP посылался когда «hung up» — повесили трубку терминала (раньше терминалы были по модему). Процесс знал — мой контролирующий терминал умер, я тоже умираю.
Но потом демоны (которые не имеют терминала) переиспользовали SIGHUP как «перечитай config». Это convention, а не стандарт:
# nginx: re-open log files и reload config
sudo kill -HUP $(cat /var/run/nginx.pid)
# или (для системных сервисов)
sudo systemctl reload nginx
# postgres: reload pg_hba.conf и postgresql.conf
sudo kill -HUP $(head -1 /var/lib/postgresql/16/main/postmaster.pid)
# или
sudo systemctl reload postgresql
Поведение SIGHUP — на усмотрение программы. Стандартом не закреплено. Что делает конкретный демон — нужно смотреть в его документации.
Ctrl-C, Ctrl-Z, Ctrl-\ — сигналы из терминала
Когда вы в bash и нажимаете комбинацию:
Что какие комбинации в терминале посылают foreground-процессу
# Запусти процесс
sleep 100
# Нажми Ctrl-C — придёт SIGINT, процесс завершится
^C
# Запусти ещё
sleep 100
# Нажми Ctrl-Z — придёт SIGTSTP, процесс приостановится
^Z
# [1]+ Stopped sleep 100
# Возобновить в фоне
bg
# Вернуть на передний план
fg
Подробнее про jobs (fg/bg) — следующий урок 05.
Поведение сигналов в коде
Когда программист пишет программу, он может зарегистрировать signal handler — функцию, которая выполнится при приходе сигнала. Например, Python:
import signal
import sys
import time
def handle_sigterm(signum, frame):
print("Получен SIGTERM, делаю cleanup...")
# Закрываем соединения, flush, etc.
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
print("PID:", __import__('os').getpid())
time.sleep(3600)
Запустите этот скрипт, потом из другого терминала:
kill -TERM <PID>
Скрипт выведет «Получен SIGTERM, делаю cleanup…» и завершится. Это пример graceful shutdown.
А вот SIGKILL обработать нельзя. Если послать kill -9 <PID> — скрипт просто умирает без вывода.
DE-сценарии
Graceful shutdown в Kubernetes: SIGTERM и preStop hooks1. Graceful restart Airflow scheduler
# Шаг 1: найти PID
PID=$(pgrep -x airflow-scheduler)
# Шаг 2: послать TERM
kill -TERM $PID
# Шаг 3: подождать
sleep 15
# Шаг 4: проверить
if kill -0 $PID 2>/dev/null; then
echo "Scheduler не остановился за 15с, шлю KILL"
kill -9 $PID
fi
# Шаг 5: запустить заново (обычно через systemd)
sudo systemctl start airflow-scheduler
В реальности на проде — sudo systemctl restart airflow-scheduler, и systemd сам делает TERM -> ждёт TimeoutStopSec -> KILL. Модуль 14 про systemd.
2. Reload Postgres конфига без перезапуска
# Изменил pg_hba.conf — нужно reload
sudo systemctl reload postgresql
# или через kill
sudo kill -HUP $(head -1 /var/lib/postgresql/16/main/postmaster.pid)
Постгрес перечитает конфиг без обрыва текущих сессий. Полное restart — обрывает все соединения, это намного хуже.
3. «Зависший процесс, не реагирует на kill -TERM»
kill -TERM 1234
sleep 10
# Всё ещё жив? Проверь состояние
ps -p 1234 -o state,cmd
# Если состояние D — процесс в uninterruptible disk wait, никакой kill не поможет.
# Нужно ждать или ребутиться. Проблема — в диске или mount.
# Если состояние Z — это зомби, не процесс. kill бесполезен. Нужно reap-нуть через родителя.
# Если состояние S или R — попробуй kill -KILL
kill -9 1234
4. Безопасный shutdown скрипта по сигналу
Хорошие bash-скрипты ловят сигналы через trap:
#!/bin/bash
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"; exit 130' INT TERM
# 130 — стандартный exit code для SIGINT (128 + 2)
# Делаем что-то
echo "data" > "$TMPFILE"
sleep 1000 # имитация долгой задачи
# Когда придёт Ctrl-C или kill -TERM — TMPFILE удалится
Подробнее про trap — модуль 18 (production-bash).
kill -0 — проверка существования
kill -0 PID ничего не делает, но возвращает exit status:
- 0 — процесс существует и у тебя есть права слать ему сигналы
- 1 — процесса нет (или нет прав)
if kill -0 1234 2>/dev/null; then
echo "Процесс 1234 существует"
else
echo "Процесс 1234 не существует"
fi
Полезно в скриптах для проверки «жив ли процесс» без побочных эффектов.
Попробуй сам
# 1. Список всех сигналов
kill -l
# 2. Запусти sleep, прибей вежливо
sleep 300 &
PID=$!
ps -p $PID
kill -TERM $PID
ps -p $PID # должен исчезнуть
# 3. Запусти sleep, попробуй SIGSTOP/SIGCONT
sleep 300 &
PID=$!
kill -STOP $PID
ps -p $PID -o pid,state,cmd # state будет T (stopped)
kill -CONT $PID
ps -p $PID -o pid,state,cmd # state снова S (sleeping)
kill $PID
# 4. Поймай SIGTERM в python-скрипте
cat > /tmp/sig.py <<'EOF'
import signal, sys, time, os
def handler(signum, frame):
print(f"Got signal {signum}, cleaning up")
sys.exit(0)
signal.signal(signal.SIGTERM, handler)
print("PID:", os.getpid())
time.sleep(3600)
EOF
python3 /tmp/sig.py &
sleep 1
PID=$(pgrep -f /tmp/sig.py)
kill -TERM $PID
# увидишь "Got signal 15, cleaning up"
# 5. SIGKILL нельзя поймать
python3 /tmp/sig.py &
sleep 1
PID=$(pgrep -f /tmp/sig.py)
kill -9 $PID
# Просто молча умрёт, ничего не выведет
# 6. kill -0 для проверки
sleep 100 &
PID=$!
kill -0 $PID && echo "жив"
kill $PID
sleep 1
kill -0 $PID 2>/dev/null && echo "жив" || echo "мёртв"
Cross-link: предыдущий урок 03 — мониторинг. Следующий урок 05 — jobs и Ctrl-Z. Модуль 14 — systemd использует сигналы для управления сервисами (graceful shutdown, reload).