Следующий набор работает с маской сигналов процесса: sigprocmask()
устанавливает и получает маску сигналов процесса, sigpending()
получает набор ожидающих сигналов, a sigsuspend()
помещает процесс в состояние сна, временно заменяя маску сигналов процесса одним из своих параметров.
Функция POSIX API sigaction()
(весьма) запутана из-за необходимости обеспечить:
• обратную совместимость: SA_RESETHAND
и SA_RESTART
в поле sa_flags
;
• выбор, блокировать также полученный сигнал или нет: SA_NODEFER
для sa_flags
;
• возможность иметь два различных вида обработчиков сигналов: с одним или с тремя аргументами;
• выбор поведения для управления SIGCHLD
: SA_NOCLDSTOP
и SA_NOCLDWAIT
для sa_flags
.
Функция siginterrupt()
является удобной для разрешения или запрещения повторного запуска системных вызовов для данного сигнала.
Наконец, для посылки сигналов не только текущему, но также и другим процессам могут использоваться kill()
и killpg()
(конечно, с проверкой прав доступа).
10.7. Сигналы для межпроцессного взаимодействия
«ЭТО УЖАСНАЯ МЫСЛЬ! СИГНАЛЫ НЕ ПРЕДНАЗНАЧЕНЫ ДЛЯ ЭТОГО! Просто скажите НЕТ».
Одним из главных механизмов межпроцессного взаимодействия (IPC) являются каналы, которые описаны в разделе 9.3 «Базовая межпроцессная коммуникация каналы и FIFO». Сигналы также можно использовать для очень простого IPC[111]. Это довольно грубо; получатель может лишь сказать, что поступил определенный сигнал. Хотя функция sigaction()
позволяет получателю узнать PID и владельца процесса, пославшего сигнал, эти сведения обычно не очень помогают.
ЗАМЕЧАНИЕ. Как указывает цитата в начале, использование сигналов для IPC почти всегда является плохой мыслью. Мы рекомендуем по возможности избегать этого. Но нашей целью является научить вас, как использовать возможности Linux/Unix, включая их отрицательные моменты, оставляя за вами принятие информированного решения, что именно использовать.
Сигналы в качестве IPC для многих программ могут быть иногда единственным выбором. В частности, каналы не являются альтернативой, если две взаимодействующие программы не запущены общим родителем, а файлы FIFO могут не быть вариантом, если одна из взаимодействующих программ работает лишь со стандартными вводом и выводом. (Примером обычного использования сигналов являются определенные системные программы демонов, таких, как xinetd
, которые принимают несколько сигналов, уведомляющих, что нужно повторно прочесть файл настроек, осуществить проверку непротиворечивости и т.д. См.
Типичная высокоуровневая структура основанного на сигналах приложения выглядит таким образом:
for(;;){
/*
/*
}
Оригинальным интерфейсом V7 для ожидания сигнала является pause()
:
#include
int pause(void);
pause()
приостанавливает процесс; она возвращается лишь после того, как сигнал будет доставлен и его обработчик вернется из вызова. По определению, pause()
полезна лишь с перехваченными сигналами — игнорируемые сигналы при их появлении игнорируются, а сигналы с действием по умолчанию, завершающим процесс (с созданием файла образа или без него), продолжают действовать так же.
Проблема в только что описанной высокоуровневой структуре приложения кроется в части «Обработка сигнала». Когда этот код запускается, вы не захотите обрабатывать другой сигнал; вы хотите завершить обработку текущего сигнала до перехода к следующему. Одним из возможных решений является структурирование обработчика сигнала таким образом, что он устанавливает флаг и проверяет его в главном цикле: volatile sig_atomic_t signal_waiting = 0; /* true, если не обрабатываются сигналы */
void handler(int sig) {
signal_waiting = 1;
/* Установка других данных, указывающих вид сигнала */
В основном коде флаг проверяется:
for (;;) {
if (!signal_waiting) { /* Если возник другой сигнал, */
pause(); /* этот код пропускается */
signal_waiting = 1;
}