Выбрать главу

Тайм-ауты ядра

QNX/Neutrino позволяет вам получать тайм-ауты по всем блокированным состояниям. Мы обсуждали эти состояния в главе «Процессы и потоки» в разделе «Состояния потоков». Наиболее часто у вас может возникнуть потребность в этом при обмене сообщениями: клиент, посылая сообщение серверу, не желает ждать ответа «вечно». В этом случае было бы удобно использовать тайм-аут ядра. Тайм-ауты ядра также полезны в сочетании с функцией pthread_join(): завершения потока тоже не всегда хочется долго ждать.

Ниже приводится декларация для функции TimerTimeout(), которая является системным вызовом, ответственным за формирование тайм-аутов ядра.

#include <sys/neutrino.h>

int TimerTimeout(clockid_t id, int flags,

 const struct sigevent *notify,

 const uint64_t *ntime, uint64_t *otime);

Видно, что функция TimerTimeout() возвращает целое число (индикатор удачи/неудачи; 0 означает, что все в порядке, -1 — что произошла ошибка, и ее код записан в errno). Источник синхроимпульсов (CLOCK_REALTIME, и т.п.) указывается в id, параметр flags задает соответствующее состояние (или состояния). Параметр notify всегда должен быть событием уведомления типа SIGEV_UNBLOCK; параметр ntime указывает относительное время, спустя которое ядро должно сгенерировать тайм-аут. Параметр otime показывает предыдущее значение тайм-аута и в большинстве случаев не используется (вы можете передать вместо него NULL).

Важно отметить, что тайм-ауты «взводятся» функцией TimerTimeout(), а запускаются по входу в одно из состояний, указанных в параметре flags. Сбрасывается тайм-аут при возврате из любого системного вызова. Это означает, что вы должны заново «взводить» тайм-аут перед каждым системным вызовом, к которому вы хотите его применить. Сбрасывать тайм-аут после системного вызова не надо — это выполняется автоматически.

Тайм-ауты ядра и функция pthread_join()

Самый простой пример для рассмотрения — это использование тайм-аута с функцией pthread_join(). Вот как это можно было бы сделать:

/*

 * tt1.c

*/

#include <stdio.h>

#include <pthread.h>

#include <inttypes.h>

#include <errno.h>

#include <sys/neutrino.h>

#define SEC_NSEC 1000000000LL // В одной секунде

                              // 1 биллион наносекунд

void* long_thread(void *notused) {

 printf("Этот поток выполняется более 10 секунд\n");

 sleep(20);

}

int main(void) // Игнорировать аргументы

{

 uint64_t timeout;

 struct sigevent event;

 int rval;

 pthread_t thread_id;

 // Настроить событие — это достаточно сделать однажды

 // Либо так, либо event.sigev_notify = SIGEV_UNBLOCK:

 SIGEV_UNBLOCK_INIT(&event);

 // Создать поток

 pthread_create(&thread_id, NULL, long_thread, NULL);

 // Установить тайм-аут 10 секунд

 timeout = 10LL * SEC_NSEC;

 TimerTimeout(CLOCK_REALTIME, _NTO_TIMEOUT_JOIN, &event,

  &timeout, NULL);

 rval = pthread_join(thread_id, NULL);

 if (rval == ETIMEDOUT) {

  printf("Истекли 10 секунд, поток %d все еще"

   " выполняется!\n",

   thread_id);

 }

 sleep(5);

 TimerTimeout(СLOCK_REALTIME, _NTO_TIMEOUT_JOIN, &event,

  &timeout, NULL);

 rval = pthread_join(thread_id, NULL);

 if (rval == ETIMEDOUT) {

  printf("Истекли 25 секунд, поток %d все еще выполняется"

   " (нехорошо)!\n",

   thread_id);

 } else {

  printf("Поток %d завершен (как и ожидалось!)\n",

   thread_id);

 }

}

Мы применили макроопределение SIGEV_UNBLOCK_INIT() для инициализации структуры события, но можно было установить sigev_notify в SIGEV_UNBLOCK и «вручную». Можно было даже сделать еще более изящно, передав NULL вместо struct sigevent — функция TimerTimeout() понимает это как знак, что нужно использовать SIGEV_UNBLOCK.

Если поток (заданный в thread_id) остается работающим более 10 секунд, то системный вызов завершится по тайм-ауту — функция pthread_join() возвратится с ошибкой, установив errno в ETIMEDOUT.

Вы можете использовать и другую «стенографию», указав NULL в качестве значения тайм-аута (параметр ntime в декларации выше), что предпишет ядру не блокироваться в данном состоянии. Этот прием можно использовать для организации программного опроса. (Хоть программный опрос и считается дурным тоном, его можно весьма эффективно использовать в случае с pthread_join(), периодически проверяя, завершился ли нужный поток. Если нет, можно пока сделать что-нибудь другое.)

Ниже представлен пример программы, в которой демонстрируется неблокирующий вызов pthread_join():

int pthread_join_nb(int tid, void **rval) {

 TimerTimeout(CLOCK_REALTIME, _NTO_TIMEOUT_JOIN,

  NULL, NULL, NULL);

 return (pthread_join(tid, rval));

}

Тайм-ауты ядра при обмене сообщениями

Все становятся несколько сложнее, когда вы используете тайм-ауты ядра при обмене сообщениями. Вспомните главу «Обмен сообщениями», раздел «Обмен сообщениями и модель «клиент/сервер») — на момент отправки клиентом сообщения сервер может как ожидать его, так и нет. Это означает, что клиент может заблокироваться как по передаче (если сервер еще не принял сообщение), так и по ответу (если сервер принял сообщение, но еще не ответил). Основной смысл здесь в том, что вы должны предусмотреть оба блокирующих состояния в параметре flags функции TimerTimeout(), потому что клиент может оказаться в любом из них.

Чтобы задать несколько состояний, сложите их операцией ИЛИ (OR):

TimerTimeout(... _NTO_TIMEOUT_SEND | _NTO_TIMEOUT_REPLY,

 ...);