Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » Linux программирование в примерах - Арнольд Роббинс

Linux программирование в примерах - Арнольд Роббинс

Читать онлайн Linux программирование в примерах - Арнольд Роббинс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 90 91 92 93 94 95 96 97 98 ... 165
Перейти на страницу:

ЗАМЕЧАНИЕ. Как указывает цитата в начале, использование сигналов для 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.8. Важные сигналы специального назначения

Некоторые сигналы имеют особое назначение. Здесь мы опишем наиболее важные.

10.8.1. Сигнальные часы: sleep(), alarm() и SIGALARM

Часто бывает необходимо написать программу в виде

while (/* некоторое неверное условие */) {

 /* подождать некоторое время */

}

Часто такая потребность возникает в сценариях оболочки, например, в ожидании регистрации определенного пользователя:

until who | grep '^arnold' > /dev/null

do

 sleep 10

done

Два механизма, один низкоуровневый, другой высокоуровневый, позволяют работающему процессу узнать, когда истекло заданное количество секунд.

10.8.1.1. Труднее, но с большим контролем: alarm() и SIGALARM

Основным строительным блоком является системный вызов alarm():

#include <unistd.h> /* POSIX */

unsigned int alarm(unsigned int seconds);

После того, как alarm() возвратится, программа продолжает работать. Однако, когда истекают seconds секунд, ядро посылает процессу SIGALARM. Действием по умолчанию является завершение процесса, но вы скорее всего вместо этого установите обработчик сигнала для SIGALARM.

Возвращаемое значение либо 0, либо, если был установлен предыдущий сигнальный интервал, число секунд, остающихся до его завершения. Однако, для процесса имеется лишь один такой сигнальный интервал; предыдущий отменяется, а новый помещается на его место.

Преимуществом здесь является то, что со своим установленным обработчиком вы можете делать при поступлении сигнала все, что хотите. Недостаток же в том, что приходится быть готовым к работе в нескольких контекстах: основном контексте и контексте обработчика сигнала.

10.8.1.2. Простой и легкий: sleep()

Более легкий способ ожидания истечения фиксированного промежутка времени заключается в использовании функции sleep():

#include <unistd.h> /* POSIX */

unsigned int sleep(unsigned int seconds);

Возвращаемое значение равно 0, если процесс проспал все отведенное время. В противном случае возвращается оставшееся для сна время. Это последнее значение может возникнуть в случае, если появился сигнал, пока процесс дремал.

ЗАМЕЧАНИЕ. Функция sleep() часто реализуется через сочетание signal(), alarm() и pause(). Такой подход делает опасным смешивание sleep() с вашим собственным вызовом alarm() (или расширенной функцией setitimer(), описанной в разделе 14.3.3 «Интервальные таймеры setitimer() и getitimer()») Чтобы теперь узнать о функции nanosleep(), см. раздел 14.3.4 «Более точные паузы: nanosleep()».

10.8.2. Сигналы, управляющие заданиями

1 ... 90 91 92 93 94 95 96 97 98 ... 165
Перейти на страницу:
Тут вы можете бесплатно читать книгу Linux программирование в примерах - Арнольд Роббинс.
Комментарии