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

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

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

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

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

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

Что такое сигнал SIGCHILD в Unix/Linux системах?

SIGCHLD (сокращение от SIGnal CHILD) — это стандартный сигнал в операционных системах семейства Unix и Linux, который отправляется родительскому процессу при изменении статуса его дочернего процесса. Изменение статуса обычно означает одно из трёх событий: завершение дочернего процесса (нормальное или аварийное), остановку (приостановку) или возобновление выполнения. Этот сигнал является ключевым механизмом для реализации корректного управления дочерними процессами и предотвращения появления зомби-процессов (zombie processes).

Основное назначение и сценарии использования

Основная цель SIGCHLD — уведомить родительский процесс о том, что с его потомком что-то произошло, чтобы родитель мог выполнить необходимые действия, например:

  • Собрать статус завершения дочернего процесса с помощью системного вызова wait() или waitpid().
  • Освободить системные ресурсы, которые всё ещё зарезервированы для завершённого процесса (именно это предотвращает превращение процесса в зомби).
  • Перезапустить завершившийся сервис, если это часть пула воркеров или демона.
  • Логировать факт завершения дочерней задачи.

Без обработки этого сигнала родительский процесс может не узнать о завершении потомка, и тот останется в таблице процессов как зомби (defunct процесс), занимая запись в ней, пока родитель сам не завершится или не вызовет wait().

Поведение по умолчанию и варианты обработки

Поведение по умолчанию для SIGCHLD — игнорирование (ignore). Однако это "игнорирование" особенное: ядро всё равно очищает запись о завершённом процессе, если родитель явно проигнорировал сигнал с помощью sigaction с флагом SA_NOCLDWAIT или установил обработчик на SIG_IGN (в POSIX.1-2001 и новее). Если же обработчик не установлен вообще (по умолчанию), то дочерний процесс может стать зомби.

Программист может настроить обработку сигнала тремя основными способами:

  1. Установка явного обработчика сигнала (signal handler). Это наиболее гибкий способ, позволяющий выполнить произвольный код при получении уведомления.
  2. Явное игнорирование сигнала с помощью SIG_IGN (как описано выше), чтобы ядро автоматически убирало зомби.
  3. Использование системного вызова wait() или waitpid() в цикле или с флагом WNOHANG. Часто обработчик сигнала как раз и вызывает waitpid() с этим флагом, чтобы собрать статус всех завершившихся потомков, не блокируя выполнение родителя.

Примеры кода на языке C

Вот простой пример установки обработчика для SIGCHLD:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

// Обработчик сигнала SIGCHLD
void sigchld_handler(int sig) {
    int saved_errno = errno; // Сохраняем errno
    // Собираем статус завершения без блокировки (WNOHANG)
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        // Цикл продолжается, пока есть завершённые дети
    }
    errno = saved_errno; // Восстанавливаем errno
}

int main() {
    struct sigaction sa;

    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // SA_NOCLDSTOP - не получать SIGCHLD при остановке
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    // Создаём дочерний процесс
    pid_t pid = fork();
    if (pid == 0) {
        // Код дочернего процесса
        printf("Дочерний процесс (PID=%d) запущен.\n", getpid());
        sleep(2);
        printf("Дочерний процесс (PID=%d) завершается.\n", getpid());
        exit(0);
    } else if (pid > 0) {
        // Код родительского процесса
        printf("Родительский процесс (PID=%d) ожидает. Дочерний PID=%d\n", getpid(), pid);
        // Родитель может заниматься своими делами, обработчик соберёт зомби
        sleep(10);
        printf("Родительский процесс завершает работу.\n");
    }

    return 0;
}

А вот пример с явным игнорированием для автоматической очистки:

// ...
int main() {
    struct sigaction sa;

    sa.sa_handler = SIG_IGN; // Явное игнорирование
    sa.sa_flags = SA_NOCLDWAIT; // Ключевой флаг для автоматической уборки зомби
    sigaction(SIGCHLD, &sa, NULL);

    // При fork() и завершении дочернего процесса, зомби не появится
    // ...
}

Важность в контексте DevOps и эксплуатации

Понимание SIGCHLD критически важно для DevOps-инженера по нескольким причинам:

  • Отладка и мониторинг: Необъяснимые появления зомби-процессов в мониторинге (например, в выводе ps aux) — прямой признак ошибки в управлении дочерними процессами в приложении.
  • Надёжность сервисов: Многие демоны, серверы (например, веб-серверы типа Nginx) и супервизоры (например, systemd, supervisord) активно используют форки и пулы процессов. Корректная обработка SIGCHLD гарантирует, что завершившиеся воркеры будут перезапущены, а ресурсы — освобождены.
  • Написание скриптов и инструментов: При написании скриптов на Bash/Python, которые запускают фоновые задачи, необходимо понимать, как собирать статусы завершения, чтобы скрипт не "протекал".
  • Контейнеризация: Внутри контейнеров PID namespace обычно изолирован, и процесс с PID 1 (init) обязан обрабатывать SIGCHLD и собирать статусы сирот, иначе контейнер может заполниться зомби. Именно для этого используют специализированные init-процессы (tini, dumb-init).

Таким образом, SIGCHLD — это не просто техническая деталь API процессов, а фундаментальный механизм, обеспечивающий стабильность и предсказуемость многозадачной среды. Его игнорирование в production-коде почти всегда ведёт к трудноуловимым ошибкам и утечкам ресурсов.

Что такое сигнал SIGCHILD? | PrepBro