Ответы на вопросы по ОС -- экзамен "Операционные системы"

В АДЕКВАТНОМ ВИДЕ , МАТЕРИАЛ ПРИВЕДЁННЫЙ, НИЖЕ ПРЕДСТАВЛЕН ЗДЕСЬ

Операционные системы
Вопросы к экзамену

1. Процессы и потоки.
Основным понятием, связанным с операционными системами является процесс - абстрактное понятие, описывающее работу программы. Процессом по сути является программа в момент её выполнения. Все остальное базируется на этом понятии.

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

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

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

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

3. Создание процесса. Завершение процесса.
Создание процесса.
Основные события, приводящие к созданию процесса:
? Инициализация системы
? Выполнение изданного работающим процессом системного запроса на создание процесса
? Запрос пользователя на создание нового процесса
? Инициирование пакетного задания

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

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

В UNIX существует только один системный запрос, направленный на создание процесса: fork. Этот запрос создает дубликат вызываемого процесса. После выполнения запроса fork двум процессам - родительскому и дочернему - соответствуют одинаковые образы памяти, строки окружения и открытые файлы. Обычно, дочерний процесс выполняет системный вызов execve для изменения образа памяти и запуска новой программы.
В Windows вызов всего одной функции CreateProcess управляет и созданием процесса и запуском нужной в ней программы.

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

Завершение процесса
Основные события, приводящие к завершению процесса:
? Обычный выход (преднамеренно)
? Выход по ошибке (преднамеренно)
? Выход по неисправимой ошибке (непреднамеренно)
? Уничтожение другим процессом (непреднамеренно)

В основном, процессы завершаются по мере выполнения своей работы. После окончания компиляции программы, компилятор выполняет системный запрос, чтобы сообщить операционной системе о завершении работы. В UNIX этот запрос - exit, а в Windows - ExitProcess. Так же, причиной завершения процесса может служить выполнение другим процессом системного запроса на уничтожение процесса. В UNIX такой системный запрос - kill, а в Windows - TerminateProcess. В обоих случаях “киллер” должен обладать соответствующими правами к убиваемому процессу.

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

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

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

5. Состояния процесса.
Существуют три возможных состояния процесса:
? Работающий (в этот конкретный момент использующий процессор)
? Готовый к работе (процесс временно приостановлен, чтобы позволить выполниться другому процессу)
? Заблокированный (процесс не может быть запущен прежде, чем произойдет некое внешнее событие)

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

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

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

7. Контекст выполнения процесса.

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

Статическая часть контекста процесса системного уровня включает следующее:
A. Описатель процесса, т.е. элемент таблицы описателей существующих в системе процессов. Описатель процесса включает, в частности, следующую информацию:
? состояние процесса;
? физический адрес в основной или внешней памяти u-области процесса;
? идентификаторы пользователя, от имени которого запущен процесс;
? идентификатор процесса;
? прочую информацию, связанную с управлением процессом.

B. U-область (u-area), индивидуальная для каждого процесса область пространства ядра, обладающая тем свойством, что хотя u-область каждого процесса располагается в отдельном месте физической памяти, u-области всех процессов имеют один и тот же виртуальный адрес в адресном пространстве ядра. Именно это означает, что какая бы программа ядра не выполнялась, она всегда выполняется как ядерная часть некоторого пользовательского процесса, и именно того процесса, u-область которого является "видимой" для ядра в данный момент времени. U-область процесса содержит:
? указатель на описатель процесса;
? идентификаторы пользователя;
? счетчик времени, которое процесс реально выполнялся (т.е. занимал процессор) в режиме пользователя и режиме ядра;
? параметры системного вызова;
? результаты системного вызова;
? таблица дескрипторов открытых файлов;
? предельные размеры адресного пространства процесса;
? предельные размеры файла, в который процесс может писать;
? и т.д.

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

8. Регистры, счетчик команд, стек.

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

9. Виртуальное адресное пространство.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Недостатки:
? Проблема реализации блокирующих системных запросов.
? Ошибка из-за отсутствия страницы.
? При запуске одного потока ни один другой поток не будет запущен, пока первый поток добровольно не отдаст процессор.
? Программисты хотят использовать потоки именно в тех приложениях, в которых потоки часто блокируются, например в многопоточном web-cepвepe.

