Learning Platform
Глоссарий Troubleshooting
Урок 11.04 · 22 мин
Начальный
signalskillSIGTERMSIGKILLSIGHUPSIGINTgraceful shutdown

Сигналы и kill: как правильно убивать процессы

«Убить процесс» — это упрощение. На самом деле в Linux вы не убиваете процесс напрямую. Вы посылаете ему сигнал, а процесс уже решает, как реагировать. Большинство сигналов можно обработать, перехватить, проигнорировать. Только два сигнала — SIGKILL и SIGSTOP — нельзя ни перехватить, ни проигнорировать.

Этот урок про то, какие сигналы существуют, что они значат, и почему правило хорошего тона — никогда не делать kill -9 сразу, без попытки graceful shutdown.


Что такое сигнал

Сигналы как механизм IPC: обработчики, маскирование, async-safety

Сигнал — это асинхронное уведомление, которое одно процесс шлёт другому через ядро. Это форма IPC (Inter-Process Communication) — простая, ограниченная, но универсальная.

Когда процесс A шлёт сигнал процессу B:

  1. Процесс A вызывает syscall kill(pid, signum).
  2. Ядро проверяет права (можно ли A слать сигнал B — зависит от UID).
  3. Ядро ставит signum в pending-маску B.
  4. При следующем выходе из ядра в user-space B — выполняется обработчик сигнала.
Жизненный цикл сигнала

От kill до выполнения обработчика — что происходит

Process A
Kernel
Process B
syscall: kill(pid_B, SIGTERM)проверка прав (UID-match)ставит SIGTERM в pending maskна выходе из ядра — вызывает signal handlerв handler — закрывает соединения, flush, exit(0)

Главные сигналы для DE

Сигналов в Linux примерно 30 (зависит от архитектуры). Не нужно учить все. DE нужно знать 6-7 главных:

Сигналы, которые нужно помнить

Семь сигналов, покрывающих 95% сценариев

SIGHUP (1)HangUP. Исторически — терминал закрылся. Сейчас — convention для перечитывания config-файла (nginx, postgres). Демоны интерпретируют как reload
SIGINT (2)Interrupt. Ctrl-C в терминале посылает SIGINT foreground-процессу. Просьба остановиться. По умолчанию завершает процесс
SIGKILL (9)Жёсткое убийство. НЕЛЬЗЯ перехватить, нельзя проигнорировать. Ядро прибивает процесс без шансов. Используется когда graceful не работает
SIGTERM (15)Terminate (вежливое). Дефолт kill PID без флагов. Процесс может перехватить — обычно делает cleanup и exit. Это правильный способ просить остановиться
SIGSTOP (19)Suspend (нельзя проигнорировать). Процесс ставится в состояние T (Stopped). Не убивает — приостанавливает
SIGCONT (18)Continue. Возобновляет остановленный SIGSTOP/SIGTSTP процесс
SIGTSTP (20)Terminal Stop. Ctrl-Z в терминале. Аналог SIGSTOP, но процесс может его перехватить

Список всех сигналов:

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 — главное правило

WARNING

Правило: всегда сначала 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
fi

kill -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.
DANGER

Никогда не делайте 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 и нажимаете комбинацию:

Keyboard -> signal

Что какие комбинации в терминале посылают foreground-процессу

Ctrl-CSIGINT — interrupt. Терминал шлёт foreground-группе процессов. По умолчанию = завершение
Ctrl-ZSIGTSTP — terminal stop. Приостанавливает процесс, переводит в фон. Возобновить через fg/bg
Ctrl-backslashSIGQUIT — quit с core dump. Завершает процесс и создаёт core-файл с снимком памяти для дебага
# Запусти процесс
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 hooks

1. 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).


Проверка знанийKnowledge check
Почему DevOps говорят «не делайте kill -9 сразу»? Что конкретно теряется и какой правильный паттерн?
ОтветAnswer
SIGKILL (kill -9) — единственный сигнал, который ядро доставляет принудительно, без шансов процессу обработать. Программа не успевает: закрыть открытые сокеты (на той стороне болтаются полу-закрытые соединения), flush-нуть буферы записи на диск (теряются последние строки логов, последние commits в storage engines типа MyISAM), снять блокировки (lock-файлы /var/run/*.pid остаются — следующий запуск думает что процесс жив и отказывается стартовать), записать состояние в БД (Airflow worker не успевает пометить task как failed — он остаётся в running). Правильный паттерн: 1) kill -TERM PID (SIGTERM, программа делает cleanup); 2) sleep 5-15 секунд; 3) kill -0 PID для проверки — если ещё жив; 4) только тогда kill -KILL PID. Этот паттерн — основа systemd: TimeoutStopSec задаёт паузу между TERM и KILL, по умолчанию 90 секунд.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Какой сигнал по умолчанию посылает команда `kill 1234` без флагов?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5