Мораль: не следует рассчитывать на то, что разрешающая способность ваших таймеров будет лучше, чем у системного отсчета.
В QNX/Neutrino у приложений есть возможность программной подстройки аппаратного делителя и ядра вместе с ним (чтобы ядро знало, с какой частотой вызывается ISR таймера). Мы поговорим об этом далее в разделе «Опрос и установка часов реального времени».
Флуктуации отсчета времени
Существует еще одно явление, которое вы должны принимать во внимание. Предположим, что разрешающая способность у вас равна 10 миллисекундам, а вы желаете сформировать задержку длительностью в 20 миллисекунд.
Всегда ли вы можете быть уверены, что от момента вызова функции delay() до возврата из нее пройдет ровно 20 миллисекунд?
Никогда.
На это есть две серьезные причины. Первая причина довольно проста: при блокировании поток изымается из очереди готовности. Это означает, что процессор может перейти к другому потоку вашего приоритета. Когда ваши 20 миллисекунд истекут, ваш поток будет помещен в конец очереди готовности по этому приоритету и будет таким образом оставлен на милость потока, выполняющегося в данный момент. Это относится также к обработчикам прерываний и к потокам более высокого приоритета — то, что ваш поток перешел в состояние READY, еще не означает, что ему сразу предоставят процессор.
Вторая причина несколько более хитрая. Чтобы понять ее смысл, посмотрите на нижеприведенный рисунок.
Флуктуации отсчета времени.
Проблема здесь состоит в том, что ваш запрос является асинхронным по отношению к источнику отсчетов. У вас нет никакой возможности синхронизировать аппаратный таймер с вашим запросом. Поэтому в итоге вы получите интервал задержки где-то в диапазоне от 20 до 30 мс — в зависимости от того, в какой момент между отсчетами аппаратных часов возник ваш запрос.
Это очень важный момент. Флуктуации отсчета времени — одна из печальных жизненных реалий. Единственный способ избавиться от этой проблемы заключается в увеличении разрешающей способности так, чтобы получающиеся погрешности укладывались в пределы установленных допусков. (Как это делается, мы рассмотрим ниже, в разделе «Опрос и установка часов реального времени».). Имейте в виду, что флуктуации проявляются только на первом отсчете таймера — задержка на 100 секунд, реализуемая с помощью таймера с разрешением в 10 мс, попадет в интервал между 100 и 100.01 секундами.
Типы таймеров
Таймер, работу которого мы только что обсудили, называют относительным таймером. Для такого таймера период ожидания задается относительно текущего времени. Если бы вы пожелали задержать выполнение вашего потока до 12 часов 4 минут 33 секунд EDT (Eastern Daylight Time — восточное поясное время — прим. ред.) 20 января 2005 года, вам пришлось бы сначала рассчитать точное число секунд от «сейчас» до выбранного вами момента и включить относительный таймер с задержкой на это число секунд. Поскольку это довольно часто встречающаяся операция, в QNX/Neutrino реализованы абсолютные таймеры, которые обеспечивают задержку до заданного времени (а не на заданное время, как в случае относительного таймера).
А что если вы захотите сделать что-нибудь полезное, пока поток ожидает наступления установленной даты? Или делать что-либо и получать «синхроимпульс» каждые 27 секунд? Здесь нельзя просто так позволить себе спать!
Как мы уже обсуждали в главе «Процессы и потоки», вы можете просто запустить другой поток, и пусть он выполняет работу, пока ваш поток спит. Однако, поскольку мы говорим сейчас о таймерах, посмотрим, как это можно сделать другим способом.
В зависимости от выбранной цели, вы можете сделать это с помощью либо периодического, либо однократного таймера. Периодический таймер — это таймер, который срабатывает периодически, уведомляя поток (снова и снова), что истек некоторый временной интервал. Однократный таймер — это таймер, который срабатывает только один раз.
Реализация этих таймеров в ядре основана на том же самом принципе, что и в случае с таймером задержки из нашего первого примера. Ядро запоминает абсолютное значение времени (если вы укажете сделать именно так) и хранит его. Обработчик прерываний таймера сравнивает сохраненное значение времени с текущим.
Однако, вместо удаления из очереди на выполнение после системного вызова, на этот раз ваш поток продолжит работу. И в момент, когда суточное время достигнет заданного вами и хранимого в памяти момента времени, ядро уведомит ваш поток о том, что назначенное время пришло.
Схема уведомления
Как получить уведомление о тайм-ауте? При использовании таймера задержки вы получаете уведомление просто посредством возвращения в состояние READY.
При использовании периодически и однократных таймеров у вас появляется выбор:
• послать импульс;
• послать сигнал:
• создать поток.
Импульсы мы уже обсуждали главе «Обмен сообщениями»; сигналы — стандартный для UNIX механизм. Здесь же мы кратко рассмотрим уведомления при помощи создания потока.
Независимо от выбранной вами схемы уведомления, вам обязательно придется заполнять структуру struct sigevent
. Давайте вкратце посмотрим, как это делается.
struct sigevent {
int sigev_notify;
union {
int sigev_signo;
int sigev_coid;
int sigev_id;
void (*sigev_notify_function)(union sigval);
};
union sigval sigev_value;
union {
struct {
short sigev_code;
short sigev_priority;
};
pthread_attr_t *sigev_notify_attributes;
};
};
Обратите внимание, что в приведенной декларации используются неименованные объединения и структуры. Внимательное изучение файла заголовка покажет вам, как этот трюк проходит с компиляторами, не поддерживающими такую особенность. По существу там есть директива #define
, которая заставляет именованные объединения и структуры выглядеть неименованными. Подробнее см. <sys/siginfo.h>
.
Первое поле, которое вы должны заполнить, — это элемент sigev_notify, который определяет выбранный вами тип уведомления:
SIGEV_PULSE
Будет передан импульс.
SIGEV_SIGNAL, SIGEV_ SIGNAL _CODE или SIGEV_SIGNAL_THREAD
Будет передан сигнал.
SIGEV_UNBLOCK
В данном случае не используется; предназначен для тайм-аутов ядра (см. ниже в разделе «Тайм-ауты ядра»).
SIGEV_INTR
В данном случае не используется; предназначен для прерываний (см. главу «Прерывания»),
SIGEV_THREAD
Будет создан поток.
Поскольку мы намерены использовать структуру struct sigevent
для таймеров, нас будут интересовать только такие значения sigev_notify как SIGEV_PULSE, SIGEV_SIGNAL* и SIGNAL_THREAD; остальные мы рассмотрим в соответствующих их применению разделах.