GRID_УП
.pdf151
6 МИКРОЯДРО ОС QNX NEUTRINO
6.1 Введение
Микроядро ОС QNX Neutrino реализует основные функции стандартов POSIX, используемые во встраиваемых системах реального времени, а также базовые службы обмена сообщениями. Те функции, которые не реализованы в микроядре, выполняются дополнительными процессами или разделяемыми библиотеками.
На самых нижних уровнях микроядра расположено всего несколько базовых объектов, а также четко отрегулированные процедуры для управления ими. Это является основой, на которой строится ОС QNX (рис. 6.1).
Интерфейс |
Микроядро |
Объекты |
Поток |
|
Соединение |
Планирование |
|
|
Синхронизация |
|
Поток |
|
Диспетчер |
|
|
|
|
Сигнал |
|
сообщения |
|
|
|
Сообщение |
|
|
Канал |
|
Таймер |
|
Вектор |
|
|
|
|
Таймер |
|
|
Часы |
|
Импульс |
|
|
|
Прерывания |
|
Канал |
Рис. 6.1 — Микроядро ОС QNX Neutrino
152
ВОС QNX Neutrino большинство служб по обеспечению работы в реальном времени и по планированию потоков реализовано прямо в микроядре. Эти службы могут работать без использования дополнительных модулей.
Вмикроядре ОС QNX существуют системные вызовы (kernel calls), которые служат для управления следующими объектами:
•потоками;
•сообщениями;
•сигналами;
•часами;
•таймерами;
•обработчиками прерываний;
•семафорами;
•блокировками взаимного исключения, мьютексами;
•условными переменными;
•барьерами.
ОС QNX целиком построена на основе таких вызовов, причем ядро ОС полностью вытесняемо (preemtable), в том числе и во время обмена сообщениями между процессами.
В микроядро включены службы, которые порождали наиболее короткую ветвь исполняемого кода. Операции, требующие значительных ресурсов (например, загрузка процесса), были переданы внешним процессам и потокам, в которых работа по переключению на контекст другого процесса пренебрежительно мала в сравнении с работой по обработке запроса, выполняемой внутри этого потока.
Оценим работу, которая выполняется между переключениями контекстов во время обмена сообщениями, и учтем высокую скорость их переключений в минимизированном ядре. Очевидно, что объем времени, расходуемый на выполнение этих действий, настолько минимален, что становится пренебрежительно малым в сравнении с работой, производимой для обработки запросов от механизма обмена межзадачными сообщениями.
153
На рис. 6.2 показан механизм вытеснения в ядре для процессоров с архитектурой Intel x86 без поддержки SMP. Запрет прерываний или отсутствие вытеснения происходит только на короткие периоды времени (как правило, эти периоды не превышают порядка сотен наносекунд).
SYSCALL |
Вход |
Микро- |
|
|
|
|
секунды |
|
|
|
|
Операции ядра, в том числе передача сообщений
От микросекунд до миллисекунд
|
Завершение |
Микро- |
|
|
секунды |
|
|
Микро- |
SYSEXIT |
Выход |
|
|
|
секунды |
|
|
|
Прерывания
запрещены
Прерывания разрешены, полное вытеснение
Прерывания разрешены, вытеснения нет
Прерывания
запрещены
Рис. 6.2 — Механизм вытеснения в ОС QNX
154
6.2 Потоки и процессы
6.2.1Вызовы по управлению потоками
Вядре ОС QNX минимальной планируемой и выполняемой единицей является поток. Процесс можно рассматривать как объект, который содержит в себе эти потоки и определяет для их выполнения свое адресное пространство.
Вмикроядре ОС QNX реализован ряд вызовов по управлению потоками POSIX и соответствующие им вызовы микроядра
(табл. 6.1).
Таблица 6.1 — Вызовы по управлению потоками POSIX и соответствующие вызовы микроядра [9]
POSIX-вызов |
Вызов микроядра |
Описание |
pthread_create() |
ThreadCreate() |
Создать поток |
pthread_exit() |
ThreadDestroy() |
Уничтожитьпоток |
pthread_detach() |
ThreadDetach() |
Отсоединить |
|
|
поток, чтобы не |
|
|
ждать его за- |
|
|
вершения |
pthread_join() |
ThreadJoin() |
Присоединить |
|
|
поток и ждать |
|
|
его кода завер- |
|
|
шения |
pthread_cancel() |
ThreadCancel() |
Завершить поток |
|
|
вследующей |
|
|
точке завершения |
отсутствует |
ThreadCtl() |
Изменить харак- |
|
|
теристики пото- |
|
|
ка |
pthread_mutex_init() |
SyncTypeCreate() |
Создать мьютекс |
pthread_mutex_destroy() |
SyncDestroy() |
Уничтожить |
|
|
мьютекс |
pthread_mutex_lock() |
SyncMutexLock() |
Блокировать |
|
|
мьютекс |
pthread_mutex_trylock() |
SyncMutexLock() |
Условно блоки- |
|
|
ровать мьютекс |
|
155 |
|
Продолжение табл. 6.1 |
|
Описание |
POSIX-вызов |
Вызов микроядра |
|
pthread_mutex_unlock() |
SyncMutexUnLock() |
Снять блокиров- |
|
|
ку мьютекса |
pthread_cond_init() |
SyncTypeCreate() |
Создать услов- |
|
|
ную переменную |
pthread_cond_destroy() |
SyncDestroy() |
Уничтожить ус- |
|
|
ловную пере- |
|
|
менную |
pthread_cond_wait() |
SyncCondvarWait() |
Ожидать услов- |
|
|
ную переменную |
pthread_cond_signal() |
SyncCondvarSignal() |
Разблокировать |
|
|
один из потоков, |
|
|
блокированных |
|
|
на условной пе- |
|
|
ременной |
pthread_cond_broadcast() |
SyncCondvarSignal() |
Разблокировать |
|
|
все потоки, бло- |
|
|
кированные на |
|
|
условной пере- |
|
|
менной |
pthread_getschedparam() |
SchedGet() |
Получить пара- |
|
|
метры планиро- |
|
|
вания и дисцип- |
|
|
лину потока |
pthread_setschedparam() |
SchedSet() |
Установить па- |
|
|
раметры плани- |
|
|
рования и дис- |
|
|
циплину потока |
pthread_sigmask() |
SignalProcMask() |
Проверить и вы- |
|
|
вести маску сиг- |
|
|
налов потока |
pthread_kill() |
SignalKill() |
Отправить сиг- |
|
|
нал потоку |
sem_post() |
SyncSemPost() |
Увеличить зна- |
|
|
чение счетчика |
|
|
на семафоре |
sem_wait() |
SynSemWait() |
Уменьшить зна- |
|
|
чение счетчика |
|
|
на семафоре |
|
|
|
156 |
|
|
|
|
Окончание табл. 6.1 |
|
|
|
|
Описание |
|
|
POSIX-вызов |
|
Вызов микроядра |
|
|
|||
pthread_barrierattr_getpshared() |
|
Получить значение атрибута |
|
||||
|
|
|
|
совместного использования для |
|
||
|
|
|
|
заданного барьера |
|
|
|
pthread_barrierattr_ destroy() |
|
|
Уничтожить атрибутную запись |
|
|||
|
|
|
|
барьера |
|
|
|
pthread_barrierattr_iпit() |
|
|
Инициализировать атрибуты |
|
|||
|
|
|
|
объекта |
|
|
|
pthread_barrierattr_ setpshared() |
|
Установить значение атрибута |
|
||||
|
|
|
|
совместного использования для |
|
||
|
|
|
|
заданного барьера |
|
|
|
pthread_barrier_destroy() |
|
|
Уничтожить барьер |
|
|||
pthread_ barrier_iпit() |
|
|
Инициализировать барьер |
|
|||
pthread_barrier_wait() |
|
|
Синхронизировать потоки на |
|
|||
|
|
|
|
барьере |
|
|
|
pthread_sleepon_lock() |
|
|
Блокировать поток с использо- |
|
|||
|
|
|
|
ванием ждущей блокировки |
|
||
pthread_rwlock_rdlock() |
|
|
Блокировка потока по чте- |
|
|||
|
|
|
|
нию/записи |
|
|
|
pthread_rwlock_wrlock() |
|
|
Блокировка потока по записи |
|
|||
pthread_rwlock_unlock() |
|
|
Снятие блокировки по чтению |
|
|||
pthread_rwlock_tryrdlock() |
|
|
Тестирование блокировки по |
|
|||
|
|
|
|
чтению/записи |
|
|
|
pthread_rwlock_trywrlock() |
|
|
Тестирование блокировки по |
|
|||
|
|
|
|
записи |
|
|
Несмотря на то, что потоки внутри процесса используют одно адресное пространство, каждый из этих потоков имеет «собственные» данные. В некоторых случаях эти данные защищаются внутри ядра (например, идентификатор потока), в то время как другие данные остаются незащищенными в адресном пространстве процесса (например, у каждого потока свой стек).
Данные, относящиеся к потоку, реализуются в библиотеки pthread и хранятся в локальной памяти потока. Они обеспечивают механизм, предназначенный для связывания глобального целочисленного ключа процесса (process global integer key) с
уникальным значением данных для каждого потока.
157
6.2.2 Состояния потока
Потоки создаются и уничтожаются динамически, и их количество внутри процесса может изменяться в значительных пределах. Создание потока включает в себя выделение и инициализацию необходимых ресурсов внутри адресного пространства процесса, а также запуск выполнения потока с некоторой функции.
Завершение потока включает в себя остановку потока и освобождение ресурсов потока.
Поток ОС QNX может находиться в одном из следующих состояний [9]:
−CONDVAR — поток блокирован на условной переменной (например, при вызове функции pthread_cond_var_wait());
−DEAD — поток завершен и ожидает завершения другого
потока;
−INTERRUPT — поток блокирован и ожидает прерывания (при вызове функции InterruptWait());
−JOIN — поток блокирован и ожидает завершения другого потока (например, при вызове функции pthread_join());
−MUTEX — поток блокирован блокировкой взаимного исключения (например, при вызове функции pthread_mutex_
lock());
−NANOSLEEP — поток находится в режиме ожидания в течение короткого периода времени (например, при вызове функции nanosleep());
−NET_REPLY — поток ожидает ответа на сообщение другого узла сети (поток вызвал функцию MsgReply);
−NET_SEND — поток ожидает получение импульса или сигнала от другого узла сети (поток вызвал функцию MsgSend-
Pulse(), MsgDeliverEvent или SignalKill());
−READY — поток ожидает выполнения, пока процессор занят выполнением другого потока равного или более высокого приоритета;
−RECEIVE — поток блокирован на операции получения сообщения (поток вызвал функцию MsgReceive);
158
−REPLY — поток блокирован при ответе на сообщение (поток вызвал функцию MsgReply);
−RUNNING — поток выполняется процессором;
−SEM — поток ожидает освобождения семафора (поток вызвал функцию SyncSemWait());
−SEND — поток блокируется при отправке сообщения (поток вызвал функцию MsgSend);
−SIGSUSPEND — поток блокирован и ожидает сигнала (поток вызвал функцию sigsuspend());
−SIGWAITINFO — поток блокирован и ожидает сигнала (поток вызвал функцию sigwaitinfo());
−STACK — поток ожидает выделения виртуального адресного пространства для своего стека (родительский поток вы-
зывает функцию ThreadCreate());
−STOPPED — поток блокирован и ожидает сигнала
SIGCOUNT;
−WAITCTX — поток ожидает доступности нецелочисленного контекста;
−WAITPAGE — поток ожидает выделения физической памяти для виртуального адреса;
−WAITTHREAD — поток ожидает завершения создания дочернего потока (поток вызвал функцию ThreadCreate()).
6.2.3Планирование потоков
При каждом вызове ядра, исключении или аппаратном прерывании, в результате которого управление передается коду ядра, выполнение активного потока временно приостанавливается. Действие планирования совершается в момент изменения состояния любого потока, независимо от того, в каком процессе поток расположен. Планирование потоков осуществляется по всем процессам сразу [9].
Как правило, выполнение приостановленного потока через некоторое время возобновляется. При этом планировщик (sсllеdulег) выполняет переключение контекстов с одного потока на другой всякий раз, когда активный поток [9]:
159
–блокируется. Активный поток блокируется, если он должен ожидать какого-либо события (например, ответа на запрос механизма обмена сообщениями, освобождения мьютекса
ит.д.). Блокированный поток удаляется из очереди готовности, после чего запускается поток с наивысшим приоритетом. Когда блокированный поток разблокируется, он помещается в конец очереди готовности на соответствующий приоритетный уровень;
–вытесняется. Активный поток вытесняется, когда поток с более высоким приоритетом помещается в очередь готовности (т.е. он переходит в состояние готовности (READY) в результате снятия условия блокировки). Прерванный поток остается на соответствующем приоритетном уровне в начале очереди готовности, а поток с более высоким приоритетом начинает выполняться;
–отдает управление. Активный поток самостоятельно освобождает процессор и помещается в конец очереди готовности на данном уровне приоритета. После этого запускается поток с наивысшим приоритетом (в том числе им может быть поток, который только что отдал управление).
Каждому потоку назначается свой приоритет. Планировщик выбирает поток для выполнения в соответствии с приоритетом каждого потока, находящегося в состоянии готовности (READY), т.е. способного использовать процессор. Таким образом, выбирается поток с наивысшим приоритетом [9].
Всего в ОС QNX Neutrino 6.3 поддерживается до 256 уровней приоритетов. Приоритет выполнения каждого непривилегированного потока может изменяться в пределах от 1 до 63 (наивысший приоритет), независимо от его дисциплины планирования. Только привилегированные (гoot) потоки могут иметь приоритет выше 63. Специальный поток с именем idle в администраторе процессов имеет приоритет 0 и всегда готов к выполнению. По умолчанию поток наследует приоритет своего родительского потока [9].
Команда procnto -р позволяет изменить диапазон допустимых приоритетов для непривилегированного процесса:
procnto -р приоритет
160
Следует обратить внимание на то, что для предотвращения инверсии приоритетов ядро может временно повышать приоритет потока.
Потоки, находящиеся в очереди, упорядочиваются по приоритету. В действительности, очередь готовых потоков состоит из 256 отдельных очередей по одной на каждый приоритет. Потоки выстраиваются в очереди по порядку поступления (FIFO) и в соответствии с их приоритетом. Для выполнения выбирается первый поток с наивысшим приоритетом [9].
Для работы в ОС QNX Neutrino 6.3 используются следующие алгоритмы планирования:
–FIFO-планирование (см. п. 3.1.2);
–циклическое планирование (Round robin, см. п. 3.1.2);
–спорадическое планирование.
Каждый поток в системе может выполняться по любому из методов. Методы применяются для каждого отдельного потока, а не для потоков или процессов одновременно.
FIFO-планирование и циклическое планирование применяются только в случаях, когда два или более потоков, имеющих одинаковый приоритет, находятся в состоянии готовности.
Спорадическое планирование. Алгоритм спорадического планирования обычно используется для задания верхнего лимита на время выполнения потока в пределах заданного периода времени. Этот метод необходим при выполнении монотонного частотного анализа системы, обслуживающей как периодические, так и апериодические события. По сути, данный алгоритм позволяет потоку обслуживать апериодические события, не препятствуя своевременному выполнению других потоков или процессов в системе [9].
Как и в FIFО-планировании, поток, для которого применяется спорадическое планирование, выполняется до тех пор, пока он не блокируется или прерывается потоком с более высоким приоритетом. Кроме того, так же как и в адаптивном планировании, поток, для которого применяется спорадическое планирование, получает пониженный приоритет. Однако спорадическое планирование дает значительно более точное управление потоком [9].