Это сделано намеренно. Вы можете изменить это, добавив соответственно сообщения MT_NO_WAITERS и MT_NO_SPACE, которыми можно было бы отвечать всякий раз при обнаружении ошибок данного типа.
• Когда клиент-обработчик ждет, а клиент-поставщик пересылает ему данные, мы отвечаем обоим клиентам. Это критично, поскольку мы должны разблокировать обоих клиентов.
• Мы повторно использовали буфер клиента-поставщика для обоих ответов. Этот прием программирования — опять же, вопрос стиля: в большом приложении у вас, вероятно, было бы много типов возвращаемых значений, и вы могли бы и не захотеть повторно использовать одни и те же буферы.
• В приведенном примере используется «щербатый» массив фиксированной длины с флагом «элемент задействован» (clients[i].in_use). Поскольку моей целью здесь является отнюдь не демонстрация хитростей программирования односвязных списков, я использовал простейший для понимания вариант. В конечном же программном продукте, разумеется, имело бы смысл использовать динамический список.
• Когда функция MsgReceive() получает импульс, наше решение относительно того, действительно ли это «наш» импульс, фактически является весьма слабо аргументированным — мы просто предполагаем (согласно комментариям), что все входящие импульсы имеют тип CODE_TIMER. Опять же, в конечном продукте следовало бы проверять значение кода импульса и сообщать о наличии каких-либо аномалий.
Отметим, что в приведенном примере демонстрируется только один способ реализации тайм-аутов клиентуры. Позже, в этой же главе (в разделе «Тайм-ауты ядра») мы поговорим о тайм-аутах ядра. Это еще один способ делать почти то же самое, только управление на этот раз осуществляется клиентом, а не таймером.
Внутренние периодические события серверов
Здесь мы имеем несколько другое применение для периодических сообщений о тайм-аутах, когда эти сообщения предназначены сервером исключительно для внутреннего использования и не имеют никакого отношения к клиенту вообще.
Например, некоторые аппаратные средства могут требовать, чтобы сервер опрашивал их периодически — например, такое может быть в случае сетевого соединения: сервер должен периодически проверять, является ли данное подключение доступным, и это не зависит от команд клиентуры.
Другой вариант — если, например, в аппаратных средствах предусмотрен таймер «выключения по неактивности». Например, если длительное пребывание какого-то аппаратного модуля во включенном состоянии может приводить к неоправданным затратам электроэнергии, то если его никто не использует в течение, скажем, 10 секунд, его можно было бы выключить (или переключить в режим низкого энергопотребления — прим. ред.). Опять же, к клиенту это не имеет никакого отношения (за исключением того, что запрос от клиента отменит режим ожидания) — это просто функция, которую сервер должен уметь предоставлять «своим» аппаратным средствам.
Код в этом случае сильно бы напоминал приведенный выше пример, за исключением того, что вместо списка ожидающих клиентов у вас была бы только одна переменная тайм-аута. С каждым событием от таймера ее значение уменьшалось бы, но пока оно больше нуля, ничего бы не происходило. Когда оно стало бы равным нулю, это вызывало бы отключение аппаратных средств (или какое-либо другое соответствующее действие).
Единственный трюк здесь заключается в том, что всякий раз, когда поступает сообщение от клиента, использующего данные аппаратные средства, вы должны восстановить первоначальное значение этой переменной, поскольку обращение к ресурсу должно сбрасывать «обратный отсчет». И наоборот, аппаратным средствам может потребоваться определенный промежуток времени «на разогрев» после включения. В этом случае после выключения аппаратных средств вам придется при поступлении запроса от клиента организовать еще один таймер, чтобы «придержать» запрос до того момента, пока аппаратные средства не станут готовы.
Таймеры, посылающие сигналы
На настоящий момент мы уже рассмотрели практически все, что относится к таймерам, за исключением одного небольшого момента. Мы обеспечивали отправку импульса, но у нас также есть возможность посылать POSIX-сигналы. Давайте посмотрим, как это делается:
timer_create(CLOCK_REALTIME, NULL, &timerid);
Это простейший способ создать таймер, который будет посылать вам сигнал. Он обеспечивает выдачу сигнала SIGALRM при срабатывании таймера. Если бы мы предоставили struct sigevent, мы могли бы определить, какой именно сигнал мы хотим получить:
struct sigevent event;
SIGEV_SIGNAL_INIT(&event, SIGUSR1);
timer_create(CLOCK_REALTIME, &event, &timerid);
Это обеспечит нам выдачу сигнала SIGUSR1 вместо SIGALRM.
Сигналы таймера перехватываются обычными обработчиками сигналов, здесь нет ничего необычного.
Таймеры, создающие потоки
Если вы хотите по каждому срабатыванию таймера создавать новый поток, то вы можете это сделать с помощью struct sigevent и всех остальных таймерных штук, которые мы только что обсудили:
struct sigevent event;
SIGEV_THREAD_INIT(&event, maintenance_func, NULL);
Однако, пользоваться этим надо очень осторожно, потому что если вы определите слишком короткий интервал, вы можете просто утонуть в создаваемых потоках. Они просто поглотят все ресурсы вашего процессора и оперативной памяти.
Опрос и установка часов реального времени, и кое-что еще
Независимо от применения таймеров, вы можете также опрашивать и устанавливать часы реального времени, а также и плавно подстраивать их. Для этих целей можно использовать следующие функции:
Функция Тип Описание
ClockAdjust() QNX/Neutrino Плавная регулировка времени
ClockCycles() QNX/Neutrino Опрос с высоким разрешением
clock_getres() POSIX Выборка базового разрешения
clock_gettime() POSIX Получение текущего времени суток
ClockPeriod() QNX/Neutrino Получение/установка базового разрешения
clock_settime() POSIX Установка текущего времени суток
ClockTime() QNX/Neutrino Получение/установка текущего времени суток Опрос и установка
Функции clock_gettime() и clock_settime() являются POSIX-функциями, основанными на системном вызове ClockTime(). Эти функции могут применяться для получения и установки текущего времени суток. К сожалению, установка здесь является «жесткой», то есть независимо от того, какое время вы указываете в буфере, оно немедленно делается текущим. Это может иметь пугающие последствия, особенно когда получается, что время «повернуло вспять», потому что устанавливаемое время оказалось меньше «реального». Вообще настройка часов таким способом должна выполняться только при включении питания или когда время сильно не соответствует «реальному».
Если нужна плавная корректировка текущего времени, ее можно реализовать с помощью функции ClockAdjust():
int ClockAdjust(clockid_t id,
const struct _clockadjust *new,
const struct _clockadjust *old);
Параметрами здесь являются источник синхроимпульсов (всегда используйте CLOCK_REALTIME) и параметры new и old. Оба эти параметра являются необязательными и могут быть заданы как NULL. Параметр old просто возвращает текущую корректировку. Работа по корректировке часов управляется параметром new, который является указателем на структуру, содержащую два элемента, tick_nsec_inc и tick_count. Действует функция ClockAdjust() очень просто — каждые tick_count отсчетов системных часов к существующему значению системного времени добавляется корректировка tick_nsec_inc. Это означает, что чтобы передвинуть время вперед («догоняя» реальное), вы задаете для tick_nsec_inc положительное значение. Заметьте, что не надо переводить время назад — вместо этого, если ваши часы спешат, задайте для tick_nsec_inc небольшое отрицательное значение, и ваши часы соответственно замедлят ход. Таким образом, вы немного замедляете часы, пока их показания не будут соответствовать действительности. Существует эмпирическое правило, гласящее, что не следует корректировать системные часы значением, превышающим 10% от базового разрешения вашей системы (см. функцию ClockPeriod() и ее друзей, о них мы поговорим в следующем параграфе).