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

Что делает SIGHUP с процессом?

2.0 Middle🔥 61 комментариев
#Операционные системы и Linux

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

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

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

Сигнал SIGHUP: Управление процессами в Unix/Linux

SIGHUP (Signal Hang Up, сигнал №1 в POSIX-системах) — это один из базовых управляющих сигналов в Unix-подобных операционных системах (Linux, macOS, BSD), который исторически ассоциировался с «обрывом соединения» терминала.

Основное назначение и поведение по умолчанию

По умолчанию SIGHUP приводит к завершению (termination) процесса. Это унаследованное поведение связано с его происхождением: когда пользователь «вешал трубку» (отключался от модемного соединения или закрывал терминал), все процессы, запущенные из этого сеанса, получали SIGHUP от ядра и завершались. Это предотвращало «осиротение» интерактивных процессов.

Однако в современных системах его роль значительно шире. Процесс может перехватить (catch) или игнорировать (ignore) этот сигнал, изменив его стандартную семантику. Это делается через системный вызов signal() или, предпочтительнее, sigaction().

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void sighup_handler(int signum) {
    printf("Процесс %d получил SIGHUP, но не завершается!\n", getpid());
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sighup_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    // Устанавливаем собственный обработчик для SIGHUP
    if (sigaction(SIGHUP, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    printf("Процесс %d ожидает сигналы...\n", getpid());
    while(1) {
        pause(); // Ожидание сигнала
    }
    return 0;
}

Ключевые сценарии использования SIGHUP

  1. Реинициализация демонов (перечитывание конфигурации)
    Это самый распространённый и полезный современный сценарий. Многие **демоны (фоновые службы)**, такие как `nginx`, `haproxy`, `systemd`, перехватывают SIGHUP и интерпретируют его как команду:
    *   **Перечитать файлы конфигурации** без полной остановки службы.
    *   **Реоткрыть лог-файлы** (например, после log rotation утилитой `logrotate`).
    *   Выполнить другие действия по «мягкому» обновлению состояния.

```bash
# Типичное использование с веб-сервером nginx
sudo nginx -t && sudo nginx -s reload  # Команда reload отправляет SIGHUP master-процессу nginx
```

2. Управление сеансами и контрольными группами (cgroups)

    При завершении управляющего терминала (session leader) ядро отправляет SIGHUP всем процессам в его **группе процессов (process group)**, что может привести к каскадному завершению. Этого можно избежать, используя:
    *   Утилиту `nohup` (буквально «no hang up»), которая запускает процесс, заранее устанавливая игнорирование SIGHUP и перенаправляя стандартные потоки ввода/вывода.
    *   Менеджеры сеансов (`screen`, `tmux`), которые изолируют процессы от физического терминала.
```bash
# Запуск долгой задачи, которая должна пережить закрытие терминала
nohup ./long_running_task.sh > task.log 2>&1 &
```

3. Уведомление о изменениях в системе

    Некоторые системные компоненты могут использовать SIGHUP для уведомления процессов об изменениях состояния системы (например, о смене сетевых настроек).

SIGHUP в контексте разработки на Go

В Go работа с сигналами осуществляется через пакет os/signal. По умолчанию SIGHUP, как и другие сигналы, не имеет специального обработчика, и поведение определяется на уровне ОС. Однако разработчик может легко перехватить его для реализации собственной логики.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // Создаём канал для получения сигналов ОС
    sigChan := make(chan os.Signal, 1)

    // Регистрируем интересующие нас сигналы.
    // В этом примере мы перехватываем SIGHUP и SIGINT (Ctrl+C).
    signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT)

    fmt.Printf("Демон PID=%d запущен. Ожидание сигналов...\n", os.Getpid())
    fmt.Println("SIGHUP (1) - перечитать конфиг, SIGINT (2) - завершить.")

    for {
        sig := <-sigChan // Блокируемся, пока не придёт сигнал
        switch sig {
        case syscall.SIGHUP:
            // Логика реинициализации
            fmt.Println("Получен SIGHUP. Перечитываю конфигурацию...")
            // Здесь можно вызвать метод reconfig() или reload()
        case syscall.SIGINT:
            fmt.Println("Получен SIGINT. Корректное завершение...")
            os.Exit(0)
        }
    }
}

Важные технические нюансы

  • Отправка сигнала: SIGHUP можно отправить вручную с помощью утилиты kill:
    kill -HUP <pid>
    # или
    kill -1 <pid>
    
  • Наследование: Дочерние процессы, созданные через fork(), наследуют настройки обработки сигналов от родителя, но если затем вызывается exec(), поведение сбрасывается для сигналов, установленных в обработчик (они сбрасываются в действие по умолчанию), в то время как игнорируемые сигналы (как при использовании nohup) остаются игнорируемыми.
  • Сигналы реального времени: SIGHUP не является сигналом реального времени (не относится к сигналам SIGRTMIN-SIGRTMAX), поэтому его доставка не гарантирует порядок и не несёт дополнительных данных.

Итог: SIGHUP эволюционировал от простого сигнала завершения при обрыве связи до мощного инструмента управления runtime-поведением долгоживущих процессов, особенно демонов. В Go, благодаря пакету os/signal, его перехват и использование для реинициализации становится простой и идиоматичной задачей, позволяющей создавать более отказоустойчивые и удобные в администрировании приложения.

Что делает SIGHUP с процессом? | PrepBro