14. Реализация потоков в ядре.
Существует несколько способов реализации потоков: в пространстве пользователя и в ядре. Также существует и смешанная реализация.

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

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

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

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

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

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

16. Межпроцессное взаимодействие (IPC).
Процессам часто бывает необходимо взаимодействовать между собой. Например, в конвейере ядра выходные данные первого процесса должны передаваться второму процессы и т.д. по цепочке. Поэтому необходимо правильно организовать взаимодействие между процессами, по возможности не используя прерываний. Рассмотрим некоторые аспекты межпроцессного взаимодействия (IPC, interprocess communication).

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

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

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

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

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

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

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

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

Строгое чередование.

На рисунке целая переменная turn, изначально равная 0, отслеживает, чья очередь входить в критическую область. Вначале процесс 0 проверяет значение turn, считывает 0 и входит в критическую область. Процесс 1 также считывает значение turn, считывает 0 и после этого входит в цикл, непрерывно проверяя, когда значение turn будет равно 1. Постоянная проверка значения переменной в ожидании некоторого значения называется активным ожиданием. Данный способ является бесцельной тратой времени процессора. Активное ожидание используется только в случае, когда есть уверенность в небольшом времени ожидания. Блокировка, использующая активное ожидание, называется спин-блокировкой.
Когда процесс 0 покидает критическую область, он изменяет значение turn на 1, позволяя процессу 1 попасть в критическую область.
Фактически этот метод требует, чтобы два процесса попадали в критическую область строго по очереди. Не один из них не может попасть в критическую секцию (например, послать файл на печать) два раза подряд.

Алгоритм Петерсона.

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

Команда TSL.
Рассмотрим решение, требующее аппаратного обеспечения. Многие компьютеры, особенно разработанные с расчетом на несколько процессоров, имеют команду
TSL RX.LOCK
(Test and Set Lock), которая действует следующим образом. В регистр RX считывается содержимое слова памяти lock, а в ячейке памяти lock хранится некоторое ненулевое значение. Гарантируется, что операция считывания слова и сохранения неделима - другой процесс не может обратиться к слову в памяти, пока команда не выполнена. Процессор, выполняющий команду TSL, блокирует шину памяти, чтобы остальные процессоры не могли обратиться к памяти.
Рассмотрим пример использования команды TSL для взаимного исключения.

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

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

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

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

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

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

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

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

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

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

21. Мьютексы.
Иногда используется упрощенная версия семафора, называемая мьютексом (mutex, сокращение от mutual exclusion — взаимное исключение). Мьютекс не способен считать, он может лишь управлять взаимным исключением доступа к совместно используемым ресурсам или кодам. Реализация мьютекса проста и эффективна, что делает использование мьютексов особенно полезным в случае потоков, действующих только в пространстве пользователя.

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

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

22. Мониторы.
Чтобы упростить написание программ, в 1974 году Хоар (Ноаге) [155] и Бринч Хансен (Brinch Hansen) [43] предложили примитив синхронизации более высо­кого уровня, называемый монитором. Их предложения несколько отличались друг от друга, как мы увидим дальше. Монитор — набор процедур, переменных и дру­гих структур данных, объединенных в особый модуль или пакет. Процессы могут вызывать процедуры монитора, но у процедур, объявленных вне монитора, нет прямого доступа к внутренним структурам данных монитора.

