Тайм-ауты ядра
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,
...);