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

ЖАНРЫ

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

Кёртен Роб

Шрифт:

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

Семафор в роли мутекса

Мы только что задали себе вопрос: «Смогли бы мы реализовать блокировку со счетом с помощью мутекса?» Ответ был отрицательный. А если наоборот? Смогли бы мы использовать семафор в качестве мутекса?

Да, смогли бы. В действительности в некоторых операционных

системах так все и делается — никаких мутексов, одни семафоры! Зачем тогда вообще беспокоиться о мутексах?

Для того чтобы ответить на этот вопрос, рассмотрим ситуацию в нашей аналогии с ванной комнатой. Как строитель вашего дома реализовал мутекс? Я подозреваю, что в вашем доме нет ключей, которые вешались бы на двери снаружи.

Мутексы — это семафоры «специального назначения». Если вы пожелаете, чтобы в определенном месте программы выполнялся только один поток, эффективнее всего было бы реализовать это при помощи мутекса.

Позже мы рассмотрим и другие способы синхронизации потоков — объекты, которые называются условными переменными (condvar), барьерами (barrier) и ждущими блокировками (sleepon).

Чтобы не возникло путаницы, необходимо также иметь в виду, что мутекс имеет и другие свойства (например наследование приоритетов), отличающие его от семафора.

Роль ядра

Наша аналогия с процессами в жилом доме прекрасна для объяснения концепций синхронизации, но бесполезна при анализе одной очень важной проблемы. В доме у нас было много потоков, работающих одновременно. Однако в реальной жизненной ситуации обычно имеется только один процессор, так что только один объект может реально работать в одно и то же время.

Одиночный процессор

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

Несколько процессоров — симметричная мультипроцессорная система (SMP)

Если вы покупаете систему, в которой имеется множество идентичных процессоров, совместно использующих одну и ту же память и устройства, это означает, что у вас есть блок SMP. (SMP расшифровывается как «Symmetrical Multi-Processor» — «симметричный мультипроцессор»; с помощью слова «симметричный» подчеркивается, что все центральные процессоры, применяемые в системе, являются идентичными.) В таком случае число потоков, которые могут работать одновременно, ограничено количеством процессоров. (Кстати, в случае с одним процессором была та же самая ситуация!) Поскольку каждый процессор может одновременно обрабатывать только один поток, в ситуации с применением множества процессоров несколько потоков могут работать одновременно. Давайте пока абстрагируемся от числа процессоров в системе — при проектировании системы бывает полезно считать, что несколько потоков могут выполняться одновременно, даже если это и не происходит в реальной ситуации. Несколько позже в разделе «На что следует обратить внимание при использовании SMP» мы рассмотрим кое-какие неочевидные особенности симметричного мультипроцессирования.

Ядро в роли арбитра

Так кто же определяет, который из потоков должен выполняться в данный момент времени? Этим занимается ядро.

Ядро определяет, который из потоков должен использовать процессор в данный момент времени и переключает контекст на этот поток. Давайте посмотрим, что ядро при этом делает с процессором.

Процессор имеет несколько регистров (точное их число зависит от принадлежности процессора к серии, например, сравните процессор x86 с процессором MIPS, а характерный представитель серии, например, процессор 80486 — с процессором Pentium). В тот момент, когда поток выполняется, информация о нем хранится в указанных регистрах (например, данные о размещении программы в памяти).

Когда же ядро принимает решение

о том, что должен выполняться другой поток, оно должно сделать следующее:

1. Сохранить текущее состояние регистров активного потока и другую контекстную информацию.

2. Записать в регистры информацию для нового потока, а также загрузить новый контекст.

Как ядро принимает решение о том, что должен выполняться другой поток? Оно анализирует, действительно ли в данный момент времени данный поток готов к использованию процессора. Когда мы обсуждали, например, мутексы, мы говорили о состояниях блокировки (это происходило в тех случаях, когда поток пытался завладеть мутексом, уже принадлежащим другому потоку, и поэтому блокировался). Таким образом, с точки зрения ядра мы имеем один поток, который может использовать процессор, и другой поток, которые не может этого делать, потому что он заблокирован в ожидании мутекса. В этом случае ядро предоставляет процессор потоку, который готов к работе, а другой поток заносит в свой внутренний список (чтобы можно было отслеживать отслеживал запрос потока на мутекс).

Очевидно, это не очень-то интересная ситуация. Предположим, что готовы к выполнению сразу несколько потоков. Вспомним, не мы ли делегировали доступ к мутексу на основе приоритета и продолжительности ожидания? Ядро тоже использует подобную схему для определения того, который из потоков должен работать следующим. При этом играют роль два фактора: приоритет и дисциплина диспетчеризации. Рассмотрим их по очереди.

Концепция приоритетов

Рассмотрим два готовых к выполнению потока. Если эти поток имеют различные приоритеты, то весьма прост — ядро отдает процессор потоку с высшим приоритетом. Приоритеты в QNX/ Neutrino пронумерованы от единицы (самый низкий) и далее, в единичным дискретом — так же, как это было упомянуто в обсуждении получения мутекса. Заметьте, что нулевой приоритет использовать нельзя — он зарезервирован для «холостого» (idle) потока (на профессиональном жаргоне часто называемого «холодильником» — прим. ред.). (Если вы захотите узнать минимальное или максимальное значение приоритета, определенное для вашей системы, используйте функции sched_get_priority_min и sched_get_priority_max — они описаны в

<sched.h>
. В данной книге мы будем предполагать, что приоритет 1 является самым низким, а 63 самым высоким.

Если другой поток с более высоким приоритетом вдруг становится готов к выполнению, ядро немедленно переключит контекст на поток с более высоким приоритетом. Это называется вытеснением— поток с высшим приоритетом вытесняет поток с низшим приоритетом. Когда поток с высшим приоритетом заканчивает свою работу, и ядро переключает контекст обратно на поток с низшим приоритетом, который выполнялся ранее, мы называем это возобновлением— ядро возобновляет работу предыдущего потока.

Теперь предположим, что не один, а два потока готовы к выполнению и имеют один и тот же приоритет.

Дисциплины диспетчеризации

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

Ядро QNX/Neutrino поддерживает две дисциплины диспетчеризации: карусельную, она же RR (Round Robin), и FIFO (First In — First Out).

Диспетчеризация FIFO

При диспетчеризации FIFO процессор предоставляется потоку на столько времени, сколько ему необходимо. Это означает, что если один поток занят длительными вычислениями, и никакой другой поток с более высоким приоритетом не готов к выполнению, то этот поток потенциально может выполняться вечно. А как же потоки с тем же приоритетом? Они будут заблокированы тоже. (То, что в этот же момент потоки с более низким приоритетом будут заблокированы, должно быть очевидно.)

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