Книга: Linux программирование в примерах
10.7. Сигналы для межпроцессного взаимодействия
10.7. Сигналы для межпроцессного взаимодействия
«ЭТО УЖАСНАЯ МЫСЛЬ! СИГНАЛЫ НЕ ПРЕДНАЗНАЧЕНЫ ДЛЯ ЭТОГО! Просто скажите НЕТ».
Одним из главных механизмов межпроцессного взаимодействия (IPC) являются каналы, которые описаны в разделе 9.3 «Базовая межпроцессная коммуникация каналы и FIFO». Сигналы также можно использовать для очень простого IPC[111]. Это довольно грубо; получатель может лишь сказать, что поступил определенный сигнал. Хотя функция sigaction()
позволяет получателю узнать PID и владельца процесса, пославшего сигнал, эти сведения обычно не очень помогают.
ЗАМЕЧАНИЕ. Как указывает цитата в начале, использование сигналов для IPC почти всегда является плохой мыслью. Мы рекомендуем по возможности избегать этого. Но нашей целью является научить вас, как использовать возможности Linux/Unix, включая их отрицательные моменты, оставляя за вами принятие информированного решения, что именно использовать.
Сигналы в качестве IPC для многих программ могут быть иногда единственным выбором. В частности, каналы не являются альтернативой, если две взаимодействующие программы не запущены общим родителем, а файлы FIFO могут не быть вариантом, если одна из взаимодействующих программ работает лишь со стандартными вводом и выводом. (Примером обычного использования сигналов являются определенные системные программы демонов, таких, как xinetd
, которые принимают несколько сигналов, уведомляющих, что нужно повторно прочесть файл настроек, осуществить проверку непротиворечивости и т.д. См. xinetd(8) в системе GNU/Linux и inetd(8) в системе Unix.)
Типичная высокоуровневая структура основанного на сигналах приложения выглядит таким образом:
for(;;){
/* Ожидание сигнала */
/* Обработка сигнала */
}
Оригинальным интерфейсом V7 для ожидания сигнала является pause()
:
#include <unistd.h> /* POSIX */
приостанавливает процесс; она возвращается лишь после того, как сигнал будет доставлен и его обработчик вернется из вызова. По определению,
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;
}
/* Определение поступившего сигнала */
signal_waiting = 0;
/* Обработка сигнала */
}
К сожалению, этот код изобилует условиями гонки:
for (;;) {
if (!signal_waiting) {
/* <--- Сигнал может появиться здесь, после проверки условия! */
pause(); /* pause() будет вызвана в любом случае */
signal_waiting = 1;
}
/* Определение поступившего сигнала
<--- Сигнал может переписать здесь глобальные данные */
signal_waiting = 0;
/* Обработка сигнала
<--- То же и здесь, особенно для нескольких сигналов */
}
Решением является блокирование интересующего сигнала в любое время, кроме ожидания его появления. Например, предположим, что интересующим нас сигналом является SIGINT
:
void handler(int sig) {
/* sig автоматически блокируется функцией sigaction() */
/* Установить глобальные данные, касающиеся этого сигнала */
}
int main(int argc, char **argv) {
sigset_t set;
struct sigaction act;
/* ...обычная настройка, опции процесса и т.д. ... */
sigemptyset(&set); /* Создать пустой набор */
sigaddset(&set, SIGINT); /* Добавить в набор SIGINT */
sigprocmask(SIG_BLOCK, &set, NULL); /* Заблокировать его */
act.sa_mask = set; /* Настроить обработчик */
act.sa_handler = handler;
act.sa_flags = 0;
sigaction(sig, &act, NULL); /* Установить его */
... /* Возможно, установить отдельные обработчики */
... /* для других сигналов */
sigemptyset(&set); /* Восстановить пустой, допускает SIGINT */
for (;;) {
sigsuspend(&set); /* Ждать появления SIGINT */
/* Обработка сигнала. SIGINT здесь снова блокируется */
}
/* ...любой другой код... */
return 0;
}
Ключом к использованию этого является то, что sigsuspend()
временно заменяет маску сигналов процесса маской, переданной в аргументе. Это дает SIGINT
возможность появиться. При появлении он обрабатывается; обработчик сигнала возвращается, а вслед за ним возвращается также sigsuspend()
. Ко времени возвращения sigsuspend()
первоначальная маска процесса снова на месте.
Вы легко можете расширить этот пример для нескольких сигналов, блокируя в main()
и в обработчике все интересующие сигналы и разблокируя их лишь в вызове sigsuspended()
.
При наличии всего этого не следует в новом коде использовать pause()
. pause()
был стандартизован POSIX главным образом для поддержки старого кода. То же самое верно и для функции sigpause()
System V Release 3. Вместо этого, если нужно структурировать свое приложение с использованием сигналов для IPC, используйте исключительно функции API sigsuspend()
и sigaction()
.
ЗАМЕЧАНИЕ. Приведенный выше код предполагает, что маска сигналов процесса начинается пустой. Код изделия должен вместо этого работать с любой маской сигналов, имеющейся на момент запуска программы.
- 10.1. Введение
- 10.2. Действия сигналов
- 10.3. Стандартные сигналы С: signal() и raise()
- 10.4. Обработчики сигналов в действии
- 10.5. API сигналов System V Release 3: sigset() и др.
- 10.6. Сигналы POSIX
- 10.7. Сигналы для межпроцессного взаимодействия
- 10.8. Важные сигналы специального назначения
- 10.9. Сигналы, передающиеся через fork() и exec()
- 10.10. Резюме
- Упражнения
- Глава 10 Сигналы
- 10.8. Важные сигналы специального назначения
- Сигналы
- 7.2 СИГНАЛЫ
- 12.3. Доступные сигналы
- 4. Стадии бизнес-процесса взаимодействия с клиентами
- 4.9 Обеспечение взаимодействия устройств Fibre Channel
- Звуковые сигналы BIOS
- 12.6. Сигналы реального времени
- 7.2.6.3. Системные демоны и традиционные сигналы
- 5.5. Диаграммы взаимодействия
- Сигналы и шумы