Для создания потока по срабатыванию таймера установите поле sigev_notify в значение SIGEV_THREAD и заполните следующие поля:
Поле Значение и смысл
sigev_notify_function Адрес функции, возвращающей
void* и принимающей
void*, которая будет вызвана при возникновении события.
sigev_value Значение, которое будет передано функции
sigev_notify_function() в качестве параметра.
sigev_notify_attributes Атрибутная запись потока (см. главу «Процессы и потоки», параграф «Атрибутная запись потока»).
Этот тип уведомления воистину страшен. Если ваш таймер будет срабатывать слишком часто, и при этом будут готовы к выполнению потоки с более высоким приоритетом, чем вновь создаваемые, то у вас быстро вырастет огромная куча заблокированных потоков, и они съедят все ресурсы вашей машины. Пользуйтесь этим типом уведомления с осторожностью!
Общие приемы программирования уведомлений
В файле <sys/siginfo.h> есть ряд удобных макросов упрощения заполнения полей в структурах:
SIGEV_SIGNAL_INIT(eventp, signo)
Установите eventp в SIGEV_SIGNAL и впишите соответствующий номер сигнала signo.
SIGEV_SIGNAL_CODE_INIT(eventp, signo, value, code)
Установите поле eventp в SIGEV_SIGNAL_CODE, укажите номер сигнала в signo, а также задайте значения полей value и code.
SIGEV_SIGNAL_THREAD_INIT(eventp, signo, value, code)
Установите eventp в SIGEV_SIGNAL_THREAD, укажите номер сигнала в signo, а также задайте значения полей value и code.
SIGEV_PULSE_INIT(eventp, coid, priority, code, value)
Установите eventp в SIGEV_SIGNAL_PULSE, укажите идентификатор соединения в coid, а также параметры priority, code и value. Отметьте, что для priority есть специальное значение SIGEV_PULSE_PRIO_INHERIT, которое предотвращает изменение приоритета принимающего потока.
SIGEV_UNBLOCK_INIT(eventp)
Установите eventp в SIGEV_UNBLOCK.
SIGEV_INTR_INIT(eventp)
Установите eventp в SIGEV_INTR.
SIGEV_THREAD_INIT(eventp, func, attributes)
Задайте значения eventp, функции потока func и атрибутной записи attributes.
Уведомление при помощи импульса
Предположим, что вы разрабатываете сервер, который будет обречен провести большую часть своей жизни в RECEIVE-блокированном состоянии, ожидая сообщение. Идеальным вариантом здесь было бы принять специальное сообщение, указывающее, что момент, которого мы так долго ждали, наконец настал.
Как раз при таком сценарии и надо использовать импульсы в качестве схемы уведомления. В разделе «Применение таймеров», представленном ниже, я приведу пример кода, который можно использовать для периодического получения импульсов.
Предположим, что, с другой стороны, вы выполняете некоторую работу, но не желаете, чтобы она продолжалась вечно. Например, вы ожидаете возврата из некоторой функции, но не можете точно предсказать, сколько времени на это потребуется.
В этом случае оправданным выбором является использование уведомления при помощи сигнала — возможно, даже с обработчиком. (Другой вариант, который мы обсудим позже, заключается в использовании тайм-аутов ядра; см. также параграф «_NTO_CHF_UNBLOCK» в главе «Обмен сообщениями»). В параграфе «Применение таймеров», представленном ниже, мы рассмотрим пример, использующий сигналы.
Если вы вообще не собираетесь принимать сообщения, то использование сигнала и функции sigwait() является более экономной альтернативой созданию канала для принятия импульсного сообщения.
Изучив все красоты теории, давайте теперь переключим наше внимание на конкретные образцы кода, чтобы посмотреть, что можно сделать при помощи таймеров.
Чтобы работать с таймером, вам потребуется:
1. Создать объект типа «таймер».
2. Выбрать схему уведомления (сигнал, импульс или создание потока) и создать структуру уведомления (struct sigevent).
3. Выбрать нужный тип таймера (относительный или абсолютный, и однократный или периодический).
4. Запустить таймер.
Давайте теперь рассмотрим все это по порядку.
Первый этап — это создание таймера с помощью функции timer_create():
#include <time.h>
#include <sys/siginfo.h>
int timer_create(clockid_t clock_id,
struct sigevent *event, timer_t *timerid);
Аргумент clock_id сообщает функции timer_create(), на какой временном базисе вы формируете таймер. Это вещь из области POSIX — стандарт утверждает, что на различных платформах вы можете использовать различные типы временных базисов, но любая платформа должна, по меньшей мере, поддерживать базис CLOCK_REALTIME. В QNX/Neutrino есть три базиса:
• CLOCK_REALTIME
• CLOCK_SOFTTIME
• CLOCK_MONOTONIC
Сигнал, импульс или поток?
Оставим пока на время варианты CLOCK_SOFTTIME и CLOCK_MONOTONIC, поскольку они еще пока (на момент написания книги — прим. ред.) не реализованы. Втором параметром является указатель на структуру struct sigevent. Эта структура применяется для того, чтобы сообщить ядру о типе события, которое таймер должен сгенерировать при срабатывании. Мы уже обсуждали порядок заполнения struct sigevent, когда говорили о выборе схемы уведомления.
Итак, мы вызываем функцию timer_create() с временным базисом CLOCK_REALTIME и указателем на структуру struct sigevent, и ядро создает объект типа «таймер» (он возвращается в последнем аргументе). Этот объект представляет собой небольшое целое число, которое является номером таймера в таблице таймеров ядра. Считайте его просто «дескриптором».
На этот момент никаких событий пока не происходит. Вы просто создали таймер, но ведь вы еще не включали его.
Создав таймер, теперь вы должны решить, какого типа будет этот таймер. Это осуществляется путем комбинирования аргументов функции timer_settime(), которая обычно применяется для собственно запуска таймера:
#include <time.h>
int timer_settime(timer_t timerid, int flags,
struct itimerspec *value, struct itimerspec *oldvalue);
Аргумент timerid — это число, которое вы получите обратно по вызову функции timer_create(). Вы можете создать множество таймеров, а затем вызывать timer_settime() для них по отдельности, когда вам это будет необходимо.
С помощью аргумента flags вы определяете тип таймера — абсолютный или относительный.
Если вы передаете константу TIMER_ABSTIME, получается абсолютный таймер, как вы и могли бы предположить. Затем вы передаете реальные дату и время срабатывания таймера.
Если вы передаете нуль, таймер предполагается относительным.
Давайте посмотрим, как определяется время. Вот ключевые фрагменты двух структур данных из <time.h>:
struct timespec {
long tv_sec, tv_nsec;
};
struct itimerspec {
struct timespec it_value, it_interval;
};
В структуре struct itimerspec есть два поля:
it_value однократно используемое значение
it_interval перезагружаемое значение
Параметр it_value задает либо интервал времени от настоящего момента до момента срабатывания таймера (в случае относительного таймера), либо собственно время срабатывания (в случае абсолютного таймера). После срабатывания таймера значение величины it_interval задает относительное время для повторной загрузки таймера, чтобы он мог сработать снова. Заметим, что задание для it_interval нулевого значения преобразует данный таймер в однократный. Вы можете предположить, что чтобы создать «исключительно периодический» таймер, вам следует установить параметр it_interval в значение интервала перезагрузки, а параметр it_value — в нуль. К сожалению, последнее неверно — установка параметра it_value в нуль выключает таймер. Если вы хотите создать «исключительно периодический» таймер, присвойте it_value и it_interval одинаковые значения и создайте таймер как относительный. Такой таймер сработает один раз (с задержкой it_value), а затем будет циклически перезагружаться с задержкой it_interval.