Реализации взаимных исключений способствует важное свойство монитора: при обращении к монитору в любой момент времени активным может быть только один процесс. Мониторы являются структурным компонентом языка программирования, поэтому компилятор знает, что обрабатывать вызовы процедур монитора следует иначе, чем вызовы остальных процедур. Обычно при вызове процедуры монитора первые несколько команд процедуры проверяют, нет ли в мониторе активного процесса. Если активный процесс есть, вызывающему процессу придется подождать, в противном случае запрос удовлетворяется. Реализация взаимного исключения зависит от компилятора, но обычно используется мьютекс или бинарный семафор. Поскольку взаимное исключение обеспечивает компилятор, а не программист, вероятность ошибки гораздо меньше. В любом случае программист, пишущий код монитора, не должен задумываться о том, как компилятор организует взаимное исключение. Достаточно знать, что, обеспечив попадание в критические области через процедуры монитора, можно не
бояться попадания в критическую область двух процессов одновременно.
Хотя мониторы предоставляют простой способ реализации взаимного исключения, этого недостаточно. Необходим также способ блокировки процессов, которые не могут продолжать свою деятельность. В случае проблемы производителя и потребителя достаточно просто поместить все проверки буфера на заполненность
и пустоту в процедуры монитора, но как процесс заблокируется, обнаружив полный буфер?
Решение заключается во введении переменных состояния и двух операций, wait и signal. Когда процедура монитора обнаруживает, что она не в состоянии продолжать работу (например, производитель выясняет, что буфер заполнен), она выполняет операцию wait на какой-либо переменной состояния, скажем, full. Это приводит к блокировке вызывающего процесса и позволяет другому процессу войти в монитор.
Другой процесс, в нашем примере потребитель может активизировать ожидающего напарника, например, выполнив операцию signal на той переменной со­стояния, на которой он был заблокирован. Чтобы в мониторе не оказалось двух активных процессов одновременно, нам необходимо правило, определяющее по­следствия операции signal. Хоар предложил запуск «разбуженного» процесса и остановку второго. Бринч Хансен предложил другое решение: процесс, выполнивший signal, должен немедленно покинуть монитор. Иными словами, операция signal выполняется только в самом конце процедуры монитора. Мы будем использовать это решение, поскольку оно в принципе проще и к тому же легче в реализации.
Если операция signal выполнена на переменной, с которой связаны несколько за­блокированных процессов, планировщик выбирает и «оживляет» только один из них. Кроме этого, существует третье решение, не основывающееся на предположениях Хоара и Бринча Хансена: позволить процессу, выполнившему signal, продолжать работу и запустить ждущий процесс только после того, как первый процесс покинет монитор.
Переменные состояния не являются счетчиками. В отличие от семафоров они не аккумулируют сигналы, чтобы впоследствии воспользоваться ими. Это означает, что в случае выполнения операции signal на переменной состояния, с которой не связано ни одного блокированного процесса, сигнал будет утерян. Проще говоря, операция wait должна выполняться прежде, чем signal. Это правило существен­но упрощает реализацию. На практике это правило не создает проблем, поскольку отслеживать состояния процессов при необходимости не очень трудно. Процесс,

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

send(destination, &message);
receive(source, &message);

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

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

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

Решение, представленное в листинге, исключает взаимоблокировку и позволяет реализовать максимально возможный параллелизм для любого числа философов. Здесь используется массив state для отслеживания душевного состояния каждого философа: он либо ест, либо размышляет, либо голодает (пытаясь получить вилки). Философ может начать есть, только если ни один из его соседей не ест. Соседи философа с номером i определяются макросами LEFT и RIGHT (то есть если i = 2, то LEFT= 1 и RIGHT = 3).

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

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

В предлагаемом решении используются три семафора: customers, для подсчета ожидающих посетителей (клиент, сидящий в кресле брадобрея, не учитывается); barbers, количество брадобреев (0 или 1), простаивающих в ожидании клиента, и mutex для реализации взаимного исключения. Также используется переменная waiting, предназначенная для подсчета ожидающих посетителей.

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

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

Планирование - это разделение вычислительных ресурсов системы между процессами и потоками.

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

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

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


В АДЕКВАТНОМ ВИДЕ , МАТЕРИАЛ ПРИВЕДЁННЫЙ НИЖЕ ПРЕДСТАВЛЕН ЗДЕСЬ

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

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

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

В различных средах требуются различные алгоритмы планирования. Это связано с тем, что различные операционные системы и различные приложения ориентированы на разные задачи. Другими словами, то, для чего следует оптимизировать планировщик, различно в разных системах. Можно выделить три среды:
1. Системы пакетной обработки данных.
2. Интерактивные системы.
3. Системы реального времени.

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

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

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

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

