← Назад к вопросам

Кокой из сигналов (SIGTERM/SIGSKILL) может породить зомби-процесс

1.8 Middle🔥 131 комментариев
#Linux и администрирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Может ли SIGTERM или SIGKILL породить зомби-процесс?

Короткий ответ: ни один из этих сигналов напрямую не порождает зомби-процесс. Однако их использование в неправильном контексте может способствовать возникновению зомби или оставить существующие зомби-процессы "зависшими" в системе.

Чтобы разобраться в деталях, нужно чётко понимать механику завершения процессов в Linux/Unix и природу зомби-процесса (Zombie process).

Что такое зомби-процесс?

Зомби — это процесс, который завершил своё выполнение (exit), но ещё не был "утилизирован" своим родительским процессом. В Unix-подобных системах, когда процесс завершается, ядро оставляет запись о нём в таблице процессов до тех пор, пока родитель не прочитает статус завершения с помощью системного вызова wait() (или его вариантов: waitpid(), waitid()). Эта запись содержит код завершения процесса и некоторую другую метаинформацию. Пока запись существует, процесс находится в состоянии Z (Zombie). Он не consumes resources (память, CPU), но занимает запись в таблице процессов (PID).

Роль SIGTERM и SIGKILL в контексте зомби

Оба сигнала служат для завершения процессов, но работают принципиально по-разному:

  • SIGTERM (сигнал 15): Это "вежливый" сигнал завершения (termination signal). Он просит процесс завершиться корректно. Процесс-получатель может:
    *   Перехватить сигнал (`trap`), выполнить cleanup-логику (закрыть файлы, остановить дочерние процессы, освободить ресурсы) и затем завершиться.
    *   Проигнорировать сигнал (если не перехвачен, поведение по умолчанию — завершение).
    **Важно:** При получении `SIGTERM` процесс выполняет свой обычный путь завершения, включая вызов `exit()`. Если в этот момент у него есть **завершённые, но не обработанные (`wait()`-нутые) дочерние процессы, они станут зомби**. Сам процесс, завершаясь от `SIGTERM`, станет зомби для своего родителя. `SIGTERM` не создаёт зомби напрямую, но завершает процесс, переводя его в состояние, которое *может* стать зомби, если родитель не вызовет `wait()`.

  • SIGKILL (сигнал 9): Это безусловный, немедленный сигнал уничтожения (kill signal). Его нельзя перехватить (trap), проигнорировать или заблокировать. Ядро немедленно останавливает процесс, не давая ему выполнить какой-либо cleanup.
    **Ключевой момент:** Поскольку процесс уничтожается ядром в обход нормального пути завершения, **ответственность за "уборку" (reaping) его дочерних процессов берёт на себя специальный процесс — `init` (PID 1) или, в современных системах, `systemd`**. Этот процесс-приёмник периодически вызывает `wait()` для всех своих "усыновлённых" детей. Поэтому, если вы убьёте родительский процесс через `SIGKILL`, его зомби-дети (если они были) обычно очень быстро "утилизируются" `init`/`systemd`. **`SIGKILL` сам по себе также не создаёт новых зомби.**

Какой же сигнал более "опасен" с точки зрения появления зомби?

Парадоксально, но SIGTERM потенциально более "опасен" в контексте долгоживущих зомби, если родительский процесс написан с ошибкой.

Рассмотрим сценарий:

  1. Есть родительский процесс (parent), который не вызывает wait() для своих завершённых детей (классическая ошибка в коде — "неубранные дети").
  2. Его дочерний процесс (child) завершается. Он становится зомби.
  3. Если мы пошлём parent сигнал SIGTERM, он может корректно завершиться, но так и не вызвать wait() для своего зомби-ребёнка. Теперь у нас есть зомби (child), чей родитель (parent) мёртв. В этом случае ядро передаёт child процессу init/systemd, который быстро его "утилизирует". Период существования зомби продлится лишь доли секунды.
  4. Если же мы пошлём parent сигнал SIGKILL, история будет аналогичной: родитель уничтожен, его зомби-ребёнок усыновлён init и утилизирован.

Главная проблема возникает, если родитель игнорирует SIGTERM или находится в бесконечном цикле и не завершается после его получения. В этом случае родитель продолжает жить, не вызывая wait(), и его зомби-дети останутся в системе навсегда (или до перезагрузки). SIGKILL в такой ситуации — единственный способ убить такого родителя и позволить системе через init очистить зомби.

Пример на Python

Рассмотрим код с ошибкой, создающий зомби, и действие сигналов.

#!/usr/bin/env python3
import os
import sys
import time
import signal

def child_process():
    print(f"[Child PID {os.getpid()}] Запущен. Завершаюсь сразу.")
    sys.exit(0)

def parent_process():
    # НАМЕРЕННАЯ ОШИБКА: fork без wait()
    pid = os.fork()
    if pid == 0:
        child_process()
    else:
        print(f"[Parent PID {os.getpid()}] Породил child PID {pid}. НЕ БУДУ вызывать wait()!")
        # Бесконечный цикл, чтобы процесс не завершился
        while True:
            time.sleep(60)

if __name__ == "__main__":
    parent_process()

Запустите этот скрипт. В другом терминале проверьте состояние дочернего процесса:

ps aux | grep 'Z'

Вы увидите дочерний процесс в состоянии Z (зомби).

Теперь, если отправить родителю SIGTERM (kill -15 <parent_pid>), он завершится (если не перехватывает сигнал), и зомби исчезнет почти мгновенно (его "уберёт" systemd). Если же родитель перехватит SIGTERM и продолжит работу (представьте except KeyboardInterrupt: pass), зомби так и останется висеть. В этом случае SIGKILL родителя (kill -9 <parent_pid>) будет верным решением для очистки системы.

Выводы

  • Прямым виновником появления зомби-процесса является недоработка (баг) в коде родительского процесса, который не вызывает wait() для своих завершённых детей.
  • SIGTERM может "зафиксировать" состояние системы с зомби, если ошибочный родитель продолжает работу после получения сигнала.
  • SIGKILL часто является "лекарством" от скопления зомби, так как позволяет ядру передать их процессу-реаперу (init/systemd), который выполнит необходимый wait().
  • Для предотвращения зомби в своих программах всегда используйте механизмы ожидания завершения дочерних процессов (например, signal(SIGCHLD, SIG_IGN) в C, что заставляет ядро автоматически утилизировать детей, или корректные обработчики waitpid()).

Таким образом, правильный ответ на исходный вопрос: ни SIGTERM, ни SIGKILL не порождают зомби-процесс сами по себе. Их взаимодействие с ошибочным кодом, не вызывающим wait(), определяет, останутся ли зомби в системе надолго.

Кокой из сигналов (SIGTERM/SIGSKILL) может породить зомби-процесс | PrepBro