Обработка, блокировка и игнорирование сигналов

⏱ 35 мин · 🧩 заданий: 6

Три способа отреагировать

Для каждого сигнала у процесса есть диспозиция:

Диспозиция Константа Смысл
по умолчанию SIG_DFL Term / Core / Ign / Stop / Cont по таблице
игнорировать SIG_IGN сигнал отбрасывается
перехватить указатель на функцию вызвать обработчик

sigaction

Старая функция signal() ведёт себя по-разному в разных системах. Используйте sigaction:

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

volatile sig_atomic_t got_int = 0;

void on_int(int sig)
{
    (void)sig;
    got_int = 1;                              /* только простые действия! */
    const char msg[] = "получен SIGINT\n";
    write(STDOUT_FILENO, msg, sizeof(msg) - 1); /* write безопасна в обработчике */
}

int main(void)
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = on_int;
    sigemptyset(&sa.sa_mask);     /* какие сигналы дополнительно блокировать во время обработчика */
    sa.sa_flags = SA_RESTART;     /* перезапускать прерванные системные вызовы */
    sigaction(SIGINT, &sa, NULL);

    while (!got_int)
        pause();                  /* ждать любой сигнал */
    return 0;
}

Во время выполнения обработчика сам сигнал автоматически блокируется (если не указан SA_NODEFER), плюс сигналы из sa_mask.

Флаги sa_flags

Флаг Назначение
SA_RESTART прерванные сигналом медленные вызовы (read, wait) перезапускаются, а не возвращают EINTR
SA_SIGINFO обработчик вида void h(int, siginfo_t *, void *) — с информацией об отправителе, адресе ошибки, значении
SA_NOCLDSTOP не присылать SIGCHLD при остановке потомков
SA_NOCLDWAIT не превращать потомков в зомби
SA_RESETHAND после первого срабатывания вернуть SIG_DFL

Правила для обработчиков

Обработчик может сработать в любой точке программы — даже посреди malloc или printf. Поэтому:

  1. Вызывайте только async-signal-safe функции: write, _exit, kill, signal, sem_post, waitpid и другие из списка man 7 signal-safety. Нельзя: printf, malloc, free, exit.
  2. Общие с программой переменные объявляйте volatile sig_atomic_t.
  3. Сохраняйте и восстанавливайте errno, если вызываете функции, которые его меняют.
  4. Лучшая стратегия — в обработчике только поставить флаг, а работу сделать в основном цикле.

Блокировка: маска сигналов

У каждого потока есть маска — множество заблокированных сигналов. Заблокированный сигнал не теряется: он остаётся ожидающим и будет доставлен сразу после снятия блокировки.

sigset_t set, old;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, &old);   /* SIG_BLOCK / SIG_UNBLOCK / SIG_SETMASK */
/* ... здесь SIGINT не прервёт ... */
sigprocmask(SIG_SETMASK, &old, NULL); /* если SIGINT пришёл — будет доставлен сейчас */

sigset_t pending;
sigpending(&pending);                 /* какие сигналы сейчас ждут */
if (sigismember(&pending, SIGINT)) { ... }

Блокировка ≠ игнорирование

Блокировка Игнорирование
Сигнал теряется? нет, ждёт да
Как установить sigprocmask, pthread_sigmask sigaction(..., SIG_IGN)
Чьё свойство потока процесса

«Запретить доставку» сигнала — значит заблокировать его. Это возможно для всех сигналов, кроме SIGKILL и SIGSTOP: их нельзя ни заблокировать, ни игнорировать, ни перехватить.

Две оговорки для таблицы задачи 1

SIGCONT можно заблокировать или перехватить, но его главное действие — продолжить остановленный процесс — ядро выполняет в момент генерации, даже если сигнал заблокирован или игнорируется. Блокировка отложит только вызов обработчика. SIGABRT, посланный через abort(), тоже «пробивает» защиту: abort() снимает блокировку SIGABRT, а если обработчик вернул управление — восстанавливает действие по умолчанию и посылает сигнал снова. Но kill -ABRT можно заблокировать как обычно. Отвечая, назовите строго SIGKILL и SIGSTOP, а про SIGCONT и SIGABRT упомяните оговорку — это покажет глубокое понимание.

Ожидание сигнала без гонок

Наивный код содержит гонку: сигнал может прийти между проверкой флага и pause(), и программа уснёт навсегда.

/* правильно: атомарно снять блокировку и уснуть */
sigset_t block, old;
sigemptyset(&block);
sigaddset(&block, SIGUSR1);
sigprocmask(SIG_BLOCK, &block, &old);
while (!got_usr1)
    sigsuspend(&old);         /* на время ожидания маска = old, SIGUSR1 разблокирован */
sigprocmask(SIG_SETMASK, &old, NULL);

Прерванные системные вызовы

Если сигнал пришёл, пока процесс спит в медленном вызове (read с терминала, wait, pause), после обработчика вызов вернёт −1 с errno = EINTR — если не установлен SA_RESTART.

ssize_t n;
do {
    n = read(fd, buf, sizeof buf);
} while (n == -1 && errno == EINTR);

Наследование

после fork после exec
обработчики копируются сбрасываются в SIG_DFL
SIG_IGN копируется сохраняется
маска копируется сохраняется
ожидающие сигналы очищаются у потомка сохраняются

Поэтому nohup работает так: устанавливает SIG_IGN для SIGHUP и делает exec нужной программы.

Итог

  • Диспозиции: SIG_DFL, SIG_IGN, обработчик; устанавливаются через sigaction.
  • В обработчике — только async-signal-safe функции и volatile sig_atomic_t флаги.
  • Блокировка (маска) откладывает доставку; игнорирование выбрасывает сигнал.
  • SIGKILL и SIGSTOP нельзя заблокировать, игнорировать или перехватить.
  • sigsuspend — ожидание без гонок; EINTR и SA_RESTART — для прерванных вызовов.

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Установите обработчик on_int для сигнала SIGINT через sigaction: обнулите структуру, заполните sa_handler, очистите маску sa_mask функцией sigemptyset и вызовите sigaction(SIGINT, &sa, NULL).

Задание 2 Заполнить таблицу

Блокировка или игнорирование?

Блокировка (маска)Игнорирование (SIG_IGN)
Сигнал остаётся ожидающим и будет доставлен после снятия блокировки
Сигнал отбрасывается при доставке и не запоминается
Устанавливается через sigprocmask / pthread_sigmask
Устанавливается через sigaction / signal
Своя у каждого потока
Общая для всех потоков процесса
Сохраняется после exec
Отметьте ячейку — «+», пустая — «−».
Задание 3 Несколько вариантов

Какие сигналы нельзя перехватить, заблокировать или проигнорировать?

Задание 4 Написать код

Заблокируйте SIGINT на время критического участка и затем восстановите старую маску. Используйте переменные sigset_t set, old; и sigprocmask с SIG_BLOCK и SIG_SETMASK.

Задание 5 Один вариант ответа

Почему в обработчике сигнала нельзя вызывать printf и malloc?

Задание 6 Заполнить таблицу

Что происходит с настройками сигналов при fork и exec?

Сохраняется после forkСохраняется после exec
Обработчик (адрес функции)
SIG_IGN (игнорирование)
Маска блокированных сигналов
Ожидающие сигналы
Отметьте ячейку — «+», пустая — «−».

Войдите, чтобы отмечать пройденные уроки.