Основным преимуществом этого алгоритма является то, что его легко понять и столь же легко программировать.

Недостатком является абсолютная неоптимизированность планирования.

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

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

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

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

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

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

Планировщик памяти периодически просматривает процессы, находящиеся на диске, чтобы решить, какой из них переместить в память. Среди критериев, используемых планировщиком, есть следующие:
1. Сколько времени прошло с тех пор, как процесс был выгружен на диск или загружен с диска?
2. Сколько времени процесс уже использовал процессор?
3. Каков размер процесса (маленькие процессы не мешают)?
4. Какова важность процесса?

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

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

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

Несколько очередей.
Один из первых приоритетных планировщиков был реализован в системе CTSS (compatible time-shared system — совместимая система с разделением времени).Основной проблемой системы CTSS было слишком медленное переключе­ние процессов, поскольку в памяти компьютера IBM 7094 мог находиться только один процесс. Каждое переключение означало выгрузку текущего процесса на диск
и считывание нового процесса с диска. Разработчики CTSS быстро сообразили, что эффективность будет выше, если процессам, ограниченным возможностями про­цессора, выделять больший квант времени, чем если предоставлять им небольшие кванты, но часто. С одной стороны, это уменьшит количество перекачек из памяти на диск, а с другой — приведет к ухудшению времени отклика, как мы уже видели.

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

В качестве примера рассмотрим процесс, которому необходимо производить вычисления в течение 100 квантов. Вначале ему будет предоставлен один квант, затем он будет перекачан на диск. В следующий раз ему достанется 2 кванта, за­тем 4, 8,16, 32, 64, хотя из 64 он использует только 37. В этом случае понадобится всего 7 перекачек (включая первоначальную загрузку) вместо 100, которые пона­добились бы при использовании циклического алгоритма. Помимо этого, по мере погружения в очереди приоритетов процесс будет все реже запускаться, предо­ставляя процессор более коротким процессам.

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

Один из методов основывается на оценке длины процесса, базирующейся на предыдущем поведении процесса. При этом запускается процесс, у которого оце­ненное время самое маленькое. Допустим, что предполагаемое время исполнения команды равно Т0 и предполагаемое время следующего запуска равно Т1. Можно улучшить оценку времени, взяв взвешенную сумму этих времен аТ0 + (1 - а)Т1. Выбирая соответствующее значение а, мы можем заставить алгоритм оценки быс­тро забывать о предыдущих запусках или, наоборот, помнить о них в течение дол­гого времени. Взяв а = 1/2, мы получим серию оценок:

Т0, Т0/2 + Т1/2, Т0/4 + Т1/4 + Т2/ 2, Т0/ 8 + Т1/8 + Т2/4 + T3/ 2.

После трех запусков вес Т0 в оценке уменьшится до 1/8.

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


В АДЕКВАТНОМ ВИДЕ , МАТЕРИАЛ ПРИВЕДЁННЫЙ НИЖЕ ПРЕДСТАВЛЕН ЗДЕСЬ


Гарантированное планирование.
Принципиально другим подходом к планированию является предоставление пользователям реальных обещаний и затем их выполнение. Вот одно обещание, которое легко произнести и легко выполнить: если вместе с вами процессором пользуются n пользователей, вам будет предоставлено 1 / n мощности процессора.

И в системе с одним пользователем и n запущенными процессорами каждому достанется 1 / n циклов процессора.
Чтобы выполнить это обещание, система должна отслеживать распределение процессора между процессами с момента создания каждого процесса. Затем система рассчитывает количество ресурсов процессора, на которое процесс имеет право, например время с момента создания, деленное на n. Теперь можно сосчитать отношение времени, предоставленного процессу, к времени, на которое он имеет право. Полученное значение 0,5 означает, что процессу выделили только полови­ну положенного, а 2,0 означает, что процессу досталось в два раза больше, чем положено. Затем запускается процесс, у которого это отношение наименьшее, пока
оно не станет больше, чем у его ближайшего соседа.

