Обработка, блокировка и игнорирование сигналов
Три способа отреагировать
Для каждого сигнала у процесса есть диспозиция:
| Диспозиция | Константа | Смысл |
|---|---|---|
| по умолчанию | 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. Поэтому:
- Вызывайте только async-signal-safe функции:
write,_exit,kill,signal,sem_post,waitpidи другие из спискаman 7 signal-safety. Нельзя:printf,malloc,free,exit. - Общие с программой переменные объявляйте
volatile sig_atomic_t. - Сохраняйте и восстанавливайте
errno, если вызываете функции, которые его меняют. - Лучшая стратегия — в обработчике только поставить флаг, а работу сделать в основном цикле.
Блокировка: маска сигналов
У каждого потока есть маска — множество заблокированных сигналов. Заблокированный сигнал не теряется: он остаётся ожидающим и будет доставлен сразу после снятия блокировки.
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
Установите обработчик on_int для сигнала SIGINT через sigaction: обнулите структуру,
заполните sa_handler, очистите маску sa_mask функцией sigemptyset и вызовите sigaction(SIGINT, &sa, NULL).
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_int;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
Блокировка или игнорирование?
| Блокировка (маска) | Игнорирование (SIG_IGN) | |
|---|---|---|
| Сигнал остаётся ожидающим и будет доставлен после снятия блокировки | ||
| Сигнал отбрасывается при доставке и не запоминается | ||
| Устанавливается через sigprocmask / pthread_sigmask | ||
| Устанавливается через sigaction / signal | ||
| Своя у каждого потока | ||
| Общая для всех потоков процесса | ||
| Сохраняется после exec |
Какие сигналы нельзя перехватить, заблокировать или проигнорировать?
Заблокируйте SIGINT на время критического участка и затем восстановите старую маску.
Используйте переменные sigset_t set, old; и sigprocmask с SIG_BLOCK и SIG_SETMASK.
sigset_t set, old;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, &old);
/* критический участок */
sigprocmask(SIG_SETMASK, &old, NULL);
Почему в обработчике сигнала нельзя вызывать printf и malloc?
Что происходит с настройками сигналов при fork и exec?
| Сохраняется после fork | Сохраняется после exec | |
|---|---|---|
| Обработчик (адрес функции) | ||
| SIG_IGN (игнорирование) | ||
| Маска блокированных сигналов | ||
| Ожидающие сигналы |
Войдите, чтобы отмечать пройденные уроки.