В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Время и работа с ним
Интервальные таймеры полезны как средство управления ходом выполнения программы. В частности, они позволяют ограничить время работы отдельных фрагментов, что особенно важно для приложений реального времени. В программе, показанной в листинге 12.36, фигурируют две функции обработки данных; каждой из них отводится не более заданного константой IT_PERIOD числа секунд.
/* * * * * * * * * * * * * * * * * * * * */ /* Программа вызывает функции обработки */ /* и контролирует время их выполнения */ /* с помощью интервального таймера */ /* * * * * * * * * * * * * * * * * * * * */ #include <stdio.h> #include <sys/time.h> #include <signal.h> #include <setjmp.h> /* Период интервального таймера (в секундах) */ #define IT_PERIOD 1 static jmp_buf buf_env; /* Буфер для функций setjmp и longjmp */ static double s; /* Результат функций обработки данных */ /* Функция обработки срабатывания таймера реального времени */ /* (сигнал SIGALRM) */ static void proc_sigalrm (int dummy) { printf ("Текущий результат текущей функции обработки данных: %g\n", s); longjmp (buf_env, 1); } /* Первая функция обработки данных */ /* (вычисляет ln (2)) */ static void proc_data_1 (void) { double d = 1; int i; s = 0; for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } } /* Вторая функция обработки данных */ /* (вычисляет sqrt (2)) */ static void proc_data_2 (void) { s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s - 2) > 0.000000001); } int main (void) { /* Массив указателей на функции обработки данных */ void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL}; /* Указатель на указатель */ /* на текущую функцию обработки данных */ void (**tfptr) (void); /* Вспомогательная временная переменная */ void (*tmpfptr) (void); struct itimerval itvl; struct sigaction sact; sigset_t sset; int i; /* Установим реакцию на сигнал SIGALRM */ if (sigemptyset (&sset) < 0) { perror ("SIGEMPTYSET"); return (1); } sact.sa_handler = proc_sigalrm; sact.sa_flags = SA_NODEFER; /* Не блокировать SIGALRM */ /* в функции обработки этого сигнала */ sact.sa_mask = sset; if (sigaction (SIGALRM, &sact, NULL) < 0) { perror ("SIGACTION"); return (2); } /* На всякий случай сделаем таймер реального времени периодическим */ itvl.it_interval.tv_sec = IT_PERIOD; itvl.it_interval.tv_usec = 0; /* Цикл вызова функций обработки данных. */ /* Выполним его дважды */ for (i = 0; i < 2; i++) { tfptr = fptrs; (void) setjmp (buf_env); /* Сюда вернется управление после срабатывания таймера */ while ((tmpfptr = *tfptr++) != NULL) { /* Даже если предыдущая функция обработки данных */ /* закончилась до того, как сработал таймер, */ /* обеспечим текущей функции полный интервал */ itvl.it_value.tv_sec = IT_PERIOD; itvl.it_value.tv_usec = 0; if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { perror ("SETITIMER"); return (3); } (*tmpfptr) (); printf ("Результат текущей функции обработки данных: %g\n", s); } } return 0; }Листинг 12.36. Пример программы, использующей интервальные таймеры реального времени для управления ходом выполнения программы.
Результаты работы приведенной программы могут выглядеть так, как показано в листинге 12.37. Видно, что первая функция обработки данных не уложилась в отведенное время, поэтому ее выполнение было прервано срабатыванием таймера ; второй функции этого времени оказалось достаточно.
Текущий результат текущей функции обработки данных: 0.693147 Результат текущей функции обработки данных: 1.41421 Текущий результат текущей функции обработки данных: 0.693147 Результат текущей функции обработки данных: 1.41421Листинг 12.37. Возможные результаты работы программы, использующей интервальные таймеры реального времени для управления ходом выполнения программы.
Комбинация нелокальных переходов и асинхронной обработки сигналов представляет собой взрывоопасную смесь, обращаться с которой следует крайне осторожно. Несмотря на кажущуюся простоту, в пояснениях нуждается целый ряд фрагментов приведенной выше программы.
Во-первых, обратим внимание на использование флага SA_NODEFER, предписывающего не блокировать сигнал при входе в функцию его (сигнала) обработки. Этот флаг необходим, поскольку не происходит нормального выхода из функции proc_sigalrm(), а значит, не происходит и восстановления маски сигналов процесса. В результате, после первого входа в функцию обработки сигнала SIGALRM он рискует остаться заблокированным, вследствие чего процесс не будет извещен о последующих срабатываниях интервального таймера реального времени.
Во-вторых, следует исходить из возможности приостановки выполнения процесса в произвольном месте, в частности, внутри вычисления выражений. Если оформить вызов функции обработки данных в виде инструкции (**tfptr++) (), то нет гарантии, что компилятор не запланирует увеличение указателя tfptr после возврата из функции, выполнение которой может быть прервано срабатыванием таймера. В таком случае управление вернется в начало внутреннего цикла, а указатель не увеличится, что приведет к повторному вызову той же функции обработки данных. Чтобы справиться с этой проблемой, пришлось задействовать вспомогательную временную переменную tmpfptr.
В-третьих, процессу может выделяться сколь угодно малая доля процессорного времени, а потому соотношение между виртуальным и реальным временем оказывается недетерминированным. Если, например, расположить вызов setjmp() после взведения таймера (см. листинг 12.38), то таймер реального времени может сработать до инициализации буфера buf_env, после чего нелокальный переход из функции обработки сигнала приведет к полному хаосу.
if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { . . . } . . . (void) setjmp (buf_env);Листинг 12.38. Пример некорректной последовательности взведения таймера и инициализации структуры для нелокального перехода.
Проще (и, вероятно, лучше) всего бороться с подобными проблемами, манипулируя маской сигналов процесса, попеременно блокируя и разрешая обработку сигнала SIGALRM. Мы, однако, попытались обойтись без этого и будем признательны, если читатель обнаружит ошибки в нашем варианте программы и сообщит о них.
Еще один любопытный момент технического характера состоит в том, что обычно проверяют значение, возвращаемое setjmp(), чтобы понять, как управление оказалось в данной точке - после естественного возврата или нелокального перехода. Но поскольку в приведенной программе это ни на что не влияет, проверку можно опустить.
К теме интервальных таймеров логически примыкает функция alarm() (см. листинг 12.39), позволяющая вызывающему процессу заказать доставку сигнала SIGALRM через заданное число секунд реального времени. Если в момент обращения к alarm() предыдущий заказ существовал и еще не был выполнен, он отменяется, а если значение аргумента seconds отлично от нуля, его место занимает новый.
#include <unistd.h> unsigned alarm (unsigned seconds);Листинг 12.39. Описание функции alarm().
При наличии предыдущего заказа функция alarm() возвращает число секунд, оставшееся до его выполнения; в противном случае результат равен нулю.
Примеры использования функции alarm() были приведены при рассмотрении средств межпроцессного взаимодействия.