Лотерейное планирование.
В основе алгоритма лежит раздача процессам лотерейных билетов на доступ к различным ресурсам, в том числе и к процессору. Когда планировщику необходимо принять решение, выбирается случайным образом лотерейный билет, и его обладатель получает доступ к ресурсу. Что касается доступа к процессору, «лотерея» может происходить 50 раз в секунду, и победитель получает 20 мс времени процессора.

Более важным процессам можно раздать дополнительные билеты, чтобы увеличить вероятность выигрыша. Если всего 100 билетов и 20 из них находятся у одного процесса, то ему достанется 20 % времени процессора. В отличие от приоритетного планировщика, в котором очень трудно оценить, что означает, скажем, приоритет 40, в лотерейном планировании все очевидно. Каждый процесс получит процент ресурсов, примерно равный проценту имеющихся у него билетов.

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

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

Справедливое планирование.
До сих пор мы предполагали, что каждый процесс управляется независимо от того, кто его хозяин. Поэтому если пользователь 1 создаст 9 процессов, а пользователь 2 — 1 процесс, то с использованием циклического планирования или в случае равных приоритетов пользователю 1 достанется 90 % процессора, а пользователю 2 всего 10.

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

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

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

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

30. Взаимоблокировки. Условия взаимоблокировки.
Часто для выполнения прикладных задач процесс нуждается в исключительном доступе не к одному, а к нескольким ресурсам. Предположим, например, что каждый из двух процессов хочет записать отсканированный документ на компакт- диск. Процесс А запрашивает разрешение на использование сканера и получает его. Процесс В запрограммирован по-другому, поэтому сначала запрашивает устройство для записи компакт-дисков и также получает его. Затем процесс А обращается к устройству для записи компакт-дисков, но запрос отклоняется до тех пор, пока это устройство занято процессом В. К сожалению, вместо того чтобы освободить устройство для записи компакт-дисков, В запрашивает сканер. В этот момент процессы заблокированы и будут вечно оставаться в этом состоянии. Такая ситуация называется тупиком, тупиковой ситуацией или взаимоблокировкой.

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

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

Последовательность событий, необходимых для использования ресурса, представлена ниже в абстрактной форме.
1. Запрос ресурса.
2. Использование ресурса.
3. Возврат ресурса

Взаимоблокировку можно определить следующим образом:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

32. Управление памятью. Подкачка.

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

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

Работа системы свопинга проиллюстрирована на рис. 4.5. На начальной стадии в памяти находится только процесс А. Затем создаются или загружаются с диска процессы В и С. На рис. 4.5, г процесс Л выгружается на диск. Затем появляется процесс D, а процесс В завершается. Наконец, процесс А снова возвращается
в память. Так как теперь процесс А имеет другое размещение в памяти, его адреса должны быть перенастроены или программно во время загрузки в память, или (более заманчивый вариант) аппаратно во время выполнения программы.

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

33. Управление памятью с помощью битовых массивов.
Если память выделяется динамически, этим процессом должна управлять операционная система. При работе с битовым массивом память разделяется на единичные блоки размещения размером от нескольких слов до нескольких килобайт. В битовой карте каждому свободному блоку соответствует один бит, равный нулю, а каждому занятому блоку — бит, установленный в 1 (или наоборот).

На рисунке показана часть памяти и соответствующий ей битовый массив. Черточками отмечены единичные блоки памяти. Заштрихованные области (0 в битовой карте) свободны.

Размер единичного блока представляет собой важный вопрос стадии разработки системы. Чем меньше единичный блок, тем больше потребуется битовый массив. Однако даже при маленьком единичном блоке, равном четырем байтам, для 32 битов памяти потребуется 1 бит в карте.

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

34. Управление памятью с помощью связных списков.

Память, показанная на рис. 4.7, а, представлена в виде связного списка сегментов на рис. 4.7, в. Каждая запись в списке указывает, является ли область памяти свободной (Н, от hole — дыра) или занятой процессом (Р, process); адрес, с которого начинается эта область; ее длину; содержит указатель на следующую запись.

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

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

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

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

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


В АДЕКВАТНОМ ВИДЕ , МАТЕРИАЛ ПРИВЕДЁННЫЙ НИЖЕ ПРЕДСТАВЛЕН ЗДЕСЬ