alarm((argc > 1)? getInt(argv[1], GN_NONNEG, "num-secs"): 10);
numRead = read(STDIN_FILENO, buf, BUF_SIZE);
savedErrno = errno; /* На случай, если alarm() изменит errno */
alarm(0); /* Убеждаемся, что таймер выключен */
errno = savedErrno;
/* Определяем результат чтения */
if (numRead == –1) {
if (errno == EINTR)
printf("Read timed out\n");
else
errMsg("read");
} else {
printf("Successful read (%ld bytes): %.*s",
(long) numRead, (int) numRead, buf);
}
exit(EXIT_SUCCESS);
}
timers/timed_read.c
Стоит отметить наличие потенциального состояния гонки в программе из листинга 23.2. Если таймер сработает после вызова alarm(), но перед началом чтения, операция read() не будет прервана обработчиком сигнала. Но, поскольку в таких ситуациях обычно используется довольно продолжительное время ожидания (минимум несколько секунд), эта проблема крайне маловероятна, поэтому на практике данный подход является вполне разумным. В книге [Stevens & Rago, 2005] предлагается альтернативная методика на основе функции longjmp(). Еще одним вариантом работы с операциями ввода/вывода является применение системных вызовов select() или poll() (см. главу 55), которые изначально поддерживают время ожидания и позволяют отслеживать ввод-вывод сразу для нескольких дескрипторов.
Иногда процесс нужно приостановить на определенное время. Этого можно добиться с помощью связки из вызова sigsuspend() и интерфейсов для работы с таймерами, с которыми мы только что познакомились, однако проще будет воспользоваться одной из функций перехода в режим сна.
23.4.1. Переход в режим сна (низкая точность): вызов sleep()
Функция sleep() приостанавливает выполнение вызывающего процесса на определенное количество секунд, указанное в аргументе seconds, или до момента перехвата сигнала (который прерывает данный вызов).
В момент возобновления работы вызов sleep() возвращает 0. Если вызов был прерван сигналом, он возвращает количество секунд, остававшееся до возобновления. Как и в случае с таймерами, установленными с помощью функций alarm() и setitimer(), нагрузка на систему может привести к тому, что возобновление работы процесса будет запланировано не сразу после вызова sleep(), а с некоторой (обычно небольшой) задержкой.
#include
unsigned int sleep(unsigned int seconds);
Возвращает 0 при нормальном завершении или количество секунд, остававшихся до возобновления работы, в случае преждевременного прерывания
Стандарт SUSv3 не описывает возможное взаимодействие между операцией sleep() и вызовами alarm() и setitimer(). В Linux функция sleep() реализована в виде обертки для вызова nanosleep() (см. подраздел 23.4.2), благодаря чему она никак не влияет на функции alarm() и setitimer(). Однако во многих других системах, особенно старых, операция sleep() реализована с помощью вызова alarm() и обработчика сигнала SIGALRM. При написании переносимого кода ее не следует смешивать с функциями alarm() и setitimer().
23.4.2. Переход в режим сна (высокая точность): вызов nanosleep()
Функция nanosleep() по принципу своей работы похожа на sleep(), но имеет ряд преимуществ, включая повышенную точность при задании интервала.
#define _POSIX_C_SOURCE 199309
#include
int nanosleep(const struct timespec *
Возвращает 0 в случае успешного завершения или –1, если произошли ошибка или прерывание
Аргумент request обозначает продолжительность сна и является указателем на структуру следующего вида:
struct timespec {
time_t tv_sec; /* Секунды */
long tv_nsec; /* Наносекунды */
};
Поле tv_nsec содержит количество наносекунд. Это должно быть число в диапазоне от 0 до 999 999 999.
Еще одним преимуществом функции nanosleep() является то, что стандарт SUSv3 явно запрещает реализовывать ее с помощью сигналов. Это означает, что, в отличие от sleep(), ее можно использовать в сочетании с вызовом alarm() или setitimer().
Несмотря на это, функция nanosleep() тоже может быть прервана обработчиком сигнала. В этом случае она вернет –1 и присвоит переменной errno ошибку EINTR; если же аргумент remain не равен NULL, буфер, на который он указывает, будет содержать время, остававшееся до возобновления работы. При желании мы можем использовать это значение, чтобы перезапустить системный вызов и продолжить сон. Такой подход продемонстрирован в листинге 23.3. В качестве аргументов командной строки данная программа принимает секунды и наносекунды для вызова nanosleep(). Программа циклически вызывает nanosleep(), пока не завершится указанный интервал времени. Если вызов прервется обработчиком сигнала SIGINT (сгенерированного с помощью сочетания клавиш Ctrl+C), он будет перезапущен с помощью значения, возвращенного в буфере remain. Запустив эту программу, мы увидим следующее: