ГЛАВА 4  

Процессы   и потоки

 

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

 

Мультипрограммирование

 

Мультипрограммирование, или многозадачность (multitasking), — это способ ор­ганизации вычислительного процесса, при котором на одном процессоре попере­менно выполняются сразу несколько программ. Эти программы совместно ис­пользуют не только процессор, но и другие ресурсы компьютера: оперативную и внешнюю память, устройства ввода-вывода, данные. Мультипрограммиро­вание призвано повысить эффективность использования вычислительной систе­мы, однако эффективность может пониматься по-разному. Наиболее характер­ными критериями эффективности вычислительных систем являются:

пропускная способность — количество задач, выполняемых вычислительной системой в единицу времени;

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

приложениями на одной машине;

реактивность системы — способность системы выдерживать заранее задан­ные (возможно, очень короткие) интервалы времени между запуском про­граммы и получением результата.

В зависимости от выбранного, критерия эффективности ОС делятся на системы пакетной обработки, системы разделения времени и системы реального времени. Каждый тип ОС имеет специфические внутренние механизмы и особые области применения. Некоторые операционные системы могут поддерживать одновремен­но несколько режимов, например часть задач может выполняться в режиме па­кетной обработки, а часть — в режиме реального времени или в режиме разделе­ния времени.

 

Мультипрограммирование в системах пакетной обработки

 

При использовании мультипрограммирования для повышения пропускной спо­собности компьютера главной целью является минимизация простоев всех уст­ройств компьютера, и прежде всего центрального процессора. Такие простои могут возникать из-за приостановки задачи по ее внутренним причинам, связан­ным, например, с ожиданием ввода данных для обработки. Данные могут хра­ниться на диске или же поступать от пользователя, работающего за терминалом, а также от измерительной аппаратуры, установленной на внешних технических объектах. При возникновении такого рода блокировки выполняемой задачи ес­тественным решением, ведущим к повышению эффективности использования процессора, является переключение процессора на выполнение другой задачи, у которой есть данные для обработки. Такая концепция мультипрограммирова­ния положена в основу так называемых пакетных систем.

Системы пакетной обработки предназначались для решения задач в основном вычислительного характера, не требующих быстрого получения результатов. Главной целью и критерием эффективности систем пакетной обработки являет­ся максимальная пропускная способность, то есть решение максимального числа задач в единицу времени.

Для достижения этой цели в системах пакетной обработки используется сле­дующая схема функционирования: в начале работы формируется пакет заданий, каждое задание содержит требование к системным ресурсам; из этого пакета за­даний формируется мультипрограммная смесь, то есть множество одновремен­но выполняемых задач. Для одновременного выполнения выбираются задачи, предъявляющие разные требования к ресурсам, так, чтобы обеспечивалась сба­лансированная загрузка всех устройств вычислительной машины. Например, в мультипрограммной смеси желательно одновременное присутствие вычислитель­ных задач и задач с интенсивным вводом-выводом. Таким образом, выбор нового задания из пакета заданий зависит от внутренней ситуации, складывающейся в системе, то есть выбирается «выгодное» задание. Следовательно, в вычисли­тельных системах, работающих под управлением пакетных ОС, невозможно га­рантировать выполнение того или иного задания в течение определенного пе­риода времени.

Рассмотрим более детально совмещение во времени операций ввода-вывода и

вычислений.

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

 

 

 

 

Другой способ совмещения вычислений с операциями ввода-вывода реализуется в компьютерах, в которых внешние устройства управляются не процессором ввода-вывода, а контроллерами. Каждое внешнее устройство (или группа внеш­них устройств одного типа) имеет свой собственный контроллер, который авто­номно отрабатывает команды, поступающие от центрального процессора. При этом контроллер и центральный процессор работают асинхронно. Поскольку многие внешние устройства включают электромеханические узлы, контроллер выпол­няет свои команды управления устройствами существенно медленнее, чем цен­тральный процессор — свои. Это обстоятельство используется для организации параллельного выполнения вычислений и операций ввода-вывода: в промежутке между передачей команд контроллеру центральный процессор может выполнять вычисления (рис. 4.1, б). Контроллер может сообщить центральному процессору о том, что он готов принять следующую команду, сигналом прерывания либо центральный процессор узнает об этом, периодически опрашивая состояние кон­троллера.

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

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

Общее время выполнения смеси задач часто оказывается меньше, чем их сум­марное время последовательного выполнения (рис. 4.2, а). Однако выполнение отдельной задачи в мультипрограммном режиме может занять больше времени, чем при монопольном выделении процессора этой задаче. Действительно, при совместном использовании процессора в системе могут возникать ситуации, ко­гда задача готова выполняться, но процессор занят выполнением другой задачи. В таких случаях задача, завершившая ввод-вывод, готова выполняться, но выну­ждена ждать освобождения процессора, и это удлиняет срок ее выполнения. Так, из рис. 4.2 видно, что в однопрограммном режиме задача А выполняется за 6 единиц времени, а в мультипрограммном — за 7. Задача В также вместо 5 единиц времени выполняется за 6. Но зато время выполнения обеих задач в мультипро­граммном режиме составляет всего 8 единиц, что на 3 единицы меньше, чем при последовательном выполнении.

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

 

 

 

 

Мультипрограммирование

в системах разделения времени

 

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

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

Системы разделения времени призваны исправить основной недостаток систем пакетной обработки — изоляцию пользователя-программиста от процесса выпол­нения его задач. Каждому пользователю в этом случае предоставляется терми­нал, с которого он может вести диалог со своей программой. Так как в системах разделения времени каждой задаче выделяется только квант процессорного вре­мени, ни одна задача не занимает процессор надолго и время ответа оказывается приемлемым. Если квант выбран достаточно небольшим, то у всех пользователей, одновременно работающих на одной и той же машине, складывается впечат­ление, что каждый из них единолично использует машину.

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

 

Мультипрограммирование

в системах реального времени

 

Еще одна разновидность мультипрограммирования используется в системах ре­ального времени, предназначенных для управления от компьютера различными техническими объектами (например, станком, спутником, научной эксперименталь­ной установкой и т. д.) или технологическими процессами (например, гальваниче­ской линией, доменным процессом и т. п.). Во всех этих случаях существует пре­дельно допустимое время, в течение которого должна быть выполнена та или иная управляющая объектом программа. В противном случае может произойти авария: спутник выйдет из зоны видимости, экспериментальные данные, поступающие с дат­чиков, будут потеряны, толщина гальванического покрытия не будет соответство­вать норме. Таким образом, критерием эффективности здесь является способность выдерживать заранее заданные интервалы времени между запуском программы и получением результата (управляющего воздействия). Это время называется време­нем реакции системы, а соответствующее свойство системы — реактивностью. Требо­вания ко времени реакции зависят от специфики управляемого процесса. Кон­троллер робота может требовать от встроенного компьютера ответ в течение менее 1 мс, в то время как при моделировании полета может быть приемлем от­вет в 40 мс.

В системах реального времени мультипрограммная смесь представляет собой фик­сированный набор заранее разработанных программ, а выбор программы на выпол­нение осуществляется по прерываниям (исходя из текущего состояния объекта) или в соответствии с расписанием плановых работ.

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

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

 

Мультипроцессорная обработка

 

Мультипроцессорная обработка — это способ организации вычислительного про­цесса в системах с несколькими процессорами, при котором несколько задач (процессов, потоков) могут одновременно выполняться на разных процессорах

системы.

Концепция мультипроцессирования ненова, она известна с 70-х годов, но до се­редины 80-х доступных многопроцессорных систем не существовало. Однако к настоящему времени стало обычным включение нескольких процессоров в ар­хитектуру даже персонального компьютера. Более того, многопроцессорность теперь является одним из необходимых требований, которые предъявляются к компьютерам, используемым в качестве центрального сервера более-менее круп­ной сети.

Не следует путать мультипроцессорную обработку с мультипрограммной обра­боткой. В мультипрограммных системах параллельная работа разных устройств позволяет одновременно вести обработку нескольких программ, но при этом в процессоре в каждый момент времени выполняется только одна программа. То есть в этом случае несколько задач выполняются попеременно на одном процес­соре, создавая лишь видимость параллельного выполнения. А в мультипроцес­сорных системах несколько задач выполняются действительно одновременно, так как имеется несколько обрабатывающих устройств — процессоров. Конечно, мультипроцессирование вовсе не исключает мультипрограммирования: на каждом из процессоров может попеременно выполняться некоторый закрепленный за дан­ным процессором набор задач.

Мультипроцессорная организация системы приводит к усложнению всех алго­ритмов управления ресурсами, например требуется планировать процессы не для одного, а для нескольких процессоров, что гораздо сложнее. Сложности за­ключаются и в возрастании числа конфликтов по обращению к устройствам ввода-вывода, данным, общей памяти и совместно используемым программам. Необходимо предусмотреть эффективные средства блокировки при доступе к разделяемым информационным структурам ядра. Все эти проблемы должна решать операционная система путем синхронизации процессов, ведения очередей и планирования ресурсов. Более того, сама операционная система должна быть спроектирована так, чтобы уменьшить существующие взаимозависимости между собственными компонентами.

В наши дни становится общепринятым введение в ОС функций поддержки мультипроцессорной обработки данных. Такие функции имеются во всех попу­лярных ОС, таких как Sun Solaris 2.x, Santa Crus Operations Open Server 3.x, IBM OS/2, Microsoft Windows NT и Novell NetWare, начиная с 4.1.

Мультипроцессорные системы часто характеризуют либо как симметричные, либо как несимметричные. При этом следует четко определять, к какому аспекту муль­типроцессорной системы относится эта характеристика — к типу архитектуры или к способу организации вычислительного процесса.

Симметричная архитектура мультипроцессорной системы предполагает одно­родность всех процессоров и единообразие включения процессоров в общую схему мультипроцессорной системы. Традиционные симметричные мультипро­цессорные конфигурации разделяют одну большую память между всеми процес­сорами.

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

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

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

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

Масштабирование в асимметричной архитектуре  реализуется иначе, чем в сим­метричной. Так как требование единого корпуса отсутствует, система может со­стоять из нескольких устройств, каждое из которых содержит один или несколь­ко процессоров. Это масштабирование по горизонтали. Каждое такое устройство называется кластером, а вся мультипроцессорная система — кластерной.

Другим аспектом мультипроцессорных систем, который может характеризовать­ся симметрией или ее отсутствием, является способ организации вычислительного процесса. Последний, как известно, определяется и реализуется операцион­ной системой.

Асимметричное мулътипроцессирование является наиболее простым способом организации вычислительного процесса в системах с несколькими процессора­ми. Этот способ часто называют также «ведущий-ведомый».

Функционирование системы по принципу «ведущий-ведомый» предполагает вы­деление одного из процессоров в качестве «ведущего», на котором работает опе­рационная* система и который управляет всеми остальными «ведомыми» процес­сорами. То есть ведущий процессор берет на себя функции распределения задач и ресурсов, а ведомые процессоры работают только как обрабатывающие устрой­ства и никаких действий по организации работы вычислительной системы не выполняют.

Так как операционная система работает только на одном процессоре и функции управления полностью централизованы, то такая операционная система оказы­вается не намного сложнее ОС однопроцессорной системы.

Асимметричная организация вычислительного процесса может быть реализова­на как для симметричной мультипроцессорной архитектуры, в которой все про­цессоры аппаратно неразличимы, так и для несимметричной, для которой харак­терна неоднородность процессоров, их специализация на аппаратном уровне.

В архитектурно-асимметричных системах на роль ведущего процессора может быть назначен наиболее надежный и производительный процессор. Если в набо­ре процессоров имеется специализированный процессор, ориентированный, на­пример, на матричные вычисления, то при планировании процессов операци­онная система, реализующая асимметричное мультипроцессирование, должна учитывать специфику этого процессора. Такая специализация снижает надеж­ность системы в целом, так как процессоры не являются взаимозаменяемыми.

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

Симметричное мультипроцессирование реализуется общей для всех процес­соров операционной системой. При симметричной организации все процессоры равноправно участвуют и в управлении вычислительным процессом, и в выпол­нении прикладных задач. Например, сигнал прерывания от принтера, который распечатывает данные прикладного процесса, выполняемого на некотором про­цессоре, может быть обработан совсем другим процессором. Разные процессоры могут в какой-то момент одновременно обслуживать как разные, так и одинако­вые модули общей операционной системы. Для этого программы операционной системы должны обладать свойством повторной входимости (реентерабельнос­тью).

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

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

Симметричная и асимметричная организация вычислительного процесса в муль­типроцессорной системе не связана напрямую с симметричной или асимметрич­ной архитектурой, она определяется типом операционной системы. Так, в сим­метричных архитектурах вычислительный процесс может быть организован как симметричным образом, так и асимметричным. Однако асимметричная архитек­тура непременно влечет за собой и асимметричный способ организации вычис­лений.

 

Планирование процессов и потоков

 

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

Подсистема управления процессами и потоками ответственна за обеспечение процессов необходимыми ресурсами. ОС поддерживает в памяти специальные ин­формационные структуры, в которые записывает, какие ресурсы выделены каж­дому процессу. Она может назначить процессу ресурсы в единоличное пользо­вание или в совместное пользование с другими процессами. Некоторые из ресур­сов выделяются процессу при его создании, а некоторые — динамически по за­просам во время выполнения. Ресурсы могут быть приписаны процессу на все время его жизни или только на определенный период. При выполнении этих функций подсистема управления процессами взаимодействует с другими подсис­темами ОС, ответственными за управление ресурсами, такими как подсистема управления памятью, подсистема ввода-вывода, файловая система.

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

Каждый раз, когда процесс завершается, ОС предпринимает шаги, чтобы «зачистить следы» его пребывания в системе. Подсистема управления процессами за­крывает все файлы, с которыми работал процесс, освобождает области опера­тивной памяти, отведенные под коды, данные и системные информационные структуры процесса. Выполняется коррекция всевозможных очередей ОС и спи­сков ресурсов, в которых имелись ссылки на завершаемый процесс.

 

Понятия «процесс» и «поток»

 

Чтобы поддерживать мультипрограммирование, ОС должна определить и офор­мить для себя те внутренние единицы работы, между которыми будет разделять­ся процессор и другие ресурсы компьютера. В настоящее время в большинстве операционных систем определены два типа единиц работы. Более крупная еди­ница работы, обычно носящая название процесса, или задачи, требует для своего выполнения нескольких более мелких работ, для обозначения которых исполь­зуют термины «поток», или <<пить».

 

 

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------

При использовании этих терминов часто возникают сложности. Это происходит в силу не­скольких причин. Во-первых, — специфика различных ОС, когда совпадающие по сути по­нятия получили разные названия, например задача (task) в OS/2, OS/360 и процесс (process) в UNIX, Windows NT, NetWare. Во-вторых, по мере развития системного про­граммирования и методов организации вычислений некоторые из этих терминов получи­ли новое смысловое значение, особенно это касается понятия «процесс», который уступил многие свои свойства новому понятию «поток». В-третьих, терминологические сложности порождаются наличием нескольких вариантов перевода англоязычных терминов на рус­ский язык. Например, термин «thread» переводится как «нить», «поток», «облегченный процесс», «минизадача» и др. Далее в качестве названия единиц работы ОС будут исполь­зоваться термины «процесс» и «поток». В тех же случаях, когда различия между этими понятиями не будут играть существенной роли, они объединяются под обобщенным тер­мином «задача».

 

Итак, в чем же состоят принципиальные отличия в понятиях «процесс» и «по­ток»?

Очевидно, что любая работа вычислительной системы заключается в выполне­нии некоторой программы. Поэтому и с процессом, и с потоком связывается оп­ределенный программный код, который для этих целей оформляется в виде ис­полняемого модуля. Чтобы этот программный код мог быть выполнен, его необ­ходимо загрузить в оперативную память, возможно, выделить некоторое место на диске для хранения данных, предоставить доступ к устройствам ввода-выво­да, например к последовательному порту для получения данных по подключенному к этому порту модему, и т. д. В ходе выполнения программе может также понадобиться доступ к информационным ресурсам, например файлам, портам TCP/UPD, семафорам. И, конечно же, невозможно выполнение программы без предоставления ей процессорного времени, то есть времени, в течение которого процессор выполняет коды данной программы.

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

В простейшем случае процесс состоит из одного потока, и именно таким обра­зом трактовалось понятие «процессу до середины 80-х годов (например, в ран­них версиях UNIX) и в таком же виде оно сохранилось в некоторых современ­ных ОС. В таких системах понятие «поток» полностью поглощается понятием «процесс», то есть остается только одна единица работы и потребления ресур­сов — процесс. Мультипрограммирование осуществляется в таких ОС на уровне процессов.

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

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------

Виртуальное адресное пространство процесса — это совокупность адресов, которыми может манипулировать программный модуль процесса. Операционная система отображает вирту­альное адресное пространство процесса на отведенную процессу физическую память.

 

При необходимости взаимодействия процессы обращаются к операционной сис­теме, которая, выполняя функции посредника, предоставляет им средства меж­процессной связи — конвейеры, почтовые ящики, разделяемые секции памяти и некоторые другие.

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

Потоки возникли в операционных системах как средство распараллеливания вы­числений. Конечно, задача распараллеливания вычислений в рамках одного при­ложения может быть решена и традиционными способами.

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

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

Из всего вышеизложенного следует, что в операционной системе наряду с про­цессами нужен другой механизм распараллеливания вычислений, который учи­тывал бы тесные связи между отдельными ветвями вычислений одного и того же приложения. Для этих целей современные ОС предлагают механизм много-поточной обработки (multithreading). При этом вводится новая единица работы — поток выполнения, а понятие «процесс» в значительной степени меняет смысл. Понятию «поток» соответствует последовательный переход процессора от одной команды программы к другой. ОС распределяет процессорное время между пото­ками. Процессу ОС назначает адресное пространство и набор ресурсов, которые совместно используются всеми его потоками.

ПРИМЕЧАНИЕ -----------------------------------------------------------------------:---------------------

Заметим, что в однопрограммных системах не возникает необходимости введения понятия, обозначающего единицу работы, так как там не существует проблемы разделения ресурсов.

 

Создание потоков требует от ОС меньших накладных расходов, чем процессов. В отличие от процессов, которые принадлежат разным, вообще говоря, конкури­рующим приложениям, все потоки одного процесса всегда принадлежат одному приложению, поэтому ОС изолирует потоки в гораздо меньшей степени, нежели процессы в традиционной мультипрограммной системе. Все потоки одного процесса используют общие файлы, таймеры, устройства, одну и ту же область опе­ративной памяти, одно и то же адресное пространство. Это означает, что они раз­деляют одни и те же глобальные переменные. Поскольку каждый поток может иметь доступ к любому виртуальному адресу процесса, один поток может ис­пользовать стек другого потока. Между потоками одного процесса нет полной защиты, потому что, во-первых, это невозможно, а во-вторых, не нужно. Чтобы организовать взаимодействие и обмен данными, потокам вовсе не требуется об­ращаться к ОС, им достаточно использовать общую память — один поток запи­сывает данные, а другой читает их, С другой стороны, потоки разных процессов по-прежнему хорошо защищены друг от друга.

Итак, мультипрограммирование более эффективно на уровне потоков, а не процес­сов. Каждый поток имеет собственный счетчик команд и стек. Задача, оформлен­ная в виде нескольких потоков в рамках одного процесса, может быть выполнена быстрее за счет псевдопараллельного (или параллельного в мультипроцессорной системе) выполнения ее отдельных частей. Например, если электронная таблица была разработана с учетом возможностей многопоточной обработки, то пользо­ватель может запросить пересчет своего рабочего листа и одновременно продол­жать заполнять таблицу. Особенно эффективно можно использовать многопоточность для выполнения распределенных приложений, например многопоточный сервер может параллельно выполнять запросы сразу нескольких клиентов.

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

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

 

 

 

Создание процессов и потоков

 

Создать процесс — это прежде всего означает создать описатель процесса, в каче­стве которого выступает одна или несколько информационных структур, содержащих все сведения о процессе, необходимые операционной системе для управ­ления им. В число таких сведений могут входить, например, идентификатор про­цесса, данные о расположении в памяти исполняемого модуля, степень привиле­гированности процесса (приоритет и права доступа) и т. п. Примерами описателей процесса являются блок управления задачей (ТСВ — Task Control Block) в OS/360, управляющий блок процесса. (РСВ — Process Control Block) в OS/2, де­скриптор процесса в UNIX, объект-процесс (object-process) в Windows NT.

Создание описателя процесса знаменует собой появление в системе еще одного претендента на вычислительные ресурсы. Начиная с этого момента при распре­делении ресурсов ОС должна принимать во внимание потребности нового про­цесса.

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

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

Во многих системах поток может обратиться к ОС с запросом на создание так называемых потоков-потомков. В разных ОС по-разному строятся отношения между потоками-потомками и их родителями. Например, в одних ОС выполне­ние родительского потока синхронизируется с его потомками, в частности после завершения родительского потока ОС может снимать с выполнения всех его по­томков. В других системах потоки-потомки могут выполняться асинхронно по отношению к родительскому потоку. Потомки, как правило, наследуют многие

свойства родительских потоков. Во многих системах порождение потомков является основным механизмом создания процессов и потоков.

Рассмотрим в качестве примера создание процессов в популярной версии опера­ционной системы UNIX System V Release 4.  этой системе потоки не поддержи­ваются, в качестве единицы управления и единицы потребления ресурсов высту­пает процесс.

При управлении процессами операционная система использует два основных типа информационных структур: дескриптор процесса и контекст процесса. Дескрип­тор процесса содержит такую информацию о процессе, которая необходима ядру в течение всего жизненного цикла процесса независимо от того, находится он в  активном или пассивном состоянии, находится образ процесса в оперативной памяти или выгружен на диск. (Образом процесса называется совокупность его кодов и данных.)

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

Контекст процесса содержит менее оперативную, но более объемную часть ин­формации о процессе, необходимую для возобновления выполнения процесса с прерванного места: содержимое регистров процессора, коды ошибок выполняе­мых процессором системных вызовов, информация обо всех открытых данным процессом файлах и незавершенных операциях ввода-вывода и другие данные, характеризующие состояние вычислительной среды в момент прерывания. Кон­текст, так же как и дескриптор процесса, доступен только программам ядра, то есть находится в виртуальном адресном пространстве операционной системы, однако он хранится не в области ядра, а непосредственно примыкает к образу процесса и перемещается вместе с ним, если это необходимо, из оперативной па­мяти на диск.

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

После выполнения системного вызова fork оба процесса продолжают выполне­ние с одной и той же точки. Чтобы процесс мог опознать, является он родитель­ским процессом или процессом-потомком, системный вызов fork возвращает в качестве своего значения в породивший процесс идентификатор порожденного процесса, а в порожденный процесс — NULL. Типичное разветвление на языке С записывается так:

 

if( fork( )) { действия родительского процесса

else { действия порожденного процесса }

 

 Идентификатор потомка может быть присвоен переменной, входящей в контекст, родительского процесса. Так как контекст процесса наследуется его потомками, то потомки могут узнать идентификаторы своих «старших братьев», таким образом сумма знаний наследуется при порождении и может быть распространена  между родственными процессами. На независимости идентификатора процесса  от выполняемой процессом программы построен механизм, позволяющий процессу перейти к выполнению другой программы с помощью системного вызова   exec.

Таким образом, в UNIX порождение нового процесса происходит в два этапа — сначала создается копия процесса-родителя, затем у нового процесса производится замена кодового сегмента на заданный.

Вновь созданному процессу операционная система присваивает целочисленный идентификатор, уникальный на весь период функционирования системы.

 

Планирование и диспетчеризация потоков

 

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

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

Планирование потоков, по существу, включает в себя решение двух задач;

□ определение момента времени для смены текущего активного потока;

□ выбор для выполнения потока из очереди готовых потоков.

Существует множество различных алгоритмов планирования потоков, по-своему решающих каждую из приведенных выше задач. Алгоритмы планирования мо­гут преследовать различные цели и обеспечивать разное качество мультипро­граммирования. Например, в одном случае выбирается такой алгоритм планирования, при котором гарантируется, что ни один поток/процесс не будет занимать процессор дольше определенного времени, в другом случае целью является мак­симально быстрое выполнение «коротких» задач, а в третьем случае — преиму­щественное право занять процессор получают потоки интерактивных приложе­ний. Именно особенности реализация планирования потоков в наибольшей степени определяют специфику операционной системы, в частности, является ли она сис­темой пакетной обработки, системой разделения времени или системой реально­го времени.

В большинстве операционных систем универсального назначения планирование осуществляется динамически (on-line), то есть решения принимаются во время работы системы на основе анализа текущей ситуации. ОС работает в условиях неопределенности — потоки и процессы появляются в случайные моменты вре­мени и также непредсказуемо завершаются. Динамические планировщики могут гибко приспосабливаться к изменяющейся ситуации и не используют никаких предположений о мультипрограммной смеси. Для того чтобы оперативно найти в условиях такой неопределенности оптимальный в некотором смысле порядок вы­полнения задач, операционная система должна затрачивать значительные усилия.

Другой тип планирования — статический — может быть использован в специа­лизированных системах, в которых весь набор одновременно выполняемых за­дач определен заранее, например в системах реального времени. Планировщик называется статическим (или предварительным планировщиком), если он при­нимает решения о планировании не во время работы системы, а заранее (off-line). Соотношение между динамическим и статическим планировщиками аналогично соотношению между диспетчером железной дороги, который пропускает поезда строго по предварительно составленному расписанию, и регулировщиком на пе­рекрестке автомобильных дорог, не оснащенном светофорами, который решает, какую машину остановить, а какую пропустить, в зависимости от ситуации на перекрестке.

Результатом работы статического планировщика является таблица, называемая расписанием, в которой указывается, какому потоку/процессу, когда и на какое время должен быть предоставлен процессор. Для построения расписания плани­ровщику нужны как можно более полные предварительные знания о характери­стиках набора задач, например о максимальном времени выполнения каждой за­дачи, ограничениях предшествования, ограничениях по взаимному исключению, предельным срокам и т. д.

После того как расписание готово, оно может использоваться операционной сис­темой для переключения потоков и процессов. При этом накладные расходы ОС на исполнение расписания оказываются значительно меньшими, чем при дина­мическом планировании, и сводятся лишь к диспетчеризации потоков/процессов.

Диспетчеризация заключается в реализации найденного в результате планирова­ния (динамического или статистического) решения, то есть в переключении про­цессора с одного потока на другой. Прежде чем прервать выполнение потока, ОС запоминает его контекст, с тем чтобы впоследствии использовать эту инфор­мацию для последующего возобновления выполнения данного потока. Контекст отражает, во-первых, состояние аппаратуры компьютера в момент прерывания потока: значение счетчика команд, содержимое регистров общего назначения,

режим работы процессора, флаги, маски прерываний и другие параметры. Во-вторых, контекст включает параметры операционной среды, а именно ссылки на открытые файлы, данные о незавершенных операциях ввода-вывода, коды оши­бок выполняемых данным потоком системных вызовов и т. д.

Диспетчеризация сводится к следующему:

□ сохранение контекста текущего потока, который требуется сменить;

□  загрузка контекста нового потока, выбранного в результате планирования;

□  запуск нового потока на выполнение.

Поскольку операция переключения контекстов существенно влияет на произ­водительность вычислительной системы, программные модули ОС выполняют диспетчеризацию потоков совместно с аппаратными средствами процессора.

В контексте потока можно выделить часть, общую для всех потоков данного про­цесса (ссылки на открытые файлы), и часть, относящуюся только к данному по­току (содержимое регистров, счетчик команд, режим процессора). Например, в среде NetWare 4.x различаются три вида контекстов: глобальный контекст (кон­текст процесса), контекст группы потоков и контекст отдельного потока. Соот­ношение между данными этих контекстов напоминает соотношение глобальных и локальных переменных в программе, написанной на языке С. Переменные гло­бального контекста доступны для всех потоков, созданных в рамках одного про­цесса. Переменные локального контекста доступны только для кодов определен­ного потока, аналогично локальным переменным функции. В NetWare можно соз­давать несколько групп потоков внутри одного процесса и эти группы будут иметь свой групповой контекст. Переменные, принадлежащие групповому контексту, доступны всем потокам, входящим в группу, но недоступны остальным потокам.

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

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------

В различных ОС можно встретить компоненты ОС, имеющие названия планировщик (scheduler) или диспетчер {dispatcher). He следует однозначно судить о функциональном назначении этих компонентов по их названиям, то есть считать, что планировщик выпол­няет планирование, а диспетчер — диспетчеризацию в том смысле, в котором эти функции были определены выше. Чаще всего то и другое названия используются для обозначения компонентов, которые занимаются планированием.

 

Состояния потока

 

ОС выполняет планирование потоков, принимая во внимание их состояние. В мультипрограммной системе поток может находиться в одном из трех основ­ных состояний:

□   выполнение — активное состояние потока, во время которого поток обладает всеми необходимыми ресурсами и непосредственно выполняется процессором;

Q ожидание — пассивное состояние потока, находясь в котором, поток заблоки­рован по своим внутренним причинам (ждет осуществления некоторого со­бытия, например завершения операции ввода-вывода, получения сообщения от другого потока или освобождения какого-либо необходимого ему ресурса);

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

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------Состояния выполнения и ожидания могут быть отнесены и к задачам, выполняющимся в однопрограммном режиме, а вот состояние готовности характерно только для режима

муль­типрограммирования.

 

В течение своей жизни каждый поток переходит из одного состояния в другое в соответствии с алгоритмом планирования потоков, принятым в данной операци­онной системе.

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

 

В состоянии выполнения в однопроцессорной системе может находиться не бо­лее одного потока, а в каждом из состояний ожидания и готовности — несколько

потоков. Эти потоки образуют очереди соответственно ожидающих и готовых потоков. Очереди потоков организуются путем объединения в списки описателей отдельных потоков. Таким образом, каждый описатель потока, кроме всего прочего, содержит по крайней мере один указатель на другой описатель, соседствующий с ним в очереди. Такая организация очередей позволяет легко их переупорядочивать, включать и исключать потоки, переводить потоки из одного состояния в другое. Если предположить, что на рис. 4.4 показана очередь готовых потоков, то заплани­рованный порядок выполнения выглядит так: А, В, Е, D, С.

 

 

 

Вытесняющие и невытесняющие алгоритмы планирования

 

С самых общих позиций все множество алгоритмов планирования можно разде­лить на два класса: вытесняющие и невытесняющие алгоритмы планирования.

□  Невытесняющие (non-preemptive) алгоритмы основаны на том, что активному потоку позволяется выполняться, пока он сам, по собственной инициативе, не отдаст управление операционной системе для того, чтобы та выбрала из очереди другой готовый к выполнению поток.

□  Вытесняющие (preemptive) алгоритмы — это такие способы планирования по­токов, в которых решение о переключении процессора с выполнения одного потока на выполнение другого потока принимается операционной системой, а не активной задачей.

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

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

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

Для пользователей это означает, что управление системой теряется на произ­вольный период времени, который определяется приложением (а не пользова­телем). Если приложение тратит слишком много времени на выполнение какой-либо работы, например на форматирование диска, пользователь не может пере­ключиться с этой задачи на другую задачу, например на текстовый редактор, в то время как форматирование продолжалось бы в фоновом режиме.

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

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

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------

Понятия вытесняющих и невытесняющих алгоритмов планирования иногда отождеств­ляют с понятиями приоритетных и бесприоритетных дисциплин, что, возможно, связано со звучанием соответствующих англоязычных терминов «preemptive» и «non-preemptive». Однако это совершенно неверно, так как приоритеты в том и другом случаях могут как ис­пользоваться, так и не использоваться.

 

Почти во всех современных операционных системах, ориентированных на высо­копроизводительное выполнение приложений (UNIX, Windows NT/2000, OS/2,

VAX/VMS), реализованы вытесняющие алгоритмы планирования потоков (про­цессов). В последнее время дошла очередь и до ОС класса настольных систем, например OS/2 Warp и Windows 95/98.

Примером эффективного использования невытесняющего планирования явля­ются файл-серверы NetWare 3.x и 4.x, в которых в значительной степени благо­даря такому планированию достигнута высокая скорость выполнения файловых операций. В соответствии с концепцией невытесняющего планирования, чтобы не занимать процессор слишком долго, поток в NetWare сам отдает управление планировщику ОС, используя следующие системные вызовы:

 

□  ThreadSwitch — поток, вызвавший эту функцию, считает себя готовым к немед­ленному выполнению, но отдает управление для того, чтобы могли выпол­няться и другие потоки;

□  ThreadSwitchWithDelay — функция аналогична предыдущей, но поток считает, что будет готов к выполнению только через определенное количество пере­ключений с потока на поток;

□  Del ay — функция аналогична предыдущей, но задержка дается в миллисекундах;

ThreadSwitchLowPriority — функция отдачи управления, отличается от Thread-Switch тем, что поток просит поместить его в очередь готовых к выполнению, но низкоприоритетных потоков.

 

Планировщик NetWare использует несколько очередей готовых потоков (рис. 4.5). Только что созданный поток попадает в конец очереди RunList, которая содержит готовые к выполнению потоки. После отказа от процессора поток попадает в ту или иную очередь в зависимости от того, какой из системных вызовов был ис­пользован при передаче управления. Поток поступает в конец очереди RunList при вызове ThreadSwitch, в конец очереди DelayedWorkToDoList при вызовах Thread-SwitchWithDelay или Delay или же в конец очереди LowPriorityRunList при вызове ThreadSwitchLowPriority.

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

Потоки, находящиеся в очереди DelayedWorkToDoList, после завершения условия ожидания перемещаются в конец очереди RunList.

Потоки, находящиеся в очереди LowPriorityRunList, запускаются на выполнение только в том случае, если очередь RunList пуста. Обычно в эту очередь назнача­ются потоки, выполняющие несрочную фоновую работу.

Очередь WorkToDolist является в системе самой приоритетной. В эту очередь по­падают так называемые рабочие потоки. В NetWare, как и в некоторых других ОС, вместо создания нового потока для выполнения определенной работы мо­жет быть использован уже существующий системный поток. Пул рабочих пото­ков создается при старте системы для системных целей и выполнения срочных работ. Рабочие потоки ОС обладают наивысшим приоритетом, то есть попадают на выполнение перед потоками из очереди RunList. Планировщик разрешает выполниться подряд только определенному количеству потоков из очереди Work-ToDoList, а затем запускает поток из очереди RunList.

 

 

Описанный невытесняющий механизм организации многопоточной работы в ОС NetWare v3.x и NetWare 4.x потенциально очень производителен, так как от­личается небольшими накладными расходами ОС на диспетчеризацию потоков за счет простых алгоритмов планирования и иерархии контекстов. Но для дости­жения высокой производительности к разработчикам приложений для ОС Net­Ware предъявляются высокие требования, так как распределение процессорного времени между различными приложениями зависит в конечном счете от искус­ства программиста.

 

Алгоритмы планирования, основанные на квантовании

 

В основе многих вытесняющих алгоритмов планирования лежит концепция квантования. В соответствии с этой концепцией каждому потоку поочередно для выполнения предоставляется ограниченный непрерывный период процессорно­го времени — квант. Смена активного потока происходит, если:

□   поток завершился и покинул систему;

□   произошла ошибка;

□   поток перешел в состояние ожидания;

□   исчерпан квант процессорного времени, отведенный данному потоку.

Поток, который исчерпал свой квант, переводится в состояние готовности и ожи­дает, когда ему будет предоставлен новый квант процессорного времени, а на вы­полнение в соответствии с определенным правилом выбирается новый поток из очереди готовых, Граф состояний потока, изображенный на рис. 4.6, соответству­ет алгоритму планирования, основанному на квантовании.

 

 

Кванты, выделяемые потокам, могут быть одинаковыми для всех потоков или одинаковой длины q (рис. 4.7). Если в системе имеется n потоков, то время, которое поток различными. Рассмотрим, например, случай, когда всем потокам предоставляют­ся кванты проводит в ожидании следующего кванта, можно грубо оце­нить как q(n-l). Чем больше потоков в системе, тем больше время ожидания, тем меньше возможности вести одновременную интерактивную работу нескольким пользователям. Но если величина кванта выбрана очень небольшой, то значение произведения q(n-1) все равно будет достаточно мало для того, чтобы пользова­тель не ощущал дискомфорта от присутствия в системе других пользователей. Типичное значение кванта в системах разделения времени составляет десятки

 

 

Если квант короткий, то суммарное время, которое проводит поток в ожидании процессора, прямо пропорционально времени, требуемому для его выполнения (то есть времени, которое потребовалось бы для выполнения этого потока при монопольном использовании вычислительной системы). Действительно, поскольку время ожидания между двумя циклами выполнения равно q(n-l), а количество циклов B/q, где В — требуемое время выполнения, то W=B(n-l). За­метим, что эти соотношения представляют собой весьма грубые оценки, осно­ванные на предположении, что В значительно превышает q. При этом не учиты­вается, что потоки могут использовать кванты не полностью, что часть времени они могут тратить на ввод-вывод, что количество потоков в системе может ди­намически меняться и т. д.

Чем больше квант, тем выше вероятность того, что потоки завершатся в ре­зультате первого же цикла выполнения, и тем менее явной становится зависи­мость времени ожидания потоков от их времени выполнения. При достаточно большом кванте алгоритм квантования вырождается в алгоритм последова­тельной обработки, присущий однопрограммным системам, при котором вре­мя ожидания задачи в очереди вообще никак не зависит от ее длительности.

Кванты, выделяемые одному потоку, могут быть фиксированной величины, а могут и изменяться в разные периоды жизни потока. Пусть, например, перво­начально каждому потоку назначается достаточно большой квант, а величина каждого следующего кванта уменьшается до некоторой заранее заданной ве­личины. В таком случае преимущество получают короткие задачи, которые успевают выполняться в течение первого кванта, а длительные вычисления будут проводиться в фоновом режиме. Можно представить себе алгоритм планирования, в котором каждый следующий квант, выделяемый определен­ному потоку, больше предыдущего. Такой подход позволяет уменьшить на­кладные расходы на переключение задач в том случае, когда сразу несколько задач выполняют длительные вычисления.

Потоки получают для выполнения квант времени, но некоторые из них ис­пользуют его не полностью, например из-за необходимости выполнить ввод или вывод данных. В результате возникает ситуация, когда потоки с интен­сивными обращениями к вводу-выводу используют только небольшую часть выделенного им процессорного времени. Алгоритм планирования может ис­править эту «несправедливость». В качестве компенсации за неиспользован­ные полностью кванты потоки получают привилегии при последующем об­служивании. Для этого планировщик создает две очереди готовых потоков (рис. 4.8). Очередь 1 образована потоками, которые пришли в состояние го­товности в результате исчерпания кванта времени, а очередь 2 — потоками, у которых завершилась операция ввода-вывода. При выборе потока для выпол­нения прежде всего просматривается вторая очередь, и только если она пуста, квант выделяется потоку из первой очереди.

Многозадачные ОС теряют некоторое количество процессорного времени для выполнения вспомогательных работ во время переключения контекстов задач. При этом запоминаются и восстанавливаются регистры, флаги и указатели сте­ка, а также проверяется статус задач для передачи управления. Затраты на эти вспомогательные действия не зависят от величины кванта времени, поэтому чем больше квант, тем меньше суммарные накладные расходы, связанные с переклю­чением потоков.

 

 

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

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

 

Алгоритмы планирования, основанные на приоритетах

 

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

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

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

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

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

В качестве примера рассмотрим схему назначения приоритетов потокам, приня­тую в операционной системе Windows NT (рис. 4.9). В системе определено 32 уровня приоритетов и два класса потоков — потоки реального времени и по­токи с переменными приоритетами. Диапазон от 1 до 15 включительно отведен для потоков с переменными приоритетами, а от 16 до 31 — для более критичных ко времени потоков реального времени (приоритет 0 зарезервирован для систем­ных целей).

 

При создании процесса он в зависимости от класса получает по умолчанию ба­зовый приоритет в верхней или нижней части диапазона. Базовый приоритет процесса в дальнейшем может быть повышен или понижен операционной систе­мой. Первоначально поток получает значение базового приоритета из диапазона базового приоритета процесса, в котором он был создан. Пусть, например, значе­ние базового приоритета некоторого процесса равно К. Тогда все потоки данного процесса получат базовые приоритеты из диапазона [К-2, К+2]. Отсюда видно,  что, изменяя базовый приоритет процесса, ОС может влиять на базовые приори­теты его потоков.

В Windows NT с течением времени приоритет потока, относящегося к классу по­токов с переменными приоритетами, может отклоняться от базового приоритета    потока, причем эти изменения могут быть не связаны с изменениями базового приоритета процесса. ОС может повышать приоритет потока (который в этом ;   случае называется динамическим) в тех случаях, когда поток не полностью использовал отведенный ему квант, или понижать приоритет, если квант был ис­пользован полностью. ОС наращивает приоритет дифференцированно в зави­симости от того, какого типа событие не дало потоку полностью использовать квант. В частности, ОС повышает приоритет в большей степени потокам, кото­рые ожидают ввода с клавиатуры (интерактивным приложениям) и в меньшей степени — потокам, выполняющим дисковые операции. Именно на основе динамических приоритетов осуществляется планирование потоков. Начальной точкой отсчета для динамического приоритета является значение базового приоритета потока. Значение динамического приоритета потока ограничено снизу его базовым приоритетом, верхней же границей является нижняя граница диапазона приоритетов реального времени.

Существуют две разновидности приоритетного планирования: обслуживание с    относительными приоритетами и обслуживание с абсолютными приоритетами.

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

В системах с абсолютными приоритетами выполнение активного потока преры­вается кроме указанных выше причин, еще при одном условии: если в очереди готовых потоков появился поток, приоритет которого выше приоритета активного потока. В этом случае прерванный поток переходит в состояние готовности

  (рис. 4.10, б).

В системах, в которых планирование осуществляется на основе относительных приоритетов, минимизируются затраты на переключения процессора с одной ра­боты на другую. С другой стороны, здесь могут возникать ситуации, когда одна задача занимает процессор долгое время. Ясно, что для систем разделения вре­мени и реального времени такая дисциплина обслуживания не подходит: интер­активное приложение может ждать своей очереди часами, пока вычислительной

задаче не потребуется ввод-вывод. А вот в системах пакетной обработки (в том числе известной ОС OS/360) относительные приоритеты используются широко.

 

                       

 

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

 

 

 

 

Смешанные алгоритмы планирования

 

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

Рассмотрим более подробно алгоритм планирования в операционной системе UNIX System V Release 4. В этой ОС понятие «поток» отсутствует, и планирова­ние осуществляется на уровне процессов. В системе UNIX System V Release 4 реализована вытесняющая многозадачность, основанная на использовании при­оритетов и квантования.

Каждый процесс в зависимости от задачи, которую он решает, относится к одно­му из трех определенных в системе приоритетных классов: классу реального времени, классу системных процессов или классу процессов разделения вре­мени. Назначение и обработка приоритетов выполняются для разных классов по-разному. Процессы системного класса, зарезервированные для ядра, исполь­зуют стратегию фиксированных приоритетов. Уровень приоритета процессу на­значается ядром и никогда не изменяется.

Процессы реального времени также используют стратегию фиксированных при­оритетов, но пользователь может их изменять. Так как при наличии готовых к выполнению процессов реального времени другие процессы не рассматривают­ся, то процессы реального времени надо тщательно проектировать, чтобы они не захватывали процессор на слишком долгое время. Характеристики планирова­ния процессов реального времени включают две величины: уровень глобально­го приоритета и квант времени. Для каждого уровня приоритета по умолчанию имеется своя величина кванта времени. Процессу разрешается захватывать про­цессор на указанный квант времени, а по его истечении планировщик снимает процесс с выполнения.

Процессы разделения времени были до появления UNIX System V Release 4 един­ственным классом процессов, и по умолчанию UNIX System V Release 4 назнача­ет новому процессу именно этот класс. Состав класса процессов разделения вре­мени наиболее неопределенный и часто меняющийся в отличие от системных процессов и процессов реального времени. Для справедливого распределения времени процессора между процессами в этом классе используется стратегия ди­намических приоритетов. Величина приоритета, назначаемого процессам разде­ления времени, вычисляется пропорционально значениям двух составляющих: пользовательской части и системной части. Пользовательская часть приоритета может быть изменена администратором и владельцем процесса, но в последнем случае только в сторону его снижения.

Системная составляющая позволяет планировщику управлять процессами в за­висимости от того, как долго они занимают процессор, не уходя в состояние ожидания. У тех процессов, которые потребляют большие периоды процессорно­го времени без ухода в состояние ожидания, приоритет снижается, а у тех про­цессов, которые часто уходят в состояние ожидания после короткого периода использования процессора, приоритет повышается. Таким образом, процессам, ведущим себя не «по-джентльменски», дается низкий приоритет. Это означает, что они реже выбираются для выполнения. Это ущемление в правах компенси­руется тем, что процессам с низким приоритетом даются большие кванты вре­мени, чем процессам с высокими приоритетами. Таким образом, хотя низко­приоритетный процесс и не работает так часто, как высокоприоритетный, но зато, когда он наконец выбирается для выполнения, ему отводится больше вре­мени,

Другой пример относится к операционной системе OS/2. Планирование здесь основано на использовании квантования и абсолютных динамических приори­тетов. На множестве потоков определены приоритетные классы — критический (time critical), серверный (server), стандартный (regular) и остаточный (idle), в каждом из которых имеется 32 приоритетных уровня. Потоки критического класса имеют наивысший приоритет. В этот класс могут быть отнесены, напри­мер, системные потоки, выполняющие задачи управления сетью. Следующий по приоритетности класс предназначен, как это следует из его названия, для  потоков, обслуживающих серверные приложения. К стандартному классу могут быть отнесены потоки обычных приложений. Потоки, входящие в остаточный класс, имеют самый низкий приоритет. К этому классу относится, например, поток, выводящий на экран заставку, когда в системе не выполняется никакой ра­боты.

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

Приоритеты могут изменяться планировщиком в следующих случаях;

□ Если поток находится в ожидании процессорного времени дольше, чем это задано системной переменной MAXWAIT, то его уровень приоритета будет авто­матически увеличен операционной системой. При этом результирующее зна­чение приоритета не должно превышать нижней границы диапазона приори­тетов критического класса.

□ Если поток ушел на выполнение операции ввода-вывода, то после ее заверше­ния он получит наивысшее значение приоритета своего класса.

□ Приоритет потока автоматически повышается, когда он поступает на выпол­нение.

ОС динамически устанавливает величину кванта, отводимого потоку для выпол­нения. Величина кванта зависит от загрузки системы и интенсивности подкачки. Параметры настройки системы позволяют явно задать границы изменения кван­та. В любом случае он не может быть меньше 32 мс и больше 65 536 мс. Если по­ток был прерван до истечения кванта, то следующий выделенный ему интервал выполнения будет увеличен на время, равное одному периоду таймера (около 32 мс), и так до тех пор, пока квант не достигнет заранее заданного при настрой­ке ОС предела.

Благодаря такому алгоритму планирования в OS/2 ни один поток не будет «за­быт» системой и получит достаточно процессорного времени.

 

Планирование в системах реального времени

 

  В системах реального времени, в которых главным критерием эффективности Е  является обеспечение  временных характеристик вычислительного  процесса,    планирование имеет особое значение. Любая система реального времени должна   реагировать на сигналы управляемого объекта в течение заданных временных    ограничений. Необходимость тщательного планирования работ облегчается тем, f что в системах реального времени весь набор выполняемых задач известен заранее. Кроме того, часто в системе имеется информация о временах выполнения задач, моментах активизации, предельных допустимых сроках ожидания ответа и т. д. Эти данные могут быть использованы планировщиком для создания ста­тического расписания или для построения адекватного алгоритма динамического планирования.

При разработке алгоритмов планирования для систем реального времени необ­ходимо учитывать, какие последствия в этих системах возникают при несоблюдении временных ограничений. Если эти последствия катастрофичны, как, например, для системы управления полетами или атомной электростанцией, то операционная система реального времени, на основе которой строится управле­ние объектом, называется жесткой (hard). Если же последствия нарушения вре­менных ограничений не столь серьезны, то есть сравнимы с той пользой, кото­рую приносит система управления объектом, то система является мягкой (soft) системой реального времени. Примером мягкой системы реального времени является система резервирования билетов. Если из-за временных нарушений опе­ратору не удается зарезервировать билет, это не очень страшно — можно просто послать запрос на резервирование заново.

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

В мягких системах реального времени предполагается, что заданные временные ограничения могут иногда нарушаться, поэтому здесь обычно применяются  ме­нее затратные способы планирования.

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

При рассмотрении в качестве примеров подсистем планирования потоков/процессов в 

операционных системах Windows NT, OS/2 и UNIX System V Release 4 было отмечено, 

что во всех этих системах имеется приоритетный класс реального времени. Для пота-  ков/процессов, относящихся к этому классу, каждая из вышеназванных систем не гарантирует выполнение заданных временных ограничений, а лишь обеспечивает предпочтение  в скорости обслуживания. Следовательно, эти ОС могут быть основой для построения  мягких систем реального времени, но непригодны для жестких систем реального времени.

---------------------------------------------------------------------------------------------------------------------

 

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

Предположим, что имеется периодический набор задач {Ti} с периодами рi  пре­дельными сроками di и требованиями ко времени выполнения ci. Для проверки возможности существования расписания достаточно проанализировать расписа­ние на периоде времени, равном по крайней мере наименьшему общему множителю периодов этих задач. Необходимым критерием существования расписания    для набора периодических задач является следующее достаточно очевидное утверждение: сумма коэффициентов использования μi =  Ci /pi должна быть меньше или равна k, где k — количество доступных процессоров, то есть:

 

                                              

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

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

Проблема планирования зависимых задач очень сложна, нахождение  ее оптималь­ного решения требует больших вычислительных ресурсов, сравнимых с теми, которые требуются для собственно выполнения задач управления. Решение этой проблемы возможно за счет следующих мер:

□ Разделение проблемы планирования на две части, чтобы одна часть выполня­лась заранее, перед запуском системы, а вторая, более простая часть — во вре­мя работы системы. Предварительный анализ набора задач с взаимными

исключениями может состоять, например, в выявлении так называемых за­прещенных областей времени, в течение которых нельзя назначать выполне­ние задач, содержащих критические секции.

□ Введение ограничивающих предположений о поведении набора задач.

При таком подходе планирование приближается к статическому.

Возвращаясь к планированию независимых задач, рассмотрим классический ал­горитм для жестких систем реального времени с одним процессором, разрабо­танный в 1973 году Лью (Liu) и Лейландом (Layland). Алгоритм является дина­мическим, то есть он использует вытесняющую многозадачность и основан на относительных статических (неизменяемых в течение жизни задачи) приоритетах.

Алгоритм основан на следующих предположениях:

□  Запросы на выполнение всех задач набора, имеющих жесткие ограничения на время реакции, являются периодическими.

□  Все задачи независимы. Между любой парой задач не существует никаких ог­раничений на предшествование или на взаимное исключение.

□ Срок выполнения каждой задачи равен ее периоду pi.

□  Максимальное время выполнения каждой задачи сi известно и постоянно.

□ Время переключения контекста можно игнорировать.

□ Максимальный суммарный коэффициент загрузки процессора 2 Σ ci/pi при су­ществовании n задач не превосходит n (21/n - 1). Эта величина при стремле­нии n к бесконечности приблизительно равна n 2, то есть 0,7.

Суть алгоритма состоит в том, что всем задачам назначаются статические приоритеты в соответствии с величиной их периодов выполнения. Задача с самым коротким периодом получает наивысший приоритет, а задача с наибольшим периодом выполнения получает наименьший приоритет. При соблюдении всех ог­раничений этот алгоритм гарантирует выполнение временных ограничений для   всех задач во всех ситуациях.

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

 

Существуют также алгоритмы с динамическим изменением приоритетов, которые назначаются в соответствии с такими текущими параметрами задачи как, например, конечный срок выполнения (deadline). При необходимости назначения некоторой задачи на выполнение выбирается та, у которой текущее значение раз­ницы между конечным сроком выполнения и временем, требуемым для ее не­прерывного выполнения, является наименьшим.

 

Моменты перепланировки

 

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

процессорного времени. К таким событиям могут быть отнесены следующие:

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

□   Активная задача выполнила системный вызов, связанный с запросом на ввод-  вывод или на доступ к ресурсу, который в настоящий момент занят (например, файл данных). Планировщик переводит задачу в состояние ожидания и  выполняет перепланирование.

□   Активная задача выполнила системный вызов, связанный с освобождением  ресурса. Планировщик проверяет, не ожидает ли этот ресурс какая-либо задача. Если да, то эта задача переводится из состояния ожидания в состояние готовности. При этом, возможно, что задача, которая получила ресурс, имеет более высокий приоритет, чем текущая активная задача. После перепланирования более приоритетная задача получает доступ к процессору, вытесняя текущую задачу.

□   Внешнее (аппаратное) прерывание которое сигнализирует о завершении периферийным устройством операции ввода-вывода, переводит соответствующую задачу в очередь готовых, и выполняется планирование.

□ Внутреннее прерывание сигнализирует об ошибке, которая произошла в результате выполнения активной задачи. Планировщик снимает задачу и выполняет перепланирование.

При возникновении каждого из этих событий планировщик выполняет просмотр  

очередей и решает вопрос о том, какая задача будет выполняться следующей.  

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

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

Первые четыре цикла работы планировщика, приведенные на рисунке, были инициированы прерываниями от таймера по истечении квантов времени (эти события обозначены на рисунке как Т).

Следующая передача управления планировщику была осуществлена в результате  выполнения потоком 3 системного запроса на ввод-вывод (событие I/O). Планировщик перевел этот поток в состояние ожидания, а затем переключил процессор на поток 2. Поток 2 полностью использовал свой квант, произошло прерывание от таймера, и планировщик активизировал поток .

При выполнении потока  произошло событие R — системный вызов, в резуль­тате которого освободился некоторый ресурс (например, был закрыт файл). Это

событие вызвало перепланировку потоков. Планировщик просмотрел очередь ожидающих потоков и обнаружил, что поток 4 ждет освобождения данного ре­сурса. Этот поток был переведен в состояние готовности, но поскольку приоритет выполняющегося в данный момент потока 1 выше приоритета потока 4, планировщик вернул процессор потоку 1.

 

           

 

В следующем цикле работы планировщик активизировал поток 4, а затем, после истечения кванта и сигнала от таймера, управление получил поток 2. Этот поток не успел использовать свой квант, так как был снят с выполнения в результате    возникшей ошибки (событие ER).

Далее планировщик предоставлял процессорное время потокам 1, 4 и снова 1. Во время выполнения потока 1 произошло прерывание S от внешнего устройства, сиг­нализирующее о том, что операция передачи данных завершена. Это событие ак­тивизировало работу планировщика, в результате которой поток 3, ожидавший завершения ввода-вывода, вытеснил поток 1, так как имел в этот момент более  высокий приоритет.

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

В системах реального времени для отработки статического расписания плани­ровщик активизируется по прерываниям от таймера. Эти прерывания пронизы­вают всю временную ось, возникая через короткие постоянные интервалы вре­мени. После каждого прерывания планировщик просматривает расписание и проверяет, не пора ли переключить задачи. Кроме прерываний от таймера в сис­темах реального времени перепланирование задач может происходить по преры­ваниям от внешних устройств — различного вида датчиков и исполнительных механизмов.

 

Мультипрограммирование на основе прерываний

 

Назначение и типы прерываний

 

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

Как верно было замечено: «Прерывания названы так весьма удачно, поскольку они прерывают нормальную работу системы». Другими словами, система прерываний переводит процессор на выполнение потока команд, отличного от  того, который выполнялся до сих пор, с последующим возвратом к исходному коду. Из сказанного можно сделать вывод о том, что механизм прерываний очень похож на механизм выполнения процедур. Это на самом деле так, хотя между этими механизмами имеется важное отличие. Переключение по прерыва­нию отличается от переключения, которое происходит по команде безусловно­го или условного перехода, предусмотренной программистом в потоке команд приложения. Переход по команде происходит в заранее определенных програм­мистом точках программы в зависимости от исходных данных, обрабатываемых программой. Прерывание же происходит в произвольной точке потока команд программы, которую программист не может прогнозировать. Прерывание возни­кает либо в зависимости от внешних по отношению к процессу выполнения про­граммы событий, либо при появлении непредвиденных аварийных ситуаций в   процессе выполнения данной программы. Сходство же прерываний с процеду­рами состоит в том, что в обоих случаях выполняется некоторая подпрограмма, обрабатывающая специальную ситуацию, а затем продолжается выполнение основной ветви программы.

 

В зависимости от источника прерывания делятся на три больших класса:

 

 

□ внешние;

□ внутренние;

□ программные.

 

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

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

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

 Программные прерывания отличаются от предыдущих двух классов тем, что они

 по своей сути не являются «истинными» прерываниями. Программное прерыва­ние возникает при выполнении особой команды процессора, выполнение которой имитирует прерывание, то есть переход на новую последовательность инструк­ций. Причины использования программных прерываний вместо обычных инст­рукций вызова процедур будут изложены ниже, после рассмотрения механизма прерываний.

Прерываниям приписывается приоритет, с помощью которого они ранжируются   по степени важности и срочности. О прерываниях, имеющих одинаковое значе­ние приоритета, говорят, что они относятся к одному уровню приоритета прерываний.

Прерывания обычно обрабатываются модулями операционной системы, так как действия, выполняемые по прерыванию, относятся к управлению разделяемыми ресурсами вычислительной системы — принтером, диском, таймером, процессо­ром и т. п. Процедуры, вызываемые по прерываниям, обычно называют обработ­чиками прерываний, или процедурами обслуживания прерываний (Interrupt Service Routine, ISR). Аппаратные прерывания обрабатываются драйверами соответствую­щих внешних устройств, исключения — специальными модулями ядра, а про­граммные прерывания — процедурами ОС, обслуживающими системные вызовы. Кроме этих модулей в операционной системе может находиться так называемый диспетчер прерываний, который координирует работу отдельных обработчиков прерываний.

 

Механизм прерываний

 

Механизм прерываний поддерживается аппаратными средствами компьютера и программными средствами операционной системы. Аппаратная поддержка преры­ваний имеет свои особенности, зависящие от типа процессора и других аппарат­ных компонентов, передающих сигнал запроса прерывания от внешнего устрой­ства к процессору (таких, как контроллер внешнего устройства, шины подклю­чения внешних устройств, контроллер прерываний, являющийся посредником между сигналами шины и сигналами процессора). Особенности аппаратной peaлизации прерываний оказывают влияние на средства программной поддержки прерываний, работающие в составе ОС.

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

Устройствам, которые используют векторные прерывания, назначается вектор прерываний. Он представляет собой электрический сигнал, выставляемый на со­ответствующие шины процессора и несущий в себе информацию об определен­ном, закрепленном за данным устройством номере, который идентифицирует со- | ответствующий обработчик прерываний. Этот вектор может быть фиксированным, конфигурируемым (например, с использованием переключателей) или програм­мируемым. Операционная система может предусматривать процедуру регистра­ции вектора обработки прерываний для определенного устройства, которая свя­зывает некоторую подпрограмму обработки прерываний с определенным вектором. При получении сигнала запроса прерывания процессор выполняет специальный цикл подтверждения прерывания, в котором устройство должно идентифициро­вать себя. В течение этого цикла устройство отвечает, выставляя на шину вектор прерываний. Затем процессор использует этот вектор для нахождения обработ­чика данного прерывания. Примером шины подключения внешних устройств, кото­рая поддерживает векторные прерывания, является шина VMEbus.

При использовании опрашиваемых прерываний процессор получает от запросив­шего прерывание устройства только информацию об уровне приоритета преры­вания (например, номере IRQ на шине ISA или номере IPL на шине SBus компью­теров SPARC). С каждым уровнем прерываний может быть связано несколько устройств и соответственно несколько программ — обработчиков прерываний. При возникновении прерывания процессор должен определить, какое устройст­во из тех, которые связаны с данным уровнем прерываний, действительно запро­сило прерывание. Это достигается вызовом всех обработчиков прерываний для данного уровня приоритета, пока один из обработчиков не подтвердит, что пре­рывание пришло от обслуживаемого им устройства. Если же с каждым уровнем прерываний связано только одно устройство, то определение нужной программы обработки прерывания происходит немедленно, как и при векторном прерывании, Опрашиваемые прерывания поддерживают шины ISA, EISA, MCA, PCI и Sbus.

Механизм прерываний некоторой аппаратной платформы может сочетать век­торный и опрашиваемый типы прерываний. Типичным примером такой реализа­ции является платформа персональных компьютеров на основе процессоров Intel Pentium. Шины PCI, ISA, EISA или MCA, используемые в этой платформе в ка­честве шин подключения внешних устройств, поддерживают механизм опра­шиваемых прерываний. Контроллеры периферийных устройств выставляют на шину не вектор, а сигнал запроса прерывания определенного уровня IRQ. Одна­ко в процессоре Pentium система прерываний является векторной. Вектор пре­рываний в процессор Pentium поставляет контроллер прерываний, который ото­бражает поступающий от шины сигнал IRQ на определенный номер вектора.

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

Механизм прерываний чаще всего поддерживает приоритезацию и маскирование прерываний. Приоритезация означает, что все источники прерываний делятся на классы и каждому классу назначается свой уровень приоритета запроса на преры­вание. Приоритеты могут обслуживаться как относительные и абсолютные. Об­служивание запросов прерываний по схеме с относительными приоритетами за­ключается в том, что при одновременном поступлении запросов прерываний из разных классов выбирается запрос, имеющий высший приоритет. Однако в даль­нейшем при обслуживании этого запроса процедура обработки прерывания уже не откладывается даже в том случае, когда появляются более приоритетные запросы — решение о выборе нового запроса принимается только в момент за­вершения обслуживания очередного прерывания. Если же более приоритетным прерываниям разрешается приостанавливать работу процедур обслуживания ме­нее приоритетных прерываний, то это означает, что работает схема приоритезации с абсолютными приоритетами.

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

Упорядоченное обслуживание запросов прерываний наряду со схемами приори­тетной обработки запросов может выполняться механизмом маскирования запро­сов. Собственно говоря, в описанной схеме абсолютных приоритетов выполняет­ся маскирование — при обслуживании некоторого запроса все запросы с равным или более низким приоритетом маскируются, до есть не обслуживаются. Схема маскирования предполагает возможность временного маскирования прерываний любого класса независимо от уровня приоритета.

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

  1. При возникновении сигнала (для аппаратных прерываний) или условия (для внутренних прерываний) прерывания происходит первичное аппаратное

распознавание типа прерывания. Если прерывания данного типа в настоящий < момент запрещены (приоритетной схемой или механизмом маскирования), то процессор продолжает поддерживать естественный ход выполнения команд, В противном случае в зависимости от поступившей в процессор информации (уровень прерывания, вектор прерывания или тип условия внутреннего пре­рывания) происходит автоматический вызов процедуры обработки. прерыва­ния, адрес которой находится в специальной таблице операционной системы, размещаемой либо в регистрах процессора, либо в определенном месте опера­тивной памяти.

2.   Автоматически сохраняется некоторая часть контекста прерванного потока, которая позволит ядру возобновить исполнение потока процесса после обра­ботки прерывания. В это подмножество обычно включаются значения счетчи­ка команд, слова состояния машины, хранящего признаки основных режимов работы процессора (пример такого слова — регистр EFLAGS в Intel Pentium), а также, нескольких регистров общего назначения, которые требуются про­грамме обработки прерывания. Может быть сохранен и полный контекст про­цесса, если ОС обслуживает данное прерывание со сменой процесса. Однако в общем случае это не обязательно, часто обработка прерываний выполняется без вытеснения текущего процесса.

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

4.   Временно запрещаются прерывания данного типа, чтобы не образовалась оче­редь вложенных друг в друга потоков одной и той же процедуры. Детали выполнения этой операции зависят от особенностей аппаратной платформы, например может использоваться механизм маскирования прерываний. Мно­гие процессоры автоматически устанавливают признак запрета прерываний в начале цикла обработки прерывания, в противном случае это делает програм­ма обработки прерываний.

5. После того как прерывание обработано ядром операционной системы, пре­рванный контекст восстанавливается и работа потока возобновляется с пре­рванного места. Часть контекста восстанавливается аппаратно по команде возврата из прерываний (например, адрес следующей команды и слово со­стояния машины), а часть — программным способом, с помощью явных ко­манд извлечения данных из стека. При возврате из прерывания блокировка повторных прерываний данного типа снимается.

 

Программные прерывания

 

Программное прерывание реализует один из способов перехода на подпрограм­му с помощью специальной инструкции процессора, такой как INT в процессо­рах Intel Pentium, trap в процессорах Motorola, syscail в процессорах MIPS или Ticc в процессорах SPARC. При выполнении команды программного прерывания процессор отрабатывает ту же последовательность действий, что и при возник­новении внешнего или внутреннего прерывания, но только происходит это в предсказуемой точке программы — там, где программист поместил данную ко­манду.

Практически все современные процессоры имеют в системе команд инструкции программных прерываний. Одной из причин появления инструкций программ­ных прерываний в системе команд процессоров является то, что их использова­ние часто приводит к более компактному коду программ по сравнению с исполь­зованием стандартных команд выполнения процедур. Это объясняется тем, что разработчики процессора обычно резервируют для обработки прерываний не­большое число возможных подпрограмм, так что длина операнда в команде про­граммного прерывания, который указывает на нужную подпрограмму, меньше, чем в команде перехода на подпрограмму. Например, в процессоре х86 преду­смотрена возможность применения 256 программ обработки прерываний, поэто­му в инструкции INT операнд имеет длину в один байт (а инструкция INT 3, кото­рая предназначена для вызова отладчика, вся имеет длину один байт). Значение операнда команды INT просто является индексом в таблице из 256 адресов под­программ обработки прерываний, один из которых и используется для перехода по команде INT. При использовании команды CALL потребовался бы уже не одно­байтовый, а двух- или четырехбайтовый операнд. Другой причиной применения программных прерываний вместо обычных инструкций вызова подпрограмм является возможность смены пользовательского режима на привилегированный одновременно с вызовом процедуры — это свойство программных прерываний поддерживается большинством процессоров.

В результате программные прерывания часто используются для выполнения ог­раниченного количества вызовов функций ядра операционной системы, то есть системных вызовов.

 

Диспетчеризация и приоритезация прерываний в ОС

 

Операционная система должна играть активную роль в организации обработки прерываний. Прерывания выполняют очень полезную для вычислительной сие-

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

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

Для упорядочения работы обработчиков прерываний в операционных системах применяется тот же механизм, что и для упорядочения работы пользователь­ских процессов — механизм приоритетных очередей. Все источники прерываний обычно делятся на несколько классов, причем каждому классу присваивается приоритет. В операционной системе выделяется программный модуль, который занимается диспетчеризацией обработчиков прерываний. Этот модуль в разных ОС называется по-разному, но для определенности будем его называть диспет­чером прерываний.

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

 

 

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------

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

 

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

прерываний на примере Windows NT

 

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

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

Для исключения зависимости от аппаратной платформы в некоторых ОС вво­дится собственная программная система приоритетов прерываний. Примером та­кой ОС может служить Windows NT.

Диспетчер прерывании Windows NT (так называемый Trap Handler) работает с программной моделью прерываний, единой для всех аппаратных платформ, поддерживаемых Windows NT. Все источники прерываний (аппаратных и про­граммных, а также некоторых важных для системы исключений, например исключения по ошибке шины) делятся на несколько классов, и каждому классу присваивается уровень запроса прерывания — Interrupt Request Level, IRQL Этот уровень и представляет приоритет данного класса. Операционная система про­граммным способом поддерживает внутреннюю переменную, называемую IRQL выполняемого процессором кода, которая по назначению соответствует уровню прерывания процессора. Если процессор, на котором работает ОС, поддерживает такую переменную, то она используется и IRQL выполняемого кода отображает­ся на нее, в противном случае соответствующие функции процессора эмулиру­ются программно.

Общая схема планирования обработки прерываний выглядит в Windows NT следующим образом. При поступлении в процессор сигнала запроса на прерыва­ние/исключение вызывается диспетчер прерываний, который запоминает инфор­мацию об источнике прерывания и анализирует его приоритет. Если приоритет запроса ниже или равен IRQL прерванного кода, то обслуживание этого запроса откладывается и данные о запросе помещаются в соответствующую очередь запро­сов, после чего происходит быстрый возврат к прерванному обработчику преры­ваний. По завершении обработки высокоприоритетного прерывания управление возвращается диспетчеру прерываний, который просматривает очереди отложен­ных прерываний и выбирает из них наиболее приоритетное. При этом уровень IRQL снижается до уровня выбранного прерывания.

Если же запрос имеет более высокий приоритет, чем IRQL текущего кода, то текущий обработчик прерываний вытесняется и ставится в очередь, соответст­вующую его значению IRQL, а управление передается новому обработчику в со­ответствии с IRQL запроса. После этого уровень IRQL процессора делается рав­ным уровню IRQL принятого на выполнение запроса.

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

процесса.

В Windows NT низший уровень IRQL соответствует обычным потокам, назна­чаемым на выполнение диспетчером потоков (рис. 4.13). Это является некото­рым допущением, так как код потоков начинает выполняться процессором не в результате запроса на прерывание, но это допущение хорошо работает, посколь­ку позволяет любому «настоящему» запросу прерывать код обычного потока.

 

                       

 

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

Прерывания от внешних устройств занимают промежуточные уровни IRQL.  Конкретное соотношение между приоритетами внешних устройств определяется  приоритетами, задаваемыми аппаратной платформой, например уровнем IRQ шины PCI, назначенным устройству.

   Особую роль в работе вычислительной системы играет системный таймер: на основании его прерываний обновляются системные часы, определяющие очередной момент вызова планировщика потоков, момент выдачи управляющего воз   действия потоком реального времени и многое другое. Ввиду важности немедленной обработки прерываний от таймера, ему в Windows NT дан весьма высокий  уровень приоритета — более высокий, чем уровень любого устройства ввода-вывода.  В системе очередей диспетчера прерываний несколько очередей отведено для обслуживания отложенных программных прерываний. Программные прерывания, обслуживающие системные вызовы от приложений, выполняются с низшим уровнем приоритета, что соответствует концепции продолжения одного и того же процесса, но только в системной фазе, при выполнении системного вызова. А вот для программных прерываний, исходящих от модулей ядра ОС, отводится более высокий уровень запросов, имеющий двойное  название «диспетчерский/DPC

Этот уровень приоритета называется диспетчерским, потому что именно в эту очередь помещаются программные запросы, вызывающие диспетчер потоков. Часто при обработке высокоприоритетных прерываний возникает ситуация, требующая перепланирования потоков. Например, при обработке очередного прерывания от таймера нужно проверить, не исчерпан ли квант, выделенный текущему потоку. Другим примером может служить обработка прерывания от контроллера диска после завершения дисковой операции, которую могут ждать несколько потоков. Во всех таких ситуациях в Window^ NT планировщик/диспетчер вызывается высокоуровневыми процедурами ядра не прямо посредством вызова процедуры, а косвенно, с помощью программного прерывания. Это дает возможность отде­лить короткую, но требующую быстрой реакции системы процедуру обслужи­вания высокоприоритетного прерывания (например, наращивание системных часов) от менее критичной операции перепланирования пользовательских пото­ков. Прямой вызов планировщика/диспетчера потоков такой возможности бы не дал, и критичные запросы прерывания от контроллеров устройств ввода-вывода вынуждены были бы ждать, пока отработает планировщик. При этом планиров­щик, возможно, выбрал бы для выполнения другой поток, если бы работал после процедур обслуживания аппаратных прерываний, так как он получил бы свежие сведения о .завершении некоторых операций ввода-вывода. Помещение вызова планировщика потоков в очередь позволяет выполнять его только в тех ситуаци­ях, когда в системе отсутствуют ожидающие аппаратные запросы прерываний.

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

Второе название диспетчерского уровня, DPC, являясь аббревиатурой от Deffered Procedure Call (вызов отложенной процедуры), говорит о том, что на этом уровне ожидают своей очереди отложенные вызовы и других процедур ОС, а не только планировщика/диспетчера. Процедуры ОС могут вызывать друг друга и непосредственно, но при многослойном построении ядра существуют более и ме­нее приоритетные процедуры, и вызов менее приоритетных процедур из более приоритетных с помощью механизма программных прерываний позволяет, как и в случае с планировщиком, упорядочить во времени их выполнение, что оптими­зирует работу ОС в целом. Примером процедур ОС, работающих на высоком приоритетном уровне, являются те части драйверов устройств ввода-вывода, ко­торые выполняют короткие, но критичные ко времени реакции действия. В то же время существуют и другие части драйверов, которые выполняют менее сроч­ную, но более объемную работу1. В Windows NT такие части драйверов оформляют как процедуры, вызываемые с помощью программных прерываний уровня «диспетчерский/DPO (DPC-процедуры), а само программное прерывание вы­полняет критичная часть драйвера. Естественно, существуют и другие модули [  ОС, оформляемые подобным образом.

Описанная программная реализация приоритетного обслуживания прерываний приводит к однотипной работе ОС Windows NT на различных аппаратных плат­формах, что упрощает логику работы ОС и ее перенос на новые платформы. От­рицательным следствием такого централизованного подхода является некоторое замедление обработки прерываний, так как вместо непосредственной передачи управления драйверу устройства или обработчику исключений выполняется вызов некоего посредника — диспетчера прерываний. В ОС семейства UNIX принят похожий, но менее централизованный подход к ведению и обработке, очередей прерываний. Вместо единого диспетчера прерываний его функции выполняют процедуры, обслуживающие каждый приоритетный класс прерываний. Тем не ме­нее, общий подход к упорядочиванию обработки прерываний за счет их много­уровневой приоритезации и ведения системы очередей присутствует практически во всех современных операционных системах.

 

Процедуры обработки прерываний и текущий процесс

 

Важной особенностью процедур, выполняемых по запросам прерываний, являет­ся то, что они выполняют работу, чаще всего никак не связанную с текущим про­цессом. Например, драйвер диска может получить управление после того, как контроллер диска записал в соответствующие сектора информацию, полученную от процесса А но этот момент времени, скорее всего, не совпадет с периодом оче­редной итерации выполнения процесса А или его потока. В наиболее типичном случае процесс А будет находиться в состоянии ожидания завершения операции ввода-вывода (при синхронном режиме выполнения этой операции) и драйвер диска прервет какой-либо другой процесс, например процесс В. В Windows NT процедуры, вызываемые как DPC, также могут работать в контексте процесса, отличающегося от того, для которого они выполняют свои функции. В некото­рых случаях вообще трудно однозначно определить, для какого процесса выпол­няет работу тот или иной программный модуль ОС, например планировщик по­токов. Поэтому для такого рода процедур вводятся ограничения — они не имеют права использовать ресурсы (память, открытые файлы и т. п.), с которыми рабо­тает текущий процесс, или же от имени этого процесса запрашивать выделение дополнительных ресурсов. Процедуры обработки прерываний работают с ресур­сами, которые были выделены им при инициализации соответствующего драй­вера или инициализации самой операционной системы. Эти ресурсы принад­лежат операционной системе, а не конкретному процессу. В частности, память выделяется драйверам из системной области, то есть той области, на которую отображаются сегменты из общей части виртуального адресного пространства всех процессов. Поэтому обычно говорят, что процедуры обработки прерываний работают вне контекста процесса. Поскольку все подобные процедуры являются частью операционной системы, ответственность за соблюдение этих ограничений несет системный программист. Заставить свои модули выполнять эти ограничения ОС не может.

Хороший пример того, что не бывает правил без исключений, предоставляет нам ОС Windows NT. В ней существуют процедуры обработки прерываний, которые  выполняются всегда в контексте определенного процесса. Это процедуры, вызываемые с помощью программного прерывания АРС (Asynchronous Procedure Call,  вызов асинхронной процедуры). Для них в диспетчере прерываний предусмотрен свои уровень приоритета IRQL, выше уровня для обычного кода, но ниже ) уровня DPC. Эти процедуры могут прервать текущий код и выполниться при  соблюдении двух условий: текущий код имеет низший уровень приоритета (то  есть выполняется обычный код), текущим процессом является вполне определенный процесс, описатель которого был задан в запросе на прерывание для данной процедуры АРС. Процедуры АРС могут пользоваться ресурсами текущего  процесса, и, собственно, для этого они и были введены. Основное назначение  АРС-процедур — перемещение данных, полученных драйвером от какого-либо устройства ввода-вывода, из памяти системной области памяти, куда они помещаются после считывания из регистров контроллера этого устройства, в индивидуальную часть адресного пространства процесса, запросившего операцию ввода-вывода. Такое действие постоянно выполняется системой ввода-вывода, и для  его реализации были введены такие специфические процедуры обработки прерываний, как АРС.

Диспетчеризация прерываний является важной функцией ОС, и эта функция

реализована практически во всех мультипрограммных операционных системах.  

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

 

Системные вызовы

 

Системный вызов позволяет приложению обратиться к операционной системе с просьбой выполнить то или иное действие, оформленное как процедура (или набор процедур) кодового сегмента ОС. Для прикладного программиста операционная система выглядит как некая библиотека, предоставляющая некоторый набор полезных функций, с помощью которых можно упростить прикладную   программу или выполнить действия, запрещенные в пользовательском режиме, например обмен данными с устройством ввода-вывода. Реализация системных вызовов должна удовлетворять следующим требованиям:

□   обеспечивать переключение в привилегированный режим;

□   обладать высокой скоростью вызова процедур ОС;

□   обеспечивать по возможности единообразное обращение к системным вызо­вам для всех аппаратных платформ, на которых работает ОС;

□ допускать легкое расширение набора системных вызовов;

□ обеспечивать контроль со стороны ОС за корректным использованием сис­темных вызовов.

 Первое требование для большинства аппаратных платформ может быть выпол­нено только с помощью механизма программных прерываний. Поэтому будем  считать,  остальные требования нужно обеспечить именно для такой реализации системных вызовов. Как это обычно бывает, некоторые из этих требований Е  взаимно противоречивы.

Для обеспечения высокой скорости было бы полезно использовать векторные свойства системы программных прерываний, имеющиеся во многих процессорах,  то есть закрепить за каждым системным вызовом определенное значение вектора. Приложение при таком способе вызова непосредственно указывает в аргу­менте запроса значение вектора, после чего управление немедленно передается    требуемой процедуре операционной системы (рис. 4.14, а). Однако этот децен­трализованный способ передачи управления привязан к особенностям аппарат  ной платформы, а также не позволяет операционной системе легко модифицировать набор системных вызовов и контролировать их использование. Например, в  процессоре Pentium количество системных вызовов определяется количеством векторов прерываний, выделенных для этой цели из общего пула в 256 элементов (часть которых используется под аппаратные прерывания и обработку исключений). Добавление нового системного вызова требует от системного программиста тщательного поиска свободного элемента в таблице прерываний,  которого к тому же на каком-то этапе развития ОС может и не оказаться.

 В большинстве ОС системные вызовы обслуживаются по централизованной схеме, основанной на существовании диспетчера системных вызовов (рис. 4.14, 6). . При любом системном вызове приложение выполняет программное прерывание  с определенным и единственным номером вектора. Например, ОС Linux использует для системных вызовов команду INT 80h, а ОС Windows NT (при работе на  платформе Pentium) — INT 2Eh. Перед выполнением программного прерывания  приложение тем или иным способом передает операционной системе номер системного вызова, который является индексом в таблице адресов процедур ОС,  реализующих системные вызовы (таблица sysent на рис. 4.14). Способ передачи   зависит от реализации,  например номер можно поместить в определенный регистр общего назначения процессора или передать через стек (в этом случае после прерывания и перехода в привилегированный режим их нужно будет скопировать в системный стек из пользовательского, это действие в некоторых процессорах автоматизировано). Также некоторым способом передаются аргументы системного вызова, они могут как помещаться в регистры общего назначения,  так и передаваться через стек или массив, находящийся в оперативной памяти.

Массив удобен при большом объеме данных, передаваемых в качестве аргументов, при этом в регистре общего назначения указывается адрес этого массива.

Диспетчер системных вызовов обычно представляет собой простую программу,

которая сохраняет содержимое регистров процессора в системном стеке (поскольку

в результате программного прерывания процессор переходит в привилегированный режим), проверяет, попадает ли запрошенный номер вызова в поддерживаемый ОС диапазон (то есть не выходит ли номер за границы таблицы) и переда­ет управление процедуре ОС, адрес которой задан в таблице адресов системных вызовов.

 

Процедура реализации системного вызова извлекает из системного стека аргу­менты и выполняет заданное действие. Это действие может быть весьма простым, например чтение значения системных часов, так что системный вызов оформля­ется в виде одной функции. Более сложные системные вызовы, такие как чтение из файла или выделение процессу дополнительного сегмента памяти, требуют об­ращения основной функции системного вызова к нескольким внутренним про­цедурам ядра ОС, принадлежащим к различным подсистемам, таким как подсис­тема ввода-вывода или управления памятью.

После завершения работы системного вызова управление возвращается диспетче­ру, при

этом он получает также код завершения этого вызова. Диспетчер восста­навливает регистры процессора, помещает в определенный регистр код возврата

и выполняет инструкцию возврата из прерывания, которая восстанавливает не­привилегированный режим работы процессора.

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

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

Описанный табличный способ организации системных вызовов принят практи­чески во всех операционных системах. Он позволяет легко модифицировать со­став системных вызовов, просто добавив в таблицу новый адрес и расширив диа­пазон допустимых номеров вызовов.

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

Асинхронный системный вызов не приводит к переводу процесса в режим ожида­ния после выполнения некоторых начальных системных действий, например за­пуска операции вывода-вывода, управление возвращается прикладному процес­су (рис. 4.15, б).

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

 

Синхронизация процессов и потоков

 

Цели и средства синхронизации

 

Существует достаточно обширный класс средств операционной системы, с по­мощью которых обеспечивается взаимная синхронизация процессов и потоков. Потребность в синхронизации потоков возникает только в мультипрограммной операционной системе и связана с совместным использованием аппаратных и информационных ресурсов вычислительной системы. Синхронизация необходи­ма для исключения гонок и тупиков при обмене данными между потоками, раз­делении данных, при доступе к процессору и устройствам ввода-вывода.

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

Выполнение потока в мультипрограммной среде всегда имеет асинхронный ха­рактер. Очень сложно с полной определенностью сказать, на каком этапе выпол­нения будет находиться процесс в определенный момент времени. Даже в однопрограммном режиме не всегда можно точно оценить время выполнения задачи. Это время во многих случаях существенно зависит от значения исходных дан­ных, которые влияют на количество циклов, направления разветвления програм­мы, время выполнения операций ввода-вывода и т. п. Так как исходные данные в разные моменты запуска задачи могут быть разными, то и время выполнения от­дельных этапов и задачи в целом является весьма неопределенной величиной.

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

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

Любое взаимодействие процессов или потоков связано с их синхронизацией, ко­торая заключается в согласовании их скоростей путем приостановки потока До наступления некоторого события и последующей его активизации при наступ­лении этого события. Синхронизация лежит в основе любого взаимодействия потоков, связано ли это взаимодействие с разделением ресурсов или с обменом данными. Например, поток-получатель должен обращаться за данными только после того, как они помещены в буфер потоком-отправителем. Если же поток-получатель обратился к данным до момента их поступления в буфер, то он дол­жен быть приостановлен.

При совместном использовании аппаратных ресурсов синхронизация также со­вершенно необходима. Когда, например, активному потоку требуется доступ к последовательному порту, а с этим портом в монопольном режиме работает дру­гой поток, находящийся в данный момент в состоянии ожидания, то ОС приос­танавливает активный поток и не активизирует его до тех пор, пока нужный ему порт не освободится. Часто нужна также синхронизация с событиями, внешними

по отношению к вычислительной системе, например реакции на нажатие комби­нации клавиш Ctrl+C.

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

Для синхронизации потоков прикладных программ программист может исполь­зовать как собственные средства и приемы синхронизации, так и средства опера­ционной системы. Например, два потока одного прикладного процесса могут ко­ординировать свою работу с помощью доступной для них обоих глобальной логической переменной, которая устанавливается в единицу при осуществлении некоторого события, например выработки одним потоком данных, нужных для продолжения работы другого. Однако во многих случаях более эффективными или даже единственно возможными являются средства синхронизации, предос­тавляемые операционной системой в форме системных вызовов. Так, потоки, принадлежащие разным процессам, не имеют возможности вмешиваться каким-либо образом в работу друг друга. Без посредничества операционной системы они не могут приостановить друг друга или оповестить о произошедшем собы­тии. Средства синхронизации используются операционной системой не только для синхронизации прикладных процессов, но и для ее внутренних нужд.

Обычно разработчики операционных систем предоставляют в распоряжение при­кладных и системных программистов широкий спектр средств синхронизации. Эти средства могут образовывать иерархию, когда на основе более простых средств строятся более сложные, а также быть функционально специализированными, например средства для синхронизации потоков одного процесса, средства для синхронизации потоков разных процессов при обмене данными и т. д. Часто функциональные возможности разных системных вызовов синхронизации пере­крываются, так что для решения одной задачи программист может воспользо­ваться несколькими вызовами в зависимости от своих личных предпочтений.

 

Необходимость синхронизации и гонки

 

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

1.   Считать из файла базы данных в буфер запись о клиенте с заданным иденти­фикатором.

2.   Внести новое значение в поле Заказ (для потока А) или Оплата (для потока В).

3. Вернуть модифицированную запись в файл базы данных.

 

Обозначим соответствующие шаги для потока А как Al, A2 и A3, а для потока В как Bl, В2 и ВЗ. Предположим, что в некоторый момент поток А обновляет поле Заказ записи о клиенте N. Для этого он считывает эту запись, в свой буфер (шаг А1), модифицирует значение поля Заказ (шаг А2), но внести запись в базу данных (шаг A3) не успевает, так как его выполнение прерывается, например, вследствие завершения кванта времени.

Предположим также, что потоку В также потребовалось внести сведения об оп­лате относительно того же клиента N. Когда подходит очередь потока В, он успе­вает считать запись в свой буфер (шаг В1) и выполнить обновление поля Оплата (шаг В2), а затем прерывается. Заметим, что в буфере у потока В находится за­пись о клиенте N, в которой поле Заказ имеет прежнее, не измененное значение.

Когда в очередной раз управление будет передано потоку А, то он, продолжая свою работу, запишет запись о клиенте N с модифицированным полем Заказ в базу данных (шаг A3). После прерывания потока А и активизации потока В по­следний запишет в базу данных поверх только что обновленной записи о клиен­те N свой вариант записи, в которой обновлено значение поля Оплата. Таким об­разом, в базе данных будут зафиксированы сведения о том, что клиент N произвел оплату, но информация о его заказе окажется потерянной (рис. 4.17, а).

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

Критическая секция

 

Важным понятием синхронизации потоков является понятие «критической сек­ции» программы. Критическая секция ~ это часть программы, результат выпол­нения которой может непредсказуемо меняться, если переменные, относящиеся к этой части программы, изменяются другими потоками в то время, когда вы­полнение этой части еще не завершено. Критическая секция всегда определяется по отношению к определенным критическим данным, при несогласованном из­менении которых могут возникнуть нежелательные эффекты. В предыдущем при­мере такими критическими данными являлись записи файла базы данных. Во всех потоках, работающих с критическими данными, должна быть определена критическая секция. Заметим, что в разных потоках критическая секция состоит в общем случае из разных последовательностей команд.

Чтобы исключить эффект гонок по отношению к критическим данным, необхо­димо обеспечить, чтобы в каждый момент времени в критической секции, свя­занной с этими данными, находился только один поток. При этом неважно, нахо­дится этот поток в активном или в приостановленном состоянии. Этот прием называют взаимным исключением. Операционная система использует разные способы реализации взаимного исключения. Некоторые способы пригодны для взаимного исключения при вхождении в критическую секцию только потоков

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

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

 

Блокирующие переменные

 

Для синхронизации потоков одного процесса прикладной программист может использовать глобальные блокирующие переменные. С этими переменными, к ко­торым все потоки процесса имеют прямой доступ, программист работает, не об­ращаясь к системным вызовам ОС.

 

Каждому набору критических данных ставится в соответствие двоичная пере­менная, которой поток присваивает значение 0, когда он входит в критическую секцию, и значение 1, когда он ее покидает. На рис. 4.18 показан фрагмент алго­ритма потока, использующего для реализации взаимного исключения доступа к критическим данным D блокирующую переменную F(D). Перед входом в критическую секцию поток проверяет, не работает ли уже какой-нибудь поток с дан­ными D. Если переменная F(D) установлена в 0, то данные заняты и проверка циклически повторяется. Если же данные свободны (F(D) = 1), то значение пере­менной F(D) устанавливается в 0 и поток входит в критическую секцию. После того как поток выполнит все действия с данными D, значение переменной F(D) снова устанавливается равным 1.

Блокирующие переменные могут использоваться не только при доступе к разде­ляемым данным, но и при доступе к разделяемым ресурсам любого вида.

Если все потоки написаны с учетом вышеописанных соглашений, то взаимное исключение гарантируется. При этом потоки могут быть прерваны операци­онной системой в любой момент и в любом месте, в том числе в критической секции.

Однако следует заметить, что одно ограничение на прерывания все же имеется. Нельзя прерывать поток между выполнением операций проверки и установки блокирующей переменной. Поясним это. Пусть в результате проверки перемен­ной поток определил, что ресурс свободен, но сразу после этого, не успев уста­новить переменную в 0, был прерван. За время его приостановки другой поток занял ресурс, вошел в свою критическую секцию, но также был прерван, не за­вершив работы с разделяемым ресурсом. Когда управление было возвращено первому потоку, он, считая ресурс свободным, установил признак занятости и начал выполнять свою критическую секцию. Таким образом, был нарушен прин­цип взаимного исключения, что потенциально может привести к нежелательным последствиям. Во избежание таких ситуаций в системе команд многих компью­теров предусмотрена единая, неделимая команда анализа и присвоения значения логической переменной (например, команды ВТС, BTR и BTS процессора Pentium). При отсутствии такой команды в процессоре соответствующие действия должны реализовываться специальными системными примитивами, которые бы запре­щали прерывания на протяжении всей операции проверки и установки.

Реализация взаимного исключения описанным выше способом имеет существен­ный недостаток: в течение времени, когда один поток находится в критической секции, другой поток, которому требуется тот же ресурс, получив доступ к про­цессору, будет непрерывно опрашивать блокирующую переменную, бесполезно тратя выделяемое ему процессорное время, которое могло бы быть использовано для выполнения какого-нибудь другого потока. Для устранения этого недостат­ка во многих ОС предусматриваются специальные системные вызовы для рабо­ты с критическими секциями.

На рис. 4.19 показано, как с помощью этих функций реализовано взаимное ис­ключение в операционной системе Windows NT. Перед тем как начать измене­ние критических данных, поток выполняет системный вызов Enter Critical Section (). В рамках этого вызова сначала выполняется, как и в предыдущем случае, проверка блокирующей переменной, отражающей состояние критического ресур­са. Если системный вызов определил, что ресурс занят (F(D) = 0), он в отличие от предыдущего случая не выполняет циклический опрос, а переводит поток в состояние ожидания (D) и делает отметку о том, что данный поток должен быть активизирован, когда соответствующий ресурс освободится. Поток, который в это время использует данный ресурс, после выхода из критической секции дол­жен выполнить системную функцию Leave Critical Section (), в результате чего блокирующая переменная принимает значение, соответствующее свободному состоянию ресурса (F(D) = 1), а операционная система просматривает очередь ожидающих этот ресурс потоков и переводит первый поток из очереди в состоя­ние готовности.

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

 

 

 

Семафоры

 

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

Для работы с семафорами вводятся два примитива, традиционно обозначаемых Р и V. Пусть переменная S представляет собой семафор. Тогда действия V(S) и P(S) определяются следующим образом.

□   V(S): переменная S увеличивается на 1 единым действием. Выборка, наращивание и запоминание не могут быть прерваны. К переменной S нет доступа другим потокам во время выполнения этой операции.

□   P(S): уменьшение S на 1, если это возможно. Если S=0 и невозможно умень­шить S, оставаясь в области целых неотрицательных значений, то в этом случае поток, вызывающий операцию Р, ждет, пока это уменьшение станет возможным. Успешная проверка и уменьшение также являются неделимой операцией.

Никакие прерывания во время выполнения примитивов V и Р недопустимы.

В частном случае, когда семафор S может принимать только значения 0 и 1, он превращается в блокирующую переменную, которую по этой причине часто на­зывают двоичным семафором. Операция Р заключает в себе потенциальную воз­можность перехода потока, который ее выполняет, в состояние ожидания, в то время как операция V может при некоторых обстоятельствах активизировать дру­гой поток, приостановленный операцией Р.

Рассмотрим использование семафоров на классическом примере взаимодействия двух выполняющихся в режиме мультипрограммирования потоков, один из ко­торых пишет данные в буферный пул, а другой считывает их из буферного пула. Пусть буферный пул состоит из N буферов, каждый из которых может содержать одну запись. В общем случае поток-писатель и поток-читатель могут иметь раз­личные скорости и обращаться к буферному пулу с переменой интенсивностью. В один период скорость записи может превышать скорость чтения, в другой — наоборот. Для правильной совместной работы поток-писатель должен приоста­навливаться, когда все буферы оказываются занятыми, и активизироваться при освобождении хотя бы одного буфера. Напротив, поток-читатель должен приос­танавливаться, когда все буферы пусты, и активизироваться при появлении хотя бы одной записи.

Введем два семафора: е — число пустых буферов, и f — число заполненных буфе­ров, причем в исходном состоянии е = N, a f = 0. Тогда работа потоков с общим буферным пулом может быть описана следующим образом (рис. 4.20).

Поток-писатель прежде всего выполняет операцию Р(е), с помощью которой он проверяет, имеются ли в буферном пуле незаполненные буферы. В соответствии с семантикой операции Р, если семафор е равен 0 (то есть свободных буферов в данный момент нет), то поток-писатель переходит в состояние ожидания. Если же значением е является положительное число, то он уменьшает число свободных буферов, записывает данные в очередной свободный буфер и после этого наращи­вает число занятых буферов операцией V(f). Поток-читатель действует анало­гичным образом, с той разницей, что он начинает работу с проверки наличия заполненных буферов, а после чтения данных наращивает количество свободных буферов.

 

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

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

Семафор может использоваться и в качестве блокирующей переменной. В рас­смотренном выше примере, для того чтобы исключить коллизии при работе с разделяемой областью памяти, будем считать, что запись в буфер и считывание из буфера являются критическими секциями. Взаимное исключение будем обес­печивать с помощью двоичного семафора b (рис. 4.21). Оба потока после провер­ки доступности буферов должны выполнить проверку доступности критической секции.

 

Тупики

 

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

Итак, пусть поток-писатель начинает свою работу с проверки доступности кри­тической секции — операции Р(b), и пусть он первым войдет в критическую сек­цию. Выполняя операцию Р(е), он может обнаружить отсутствие свободных бу­феров и перейти в состояние ожидания. Как уже было показано, из этого состоя­ния его может вывести только поток-читатель, который возьмет очередную за­пись из буфера. Но поток-читатель не сможет этого сделать, так как для этого ему потребуется войти в критическую секцию, вход в которую заблокирован по­током-писателем. Таким образом, ни один из этих потоков не может завершить начатую работу и возникнет тупиковая ситуация, которая не может разрешиться без внешнего воздействия.

Рассмотрим еще один пример тупика. Пусть двум потокам, принадлежащим разным процессам и выполняющимся в режиме мультипрограммирования, для выполнения их работы нужно два ресурса, например принтер и последовательный порт. Такая ситуация может возникнуть, например, во время работы приложения, задачей кото­рого является распечатка информации, поступающей по модемной связи.

На рис. 4.22, а показаны фрагменты соответствующих программ. Поток А запра­шивает сначала принтер, а затем порт, а поток В запрашивает устройства в обрат­ном порядке. Предположим, что после того, как ОС назначила принтер потоку А и установила связанную с этим ресурсом блокирующую переменную, поток А был прерван. Управление получил поток В, который сначала выполнил запрос на получение СОМ-порта, затем при выполнении следующей команды был забло­кирован, так как принтер оказался уже занятым потоком А. Управление снова по­лучил поток А, который в соответствии со своей программой сделал попытку за­нять порт и был заблокирован, поскольку порт уже выделен потоку В. В таком положении потоки А и В могут находиться сколь угодно долго.

В зависимости от соотношения скоростей потоков они могут либо взаимно бло­кировать друг друга (рис. 4.22, б), либо образовывать очереди к разделяемым ре­сурсам (рис. 4.22, в), либо совершенно независимо использовать разделяемые ре­сурсы (рис. 4.22, г).

 

ПРИМЕЧАНИЕ ----------------------------------------------------------------------------------------------

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

 

В рассмотренных примерах тупик был образован двумя потоками, но взаимно блокировать друг друга может и большее число потоков. На рис. 2.23 показано такое распределение ресурсов Ri между несколькими потоками Tj, которое при­вело к возникновению взаимных блокировок. Стрелки обозначают потребность потока в ресурсах. Сплошная стрелка означает, что соответствующий ресурс был выделен потоку, а пунктирная стрелка соединяет поток с тем ресурсом, который необходим, но не может быть пока выделен, поскольку занят другим потоком. Например, потоку Т1 для выполнения работы необходимы ресурсы R1 и R2, из ко­торых выделен только один — R1, а ресурс R2 удерживается потоком Т2. Ни один из четырех показанных на рисунке потоков не может продолжить свою работу, так как не имеет всех необходимых для этого ресурсов.

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

 

 

 

Тупики могут быть предотвращены на стадии написания программ, то есть про­граммы должны быть написаны таким образом, чтобы тупик не мог возникнуть

при любом соотношении взаимных скоростей потоков. Так, если бы в примере,      показанном на рис. 4.22, поток А и поток В запрашивали ресурсы в одинаковой последовательности, то тупик был бы в принципе невозможен. Другой, более гибкий подход к предотвращению тупиков заключается в том, что ОС каждый раз при запуске задач анализирует их потребности в ресурсах и определяет, может ли в данной мультипрограммной смеси возникнуть тупик. Если да, то запуск новой задачи временно откладывается. ОС может также использовать опре­деленные правила при назначении ресурсов потокам, например, ресурсы могут выделяться операционной системой в определенной последовательности, общей для всех потоков.

В тех же случаях, когда тупиковую ситуацию не удалось предотвратить, важно быстро и точно ее распознать, поскольку блокированные потоки не выполняют никакой полезной работы. Если тупиковая ситуация образована множеством по­токов, занимающих массу ресурсов, распознавание тупика является нетривиаль­ной задачей. Существуют формальные, программно-реализованные методы рас­познавания тупиков, основанные на ведении таблиц распределения ресурсов и таблиц запросов к занятым ресурсам. Анализ этих таблиц позволяет обнаружить взаимные блокировки.

Если же тупиковая ситуация возникла, то не обязательно снимать с выполнения все заблокированные потоки. Можно снять только часть из них, освободив ресурсы, ожидаемые остальными потоками, можно вернуть некоторые потоки в область под­качки, можно совершить «откат» некоторых потоков до так называемой контроль­ной точки, в которой запоминается вся информация, необходимая для восстановле­ния выполнения программы с данного места. Контрольные точки расставляются в программе в тех местах, после которых возможно возникновение тупика.

 

Синхронизирующие объекты ОС

 

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

Примерами таких синхронизирующих объектов ОС являются системные сема­форы, мьютексы, события, таймеры и другие — их набор зависит от конкретной ОС, которая создает эти объекты по запросам процессов. Чтобы процессы могли разделять синхронизирующие объекты, в разных ОС используются разные методы. Некоторые ОС возвращают указатель на объект. Этот указатель может быть до­ступен всем родственным процессам, наследующим характеристики общего ро­дительского процесса. В других ОС процессы в запросах на создание объектов синхронизации указывают имена, которые должны быть им присвоены. Далее эти имена используются разными процессами для манипуляций объектами син­хронизации. В таком случае работа с синхронизирующими объектами подобна ра­боте с файлами. Их можно создавать, открывать, закрывать, уничтожать.

Кроме того, для синхронизации могут быть использованы такие «обычные» объ­екты ОС, как файлы, процессы и потоки. Все эти объекты могут находиться в двух состояниях: сигнальном и несигнальном — свободном. Для каждого объекта смысл, вкладываемый в понятие «сигнальное состояние», зависит от типа объек­та. Так, например, поток переходит" в сигнальное состояние тогда, когда он завер­шается. Процесс переходит в сигнальное состояние тогда, когда завершаются все его потоки. Файл переходит в сигнальное состояние в том случае, когда заверша­ется операция ввода-вывода для этого файла. Для остальных объектов сигнальное состояние устанавливается в результате выполнения специальных системных вы­зовов. Приостановка и активизация потоков осуществляются в зависимости от состояния синхронизирующих объектов ОС.

Потоки с помощью специального системного вызова сообщают операционной сис­теме о том, что они хотят синхронизировать свое выполнение с состоянием неко­торого объекта. Будем далее называть этот системный вызов Wait(X), где X — ука­затель на объект синхронизации. Системный вызов, с помощью которого поток может перевести объект синхронизации в сигнальное состояние, назовем Set(X).

Поток, выполнивший системный вызов Wait(X), переводится операционной сис­темой в состояние ожидания до тех пор, пока объект X не перейдет в сигнальное состояние. Примерами системных вызовов типа Wait() и Set () являются вызовы Wait For Single object() и Set Event() в Windows NT, Dos Sem Wait() и Dos Sem Set() в OS/2, sieep() и wakeup() в UNIX.

Поток может ожидать установки сигнального состояния не одного объекта, а не­скольких. При этом поток может попросить ОС активизировать его при установ­ке либо одного из указанных объектов, либо всех объектов. Поток может в каче­стве аргумента системного вызова Wait() указать также максимальное время, которое он будет ожидать перехода объекта в сигнальное состояние, после чего ОС должна его активизировать в любом случае. Может случиться, что установки некоторого объекта в сигнальное состояние ожидают сразу несколько потоков. В зависимости от объекта синхронизации в состояние готовности могут перево­диться либо все ожидающие это событие потоки, либо один из них.

Синхронизация тесно связана с планированием потоков. Во-первых, любое об­ращение потока с системным вызовом Wait(X) влечет за собой действия в под­системе планирования — этот поток снимается с выполнения и помещается в очередь ожидающих потоков, а из очереди готовых потоков выбирается и акти­визируется новый поток. Во-вторых, при переходе объекта в сигнальное состоя­ние (в результате выполнения некоторого потока — либо системного, либо при­кладного) ожидающий этот объект поток (или потоки) переводится в очередь готовых к выполнению потоков. В обоих случаях осуществляется перепланиро­вание потоков, при этом если в ОС предусмотрены изменяемые приоритеты и/или кванты времени, то они пересчитываются по правилам, принятым в этой опера­ционной системе.

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

Пусть программа приложения построена так, что для выполнения запросов, по­ступающих из сети, основной поток создает вспомогательные серверные потоки.

При поступлении от пользователя команды завершения приложения основной поток должен дождаться завершения всех серверных потоков и только после этого завершиться сам. Следовательно, процедура завершения должна включать вызов Wait(XI, Х2, ...), где XI, Х2 — указатели на серверные потоки. В результате выпол­нения данного системного вызова основной поток будет переведен в состояние ожидания и останется в нем до тех пор, пока все серверные потоки не перейдут в сигнальное состояние, то есть завершатся. После этого ОС переведет основной поток состояние готовности. При получении доступа к процессору основной поток завершится.

Другой пример. Пусть выполнение некоторого приложения требует последова­тельных работ-этапов. Для каждого этапа имеется свой отдельный процесс. Сиг­налом для начала работы каждого следующего процесса является завершение предыдущего. Для реализации такой логики работы необходимо в каждом про­цессе, кроме первого, предусмотреть выполнение системного вызова Wait(X), в котором синхронизирующим объектом является предшествующий поток. Объект-файл, переход которого в сигнальное состояние соответствует заверше­нию операции ввода-вывода с этим файлом, используется в тех случаях, когда поток, инициировавший эту операцию, решает дождаться ее завершения, прежде чем продолжить свои вычисления.

Однако круг событий, с которыми потоку может потребоваться синхронизиро­вать свое выполнение, отнюдь не исчерпывается завершением потока, процесса или операции ввода-вывода. Поэтому в ОС, как правило, имеются и другие, бо­лее универсальные объекты синхронизации, такие как событие (event), мьютекс (mutex), системный семафор и другие.

Мьютекс, как и семафор, обычно используется для управления доступом к дан­ным.

В отличие от объектов-потоков, объектов-процессов и объектов-файлов, которые при переходе в сигнальное состояние переводят в состояние готовности все по­токи, ожидающие этого события, объект-мьютекс «освобождает» из очереди ожи­дающих только один поток.

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

Объект-событие (в данном случае слово «событие» используется в узком смыс­ле, как обозначение конкретного вида объектов синхронизации) обычно исполь­зуется не для доступа к данным, а для того, чтобы оповестить другие потоки о том, что некоторые действия завершены. Пусть, например, в некотором прило­жении работа организована таким образом, что один поток читает данные из файла в буфер памяти, а другие потоки обрабатывают эти данные, затем первый поток считывает новую порцию данных, а другие потоки снова ее обрабатывают и так далее. В начале работы первый поток устанавливает объект-событие в не­сигнальное состояние. Все остальные потоки выполнили вызов Wait(X), где X -указатель события, и находятся в Приостановленном состоянии, ожидая наступ­ления этого события. Как только буфер заполняется, первый поток сообщает об этом операционной системе, выполняя вызов Set(X). Операционная система просматривает очередь ожидающих потоков и активизирует все потоки, которые ждут этого события.

 

Сигналы

 

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

Примером асинхронного сигнала является сигнал с терминала. Во многих ОС предусматривается оперативное снятие процесса с выполнения. Для этого поль­зователь может нажать некоторую комбинацию клавиш (Ctrl+C, Ctrl+Break), в ре­зультате чего ОС вырабатывает сигнал и направляет его активному процессу. Сигнал может поступить в любой момент выполнения процесса (то есть он явля­ется асинхронным), требуя от процесса немедленного завершения работы. В дан­ном случае реакцией на сигнал является безусловное завершение процесса.

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

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

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