Чтение онлайн

ЖАНРЫ

Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform

Кёртен Роб

Шрифт:
Функции работы с пулами потоков

Теперь, когда мы достаточно хорошо владеем методикой управления числом потоков в пуле, давайте обратимся к другим элементам атрибутной записи пула потоков:

// Функции и дескриптор пула потоков

THREAD_POOL_HANDLE_T *handlе;

THREAD_POOL_PARAM_T *(*block_func)(

 THREAD_POOL_PARAM_T *ctp);

void (*unblock_func)(THREAD_POOL_PARAM_T *ctp);

int (*handler_func)(THREAD_POOL_PARAM_T *ctp);

THREAD_POOL_PARAM_T *(*context_alloc)(

 THREAD_POOL_HANDLE_T *handle);

void (*context_free)(THREAD_POOL_PARAM_T *ctp);

Повторно

обратимся к рисунку «Жизненный цикл пула потоков». Из рисунка видно, что при создании потока каждый раз вызывается функция context_alloc. (Аналогично, при уничтожении потока вызывается функция context_tree). Элемент атрибутной записи с именем handler передается функции context_alloc в качестве ее единственного параметра. Функция context_alloc ответственна за индивидуальные настройки потока и возвращает указатель на контекст (списках параметров называемый ctp). Заметьте, что содержание этого указателя — исключительно ваша забота; библиотеке абсолютно все равно, что вы в него поместите.

Теперь, когда контекст создан функцией context_alloc, вызывается функция block_func для перевода потока в режим блокирования. Заметьте, что функция block_func получает на вход результат работы функции context_alloc. После того как функция block_func разблокируется, она возвращает указатель на контекст, который библиотека передает функции handler_func. Функция handler_func отвечает за выполнение «работы» — например, в типовом варианте именно она обрабатывает сообщение от клиента. На данный момент принято, что функция handler_func должна возвращать нуль — ненулевые значения зарезервированы QSSL для будущего функционального расширения. Функция unblock_func также в настоящее время зарезервирована, поэтому просто оставьте там NULL.

Возможно, ситуацию немного прояснит приведенный ниже пример псевдокода (он основан все на том же рисунке «Жизненный цикл потока в пуле потоков»):

FOREVER DO

 IF (#threads < lo_water) THEN

IF (#threads < maximum) THEN

create new thread

context = (*context_alloc)(handle);

ENDIF

 ENDIF

 retval = (*block_func)(context);

 (*handler_func)(retval);

 IF (#threads > hi_water) THEN

(*context_free)(context)

kill thread

 ENDIF

DONE

Отметим, что приведенная выше программа излишне упрощена. Ее назначение состоит только в том, чтобы продемонстрировать вам поток данных по параметрам ctp и handler и дать вам некоторое представление об алгоритмах,

которые обычно применяются для управления числом потоков.

Диспетчеризация и реальный мир

До настоящего момента мы обсуждали дисциплины диспетчеризации и состояния потоков, но практически ничего не сказали относительно того, почему и когда происходит собственно перепланирование. Существует распространенное заблуждение, что перепланирование «просто случается», безо всяких реальных причин. И в общем-то, для проектирования это довольно полезная абстракция! Однако, очень важно понимать, почему происходит перепланирование. Вспомним рисунок «Схема алгоритма диспетчеризации» (в разделе «Роль ядра»).

Перепланирование может иметь только три причины:

• аппаратное прерывание;

• системный вызов;

• сбой (исключение).

Перепланирование по аппаратному прерыванию

Перепланирование из-за аппаратного прерывания можно разделить на две категории:

• по прерыванию от таймеров;

• по прерыванию от других аппаратных средств.

Часы реального времени генерируют периодические прерывания для ядра, организуя перепланирование во времени.

Например, если вы производите вызов

sleep(10)
, часы реального времени сгенерируют некоторое число прерываний; по каждому прерыванию ядро увеличивает значение системных часов. Когда системные часы покажут, что 10 секунд истекли, ядро перепланирует ваш поток, переведя его в состояние готовности (READY). (Мы рассмотрим этот вопрос более подробно в главе «Часы, таймеры и периодические уведомления»).

Другие потоки могут ожидать аппаратные прерывания от внешних устройств, таких как последовательный порт, жесткий диск или аудио платы. В этом случае они блокируются в ядре, ожидающем аппаратное прерывание. Поток будет переупорядочен ядром только после того, как ядро сгенерирует «событие».

Перепланирование по системным вызовам

Если поток делает системный вызов, перепланирование выполняется немедленно и может рассматриваться как асинхронное в отношении прерываний таймера и других прерываний.

Например, выше мы приводили пример вызова функции

sleep(10)
. Это библиотечная функция языка Си, в конечном счете она транслируется в системный вызов. В тот же самый момент ядро приняло решение о перепланировании, чтобы удалить ваш поток из очереди готовности по соответствующему приоритету и поставить на выполнение другой поток, находящийся в состоянии готовности (READY).

Системных вызов, вызывающи процесс обязательного перепланирования, очень много. Большинство их них достаточно очевидны. Перечислим некоторые из них:

• функции таймера (например, sleep);

• функции обмена сообщениями (например, MsgSendv);

• примитивы работы с потоками (например, pthread_cancel или pthread_join).

Перепланирование по исключительным ситуациям

Последняя из вышеперечисленных причин перепланирования — это сбой процессора (CPU fault), который является исключительной ситуацией (exception) — чем-то средним между аппаратным прерыванием и системным вызовом. Исключительные ситуации асинхронны в отношении ядра (подобно прерыванию), но синхронны с вызывающими их пользовательскими программами (подобно вызову ядра — например, такая исключительная ситуация как деление на ноль). Все рассуждения, относящиеся к перепланированию по прерываниям от аппаратных средств и по системным вызовам, относятся и к исключительным ситуациям тоже.

Поделиться с друзьями: