Лекции
Лабораторные работы
Зачет
Экзамен

 

ЛИТЕРАТУРА

Основная

1

Шоу А. Логическое проектирование операционных систем: Пер. с англ. – М.: Мир, 1981.

2

Дейтл Г. Введение в операционные системы. В 2-х т. Пер. с англ. - М.: Мир, 1987.

3

Краковяк С. Основы организации и функционирования ОС ЭВМ. Пер. с франц. - М.: Мир, 1988.

4

Робачевский А. М. Операционная система UNIX. – СПб.: BHV – Санкт-Петербург, 1997.

5

Вильямс А. Системное программирование в Windows 2000 для профессионалов – СПб.: Питер, 2001.

6

Олифер В. Г., Олифер Н. А. Сетевые операционные системы. – СПб.: Питер, 2001.

7

Гордеев А. В. Молчанов А. Ю. Системное программное обеспечение. – СПб.: Питер, 2002.

8

Харт Д. М. Системное программирование в среде Windows, 3-е изд.: Пер. с англ.: - М.: Издательский дом «Вильямс», 2005.

 

Дополнительная

1

Сидельников В. В., Широков В. В. Управление процессами в программных средах АСОИУ. Учеб. Пособие/ГЭТУ. - С.-Пб., 1994.

2

Методические указания к лабораторным работам по дисциплине "Операционные среды АСОИУ" / Сост.: В. В. Сидельников, В. В. Широков; ГЭТУ. СПб., 1997.


Раздел 1. Общая характеристика операционных систем
1.1. Характеристика операционной среды как 4-х уровневой архитектуры
Каково традиционное понимание различия между операционной средой и операционной системой:
 
1.      операционная система - это программа, которая загружается при включении ПК;
2.      операционная среда - это обычная программа, но, берущая на себя после загрузки большинство функций операционной системы. 
 
Таким, например, долгое время было взаимоотношение между DOS и Windows. И долгое время не затихали споры о том, операционной средой или операционной системой были первые версии Windows.
Учитывая, что на нашей специальности готовятся разработчики программного обеспечения, считающегося скорее прикладным, чем системным, то мы не будем вдаваться в вышеупомянутые споры, а дадим свое собственное определение операционной среды, с которым можно было бы работать в течение курса.
Будем называть операционной средой АСОИУ совокупность аппаратных и программных средств, обеспечивающих выполнение требуемых прикладных функций АСОИУ.
Сами прикладные функции я бы разделил на два уровня:
1. нижний уровень - это собственно прикладная функция, такая как, например, управление объектом, ведение базы данных, разработка программного обеспечения, передача и прием информации и т.д.;
2. верхний уровень - набор сервисных функций, повышающих качество и удобство выполнения первых.
Примеры: NORTON/DOS; IDE/(редактор, транслятор, компоновщик); DBASE/база данных.
Отсюда как бы формируется двухуровневая архитектура операционной среды:
 
Оболочка (пользовательский интерфейс)
Прикладная задача
 
Это самая общая схема, которую целесообразно уточнить, добавив снизу еще два уровня в соответствие с нашим определением операционной среды:
 
Оболочка (пользовательский интерфейс)
Прикладная задача
Операционная система
Аппаратура
 
Совокупность приведенных четырех уровней и будем рассматривать как операционную среду. Т.е. говоря об операционной среде, мы должны рассматривать все четыре уровня. Но мы не будем в курсе этого делать, а только выхватим некоторые уровни, но сначала дадим краткую характеристику тенденций развития каждого уровня в контексте наших интересов.
Уровень аппаратуры
Подробное рассмотрение этого уровня проводится в других дисциплинах, отметим только, что развитие аппаратных средств компьютеров идет гигантскими темпами в направлениях увеличения разрядности обрабатываемых данных, тактовой частоты процессора размеров внутреннего КЭШа и т.д. Но среди этого в определенном смысле количественного роста характеристик аппаратных средств компьютера хотелось бы выделить один качественный скачек. Это переход от процессора 8086 к процессору 80286, аппаратно поддерживающему многозадачность и управление виртуальной памятью. Вот эта самая концепция многозадачности будет красной нитью проходить через весь курс, через призму многозадачности мы будем рассматривать все разделы курса.
 

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

Любую прикладную задачу можно представить подобным образом. На основе анализа практических примеров, можно дать следующую характеристику особенностей данного уровня:
1)      наличие связи с аппаратурой объекта, т.е. важным аспектом рассмотрения должно стать изучение всевозможных датчиков и способов ввода информации в машину; на первое место здесь выходит знакомство с системой прерываний компьютера;
2)      многофункциональность объекта приводит к тому, что вычислительная система, управляющая им, должна быть многозадачной;
3)      наличие, кроме основной функции, множества дополнительных сервисных функций;
4)      унификация архитектуры прикладной программы, т.е. среда предоставляет программисту интерфейс для доступа к ресурсам машины (API - application programming interface), только через который и можно работать. Обойти этот интерфейс становится все сложнее для развитых ОС. Однако цель этого интерфейса благородная - облегчить переносимость приложения в различные аппаратные и операционные среды.
 
Оболочка - пользовательский интерфейс
Четвертый - верхний - уровень операционной среды характеризуется следующими тенденциями:
·         совершенствование интерфейса пользователя, например:
1)      интерпретатор команд, например в DOS или в dBASE;
2)      оконное меню в текстовом режиме, например, Norton Commander;
3)      графический пользовательский интерфейс, подобный Windows с элементами мультимедийных средств.
·         унификация архитектуры всей прикладной программы обусловливает и унификацию следующих ее компонентов:
1)      пользовательского интерфейса, стандарт CUA – Common User Access касается взаимодействия пользователя и программы. Цель CUA - облегчить освоение новых программ пользователем. Например, во всех программах нажатие одних и тех же клавиш должно приводить к аналогичным действиям: F1 - всегда вызов Help, или Esc - возврат на один уровень вверх;
2)      унификацию средств обмена данными между приложениями, появляются такие средства как DDE – динамический обмен данными и OLE - встраивание и связывание объектов;
3)      унификацию средств связи для обмена по сетям, целью которой является возможность подключения к единой сети машин с различными аппаратными и операционными платформами.
 

Уровень операционной системы
Операционные системы в своем развитии прошли через ряд этапов:
1)      отсутствие ОС;
2)      ОС пакетной обработки IBM 360;
3)      ОС разделения времени PDP 11;
4)      ОС персональных ЭВМ - DOS, Windows;
5)      сетевые ОС - NetWare, NT, OS/2, UNIX, последние версии Windows .
Первой особенностью развития операционных систем является тесная связь с развитием аппаратуры. Т.е. для каждого поколения процессора есть операционная система, наиболее эффективно использующая его возможности, например:
8080 - CP/M;
8086 - MS DOS - однозадачные ОС, а т.к. после появляются процессоры, аппаратно поддерживающие многозадачность и управление виртуальной памятью, появляются и ОС, поддерживающие эти свойства,
80286 - OS/2, Windows и т. д.
 
Второй особенностью операционных систем является стремление на любом этапе развития реализовать многозадачность. Эта особенность всегда отражалась наличием некоторого “довеска” к ОС, обеспечивающего многозадачность в однозадачных системах, например:
СР/М - МР/М;
DOS - Task swapper + система прерываний;
далее этот довесок просто не нужен, т.к. такие ОС, как OS/2, UNIX, NetWare, NT являются подлинно многозадачными.
 
Классификация ОС по признаку многозадачности выглядит следующим образом:
 


 
 
 
 
 
 
 
 
 
 

1) Будем считать, что однозадачные системы мы усвоили хорошо, поэтому ими мы заниматься не будем, если только не понадобиться уточнить какой-нибудь аспект применительно к принципам многозадачности (преемственность, естественно, есть).
2) Многозадачные однопроцессорные ОС - основной принцип их реализации - разделение времени. Это основной класс систем в настоящее время, именно такими системами мы и будем заниматься в нашем курсе, поэтому сейчас мы их отложим в сторону, пробежимся по многопроцессорным системам, а затем навсегда вернемся к многозадачным однопроцессорным ОС.
3) Многозадачные многопроцессорные ОС.
В природе физические процессы протекают одновременно, параллельно во времени, поэтому параллельное протекание вычислительных процессов в компьютере является объективным отражением реального мира.
Как мы уже упоминали, стремление к организации многозадачности имело место всегда, даже в однопроцессорных системах. Интерес к многопроцессорным системам растет, особенно с учетом снижения цен на процессоры, но есть целый ряд проблем:
 
1)      Человеку свойственно мыслить последовательными категориями.
2)      В языках программирования нет средств выражения параллелизма, что также обусловлено первым фактором. О том, какие средства существуют, мы будем говорить в дальнейшем.
3)      Базовая аппаратная архитектура процессора ориентирована на последовательную обработку.
4)      Отладка программ с элементами параллелизма чрезвычайно сложна.
 
Все эти и другие сложности приводят к трудностям широкого распространения многопроцессорных систем.
Концептуального прогресс связан с появлением средств автоматического распараллеливания программ и автоматического распределения параллельных веток по процессорам.
Дадим краткую характеристику того, что называется автоматическим распараллеливанием.
Обнаружение параллелизма, как программистом, так и программно-техническими средствами - компиляторами, ОС, является чрезвычайно сложной задачей.
В ряде наиболее простых случаев он может быть указан явно программистом. Тогда фрагмент программы с элементами параллелизма будет выглядеть примерно так:
cobegin
               statement_1;
               statement_2;
               ...
               statement_N;
coend;
Такая запись может означать, что приведенные операторы могут выполняться каждый на своем процессоре. Однако, не ясно, как распределять операторы по процессорам, особенно в случае, если процессоров меньше, чем операторов, и какова будет переносимость программы, если на одной машине, например, 5 процессоров, а на другой 10, и что делать, если программист ошибся с распараллеливанием.
Поэтому компиляторы используют, как правило, неявный параллелизм программ, одним из примеров которого является алгоритм расщепления цикла. Рассмотрим следующий цикл:
 
For i := 1 To 5 Do begin
   A[i] := B[i] + C[i];
End;
 
Ясно, что такой цикл можно разбить на 5 параллельно выполняемых фрагментов. Но что делать опять, если число итераций не будет совпадать с числом процессоров, и как распределять итерации по процессорам?
Другим примером распараллеливания является "редукция высоты дерева", например, последовательность операций:
((p + q) + s) + r
можно преобразовать следующим образом:
(p + q) + (s + r)
и выполнить операции в скобках одновременно.
Название метода происходит из графового способа описания операций. Для первого варианта граф выглядит следующим образом:


 

                                         r
                                       s
                                   p     q
 
Для второго варианта граф выглядит следующим образом:
 
 
 
 

                                   p    q s     r
 
Общее правило - изменение порядка выполнения операций за счет использования ассоциативных, коммутативных и дистрибутивных свойств операций.
Наука о параллельном программировании идет в основном в этом направлении. Но существует еще одна очень большая ветвь в параллельном программировании - это системы реального времени. Здесь речь идет о системах управления технологическими процессами самой различной природы и задача параллельного программирования здесь несколько иная, а именно, создание параллельных процессов в компьютере, адекватно отражающих параллельные процессы в физическом объекте. В этом направлении созданы такие языки с элементами параллельного программирования как Ада и Модула-2 и именно с этой точки зрения мы с вами будем рассматривать параллельное выполнение программ.
Аппаратная организация многопроцессорных систем
Это, вообще говоря, предмет кафедры ВТ, поэтому здесь мы дадим только краткую характеристику аппаратных архитектур. Их мы поделим на два класса:
 
1.       системы с сильно связанной архитектурой;
2.       системы со слабосвязанной архитектурой.
 
К первому классу относятся следующие системы:
1) Системы с общей шиной


 
 
 
 
 
 
 
 

Пропускная способность такой системы определяется, в конечном счете, характеристиками шины.
2) Системы с матрицами координатной коммутации


 
 
 
 
 
 
 
 

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


 
 
 
 
 
 
 
 
 
 
 

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

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


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

В заключение описания особенностей многопроцессорных ОС отметим следующее. Многие важные понятия, которые мы с вами будем рассматривать на примере однопроцессорных многозадачных ОС, целиком и полностью переносятся на многопроцессорные ОС.
На определенном логическом уровне рассмотрения становится безразлично на многих процессорах или на одном с разделением времени выполняются параллельные программы. Таковыми являются важнейшие понятия многозадачности - понятия ПРОЦЕСС и СИНХРОНИЗАЦИЯ, которые мы подробно будем рассматривать в нашем курсе.
 
1.2. Модели операционных систем
 
Поскольку операционная система - это сложная программно-аппаратная система, для ее описания применим принцип структурирования.
 
Код ОС может быть структурирован различными способами. Простейшим является способ, при котором ОС состоит из набора процедур, каждая из которых может вызывать другую процедуру.
 
Во всех, кроме самых простейших ОС, приложения отделены от самой ОС. Код ОС выполняется в привилегированном режиме (режиме ядра) и имеет доступ к данным и аппаратному обеспечению. Приложения выполняются в непривилегированном режиме (режиме пользователя) с ограниченным доступом к системным данным и с ограниченным набором системных интерфейсов. Когда программа пользователя запрашивает обслуживание системой, процессор перехватывает запрос и переключает вызывающий поток в режим ядра. После завершения системного обслуживания операционная среда переключает поток назад, в режим пользователя, и позволяет продолжить выполнение вызвавшей программы.
 
С учетом сказанного, первый вариант структурирования выглядит следующим образом:
 

 

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

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

i + 1

i

i - 1

 
то можно выделить следующие свойства уровней:
1)                        Каждый уровень представим в виде набора объектов аппаратных или программных и набора операций над объектами данного уровня;
2)                        Каждый уровень предоставляет вышележащему уровню интерфейс - набор примитивов - базовых операций, которые может выполнять вышележащий уровень с нижележащим уровнем;
3)                        Вышележащий уровень не вникает в детали реализации нижележащего уровня, а пользуется только предоставленными примитивами.
4)                        Уровень i + 1 не пользуется непосредственно средствами уровня i - 1, а пользуется ими только через уровень i;
5)                        Допустима модификация любого уровня, при этом, если сохраняется интерфейс, то такая модификация проходит безболезненно для вышележащих уровней.

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


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

При перечислении моделей операционных сред нельзя не упомянуть объектную модель, при которой любой ресурс системы, который одновременно может быть использован более, чем одним процессом, включая память, файлы и устройства, реализован в виде объекта. Преимущества такого описания по сравнению с другими способами, следующие:
 
1)       однотипность управления ресурсами и осуществления доступа к ним;
 
2)       единство описания ресурсов;
 
3)      высок уровень структуризации ОС, позволяющий легко осуществлять  модернизацию ОС.
 
Это модели операционных систем. Реальные системы, например, Windows NT объединяют в себе черты моделей иерархической и клиент-сервер. Поэтому более подробно мы будем останавливаться на этих моделях.
Модели ОС мы с вами рассмотрели не просто так, а для того чтобы выявить функции ОС. Функции ОС мы будем выявлять на основе анализа иерархической модели ОС.
 
 
 

1.3. Функции операционных систем
Если вернуться к иерархической модели ОС, то следует отметить еще одно свойство этой модели, а именно, каждый уровень представляет собой некоторую функцию операционной системы, если перед перечнем объектов некоторого уровня поставить слово "управление", то и получим соответствующую функцию. Эта функция определена согласно ее сложности, временному масштабу выполнения и уровню абстракции.
Поэтому для перечисления функций ОС просто более подробно распишем иерархическую модель ОС.
 

№№

НАЗВАНИЕ

ОБЪЕКТ

ПРИМЕРЫ ДЕЙСТВИЯ

13

Оболочка

Интерфейс пользователя

Действия на языке обол.

Цикл опрса событий

12

Процессы пользователя

Виртуальная

машина

Создать, приостанвить, возобновить, уничтожить

11

Каталоги

Таблицы соотв. внешн. и внутр. имён

Создать, связать, модиф, прочитать, читать, записать

10

Устройства

Дисплей, принтер, клавиатура

Создать, уничтожить, откр., закрыть, читать, записать

9

Файловая система

Файлы

То же, что и на 10

8

Коммуникации

Конвейер, буфер

То же, что и на 10

7

Виртуальная память

Страницы, сегменты

Загрузить, выгрузить, прочитать, записать

6

Локальная внешняя память

Диск, сектор, дорожка

Прочитать, записать

5

Элементарные процессы

Семафоры, сигналы, дескрипторы,очереди

Создать, уничтожить, возобновить, приостановить процесс

4

Прерывания

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

Вызов, возврат, маскирование, размаскирование, уст. Вектор

3

Процедуры

Логическое завершение, набор команд, стеки

Вызов, возврат, прочит. из стека, записать в стек

2

Система команд

Инструкции, директивы

Чтение,запись, пересылка, сравнение, арифмет. операции

1

Физическая машина

Регистры, процессор, ячейки памяти

Сброс, установка, запись, чтение

 
Уровень 1 Уровень электронных схем, на котором определены такие объекты, как регистры, счетчики, логические схемы, сумматоры и т.д. Известны и операции, определенные на этих схемах: сброс, установка, чтение, запись. Таким образом, первой функцией ОС является управление физической аппаратурой.
Уровень 2. Управление аппаратурой производится с помощью выполнение команд процессора, это несколько более абстрактный уровень, чем уровень аппаратуры. Вспомним, например, программную модель процессора.
Уровень 3. На этом уровне отдельные инструкции системы команд объединяются в логически законченные участки, выполняющие определенные функции, и называемые процедурами. Процедура – это базовый элемент любой программной системы. Выполнение любой программы - это последовательность вызовов процедур.
Уровень 4. На этом уровне появляются прерывания как средство взаимодействия процессора с периферийной аппаратурой. Проблема взаимодействия с аппаратурой состоит в том, что сигналы от аппаратуры могут появляться в произвольный момент времени относительно потока выполняемых команд. Система прерываний и позволяет преодолеть эту асинхронность появления сигналов от аппаратуры. Реакция на сигналы - это определенным образом организованные процедуры - процедуры обработки прерываний. 
Первые четыре уровня - это уровни, очень сильно зависящие от аппаратуры машины. Далее идет более высокий уровень абстракции.
Уровень 5. На этом уровне появляются средства, связанные с попытками одновременного выполнения нескольких задач. Например, печать, редактирование текста и обмен данными через модем. Если процессор один, а задач требуется выполнять несколько, то появляется некоторая надстройка, обеспечивающая переключение задач. Здесь появляется понятие - контекст. При этом одна задача приостанавливается, ее контекст сохраняется в специальной структуре данных, а другая возобновляется и ее контекст восстанавливается. Большая роль здесь отводится вопросам взаимодействия задач, например, одна задача не может продолжить выполнение с какой-то точки, пока другая задача не пройдет через определенную точку в своей программе. Все средства организации многозадачности и взаимодействия задач объединены на этом уровне элементарных процессов.
Уровень 6. На данном уровне осуществляется управление доступом к устройствам внешней памяти одной машины. Пользовательские программы лишь определяют логическое расположение данных на дисках, а программы этого уровня осуществляют поиск, запись и чтение физически, определяя положение данных на дорожках и секторах. Программы этого уровня всегда оформляются в виде процессов, поэтому и находятся над соответствующим уровнем.
Уровень 7. На данном уровне осуществляется управление виртуальной памятью. Виртуальная память - это средство расширения оперативной памяти за счет дискового пространства.
Вплоть до уровня 7 операционная среда имеет дело в основном с ресурсами одного компьютера. Начиная со следующего уровня, среда выполнения программ существенно расширяется.
Уровень 8. На уровне 8 осуществляется управление коммуникациями - обменом данными - между процессами. Для этого создаются специальные средства, которые базируются на средствах 5-го уровня. При этом одни и те же примитивы используются как для коммуникаций между процессами, выполняемыми на одной машине, так и для процессов, выполняемых на разных машинах, хотя сами примитивы с некоторого нижнего уровня реализуются, естественно, по-разному.
Уровень 9. Этот уровень управляет объектами, гораздо более абстрактными, чем уровень 6. Если файлы, с которыми работает программа, расположены на другой физической машине, то для доступа к ним задействуется механизм коммуникаций.
Уровень 10. Этот уровень обеспечивает управление внешними устройствами, такими как принтер, дисплей, клавиатура. Для доступа к удаленным объектам этого уровня также может быть задействован механизм коммуникаций.
Важной особенностью уровней 8, 9, 10 является то, что на объектах этих уровней определены операции, имеющие одинаковые имена: создать, уничтожить, открыть, закрыть, прочитать, записать. Они различаются в реализации, но пользователь об этом может не задумываться. Он с помощью одинаковых вызовов будет брать данные из конвейера, из файла или из устройства. Такой прием называется поздним связыванием и известен из объектно-ориентированного программирования.
Уровень 11. На этом уровне осуществляется связывание внешних имен объектов, с которыми работает пользователь, с их внутренними именами, с которыми работает машина. Каталоги представляют собой таблицы соответствия внешних и внутренних имен, где внешние имена представляют собой цепочки символов, а внутренние - коды или адреса. Кроме того, каталоги хранят перечни методов, которые могут выполняться над объектами. Именно на этом уровне происходит "разрешение ссылок", т.е. определение своего метода для данного объекта не зависимо оттого, что имя у этого метода такое же, как и другого объекта.
Уровень 12. На данном уровне происходит управление процессами пользователя. Отличие от уровня 5 элементарных процессов состоит в том, что глубина контекста на уровне 10 существенно больше. Если на уровне 5 контекст состоит, как правило, из набора регистров и стека, то здесь контекст – это фактически целая виртуальная машина.
Уровень 13. На уровне 13 находится некоторый интерпретатор команд пользователя. Не важно, каким образом он реализован – с помощью командной строки, как в DOS, или с помощью этикеток-иконок, как в Windows. В любом случае этот интерпретатор работает в бесконечном цикле следующего вида:
 
 
 
while true do begin
 ввод команды;
 выполнение команды;
end;
 
Итак, мы кратко перечислили функции операционной системы, как управление объектами соответствующего уровня.
Наш курс будет представлять собой более подробное знакомство с уровнями, начиная с 3-го. А ядром курса будут механизмы 5-го уровня операционной системы.
 
 
 
 
 
 
 
 
 
 
 
 

Раздел 2. Механизмы последовательного выполнения программ
2.1. Классификация методов замены контекста
 
Мы будем рассматривать механизмы выполнения программ с точки зрения 3-го и 4-го уровней операционной системы, т.е. с точки зрения процедур и прерываний.
Целью этого рассмотрения является переход к знакомству с механизмами параллельного выполнения программ, т.е. с механизмами уровня 5.
Будем рассматривать выполнение программы как последовательность вызовов процедур различного вида. Переход от одной процедуры к другой происходит с помощью механизмов ВЫЗОВА и ВОЗВРАТА. При этом происходит ЗАМЕНА и ВОССТАНОВЛЕНИЕ КОНТЕКСТА процедуры.
Контекстом процедуры называют совокупность данных, доступных процессору во время выполнения этой процедуры. Контекст содержит, как правило, состояние процессора (регистры) и контекст памяти.
Существует целый ряд механизмов замены контекста, различающихся в зависимости от глубины замены и моментов времени на потоке выполнения инструкций программы. Классификация методов замены контекста приведена ниже:
 

 

В синхронных методах определен момент замены контекста с точки зрения места в программе, т.к. программист сам пишет вызов соответствующего средства в программе.
В асинхронных методах такой момент замены контекста неизвестен и может наступить в любой точке программы.
У всех методов есть некоторые общие свойства. Базовым для всех является ПРОЦЕДУРА. Для наших задач базовым средством являются СОПРОГРАММЫ, которые тоже основаны на механизмах процедур. Поэтому чтобы унифицировать наши с вами знания о процедурах, кратко рассмотрим и их.
2.2. Процедуры как синхронные методы замены контекста
Процедурой будем называть синхронное средство замены контекста, на котором определено отношение вложенности.
Будем говорить, что если процедура выполняется в данный момент, то это значит, что она находится в активном состоянии.
Т.о. выполнение программы - это переход из одного активного состояния в другое. Этот переход осуществляется инструкциями ВЫЗОВ и ВОЗВРАТ. При этом происходит замена контекста одного активного состояния на контекст другого активного состояния.
Вызов и возврат осуществляются в несколько этапов:
Вызов:
1.      подготовка данных, передаваемых из вызывающей процедуры в вызываемую;
2.      сохранение контекста вызывающей процедуры;
3.      установление контекста вызываемой процедуры;
4.      создание локальной среды вызываемой процедуры.
Возврат:
1.      подготовка результатов, передаваемых из вызываемой процедуры в вызывающую;
2.      установление контекста вызывающей процедуры.
Важно, что локальная среда и контекст вызываемой процедуры не сохраняются, а разрушаются.
 
Структура данных, используемая для реализации механизмов вызова и возврата - это стек.
Напомню инструкции работы со стеком:
 
push ax        sp := sp-2;
               ss:[sp] := ax;
 
pop ax         ax := ss:[sp];
               sp := sp + 2;
 
call Q         push cs;
               push ip;
               cs := seg(Q);
               ip := ofs(Q);
 
ret            pop ip
               pop cs
 
int            pushf
               call
 
iret           ret
               popf
 
В самом общем виде схема вызова и возврата выглядит следующим образом:

 

Стек

Лок. Среда С

Лок. Среда В

Лок. Среда А

Код А

 

Вызов В

 

Код В

 

Вызов С

 

Код С


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

Текущая среда в стеке описывается двумя параметрами: База стека и Вершина стека.
 
При вызове:
 
1.      База стека (старая) -> в стек;
2.      База стека (новая) := Вершина стека (старая);
3.      Вершина стека (новая) := Вершина стека (старая) + Размер среды;
 
При возврате:
 
1.      Вершина стека (новая) := База стека (старая);
2.      Из стека -> База стека (новая);
 
Конкретная реализация данной схемы зависит от архитектуры машины и алгоритмов компилятора.
 
Рассмотрим пример, который будет необходим для реализации лабораторных работ. Распишем по шагам состояние стека при вызове процедуры Паскаля, имеющей следующее описание:
 
Procedure Name(A, B : Word);
Var
i, k : Word;
Begin
     ...
End {Name};
 
Вызов процедуры компилируется в следующую примерную последовательность инструкций:
 
push A
push B {этап 1 - подготовка передаваемых параметров}
{----------------------------------------------------------------}
push cs {часть действий из call Name, влияющих на стек}
push ip {Этапы 2 и 3 - сохранение контекста вызывающей
push bp {процедуры; сохранение старой базы в стеке}
{----------------------------------------------------------------}
mov bp,sp {новая база    = старая вершина}
sub sp,4  {новая вершина = старая вершина + размер среды}
          {Этап 4 - создание локальной среды}
{----------------------------------------------------------------}
 
 
 
 
 
 
 
 
 
 
 
 
 
 


 

 
 

 
Доступ к локальным переменным i и k осуществляется через косвенную адресацию:
 
i - [bp - 2]
k - [bp - 4]
 
Также осуществляется доступ к передаваемым параметрам:
 
B - [bp + 6]
A - [bp + 8]
 
Возврат из процедуры компилируется в следующую последовательность инструкций:
 
mov sp,bp {вершина стека новая = база стека старая}
pop bp    {база стека новая извлекается из стека}
ret 4     {дополнительный сдвиг sp на 4 из-за 2-ух слов – переданных
          {параметров А и В}
 
После последней инструкции стек переходит в состояние до вызова процедуры Name.
Выводы
1. Если программа пишется на языке высокого уровня, обо всем рассказанном можно и не знать, хотя все равно полезно оценить, во что обходится вызов процедуры с передачей большого числа параметров и созданием большого числа локальных переменных. Но если в текст программы на языке высокого уровня включается процедура, написанная на ассемблере, то знание механизмов передачи параметров необходимо, т.к. заталкивание параметров делает компилятор, а извлекать информацию приходится программисту. Что мы и будем делать при изучении сопрограмм.
 
2. Компилятор всегда реализует некоторый протокол операций вызова и возврата. Фрагмент такого протокола приведен ниже:
 
1.      для Var-параметров в стек заталкивается два слова – адрес соответствующей переменной;
2.      очередность заталкивания параметров в стек соответствует их перечислению в списке формальных параметров (в Паскале, в Си - наоборот);
3.      для функции байтный результат возвращается в AL, слово - в AX; двойное слово - в DX:AX.
4.      меньше, чем слово в стек не заталкивается.
 
3. Так как все локальные среды создаются на одном стеке, характерной чертой процедур является вложенность вызовов - возврат происходит в точку вызова всегда и с потерей контекста выполненной процедуры. Существует другая схема смены контекстов, при которой отношение вложенности между процедурами отсутствует и возврат не есть завершение выполнения процедуры, а только ее приостанов с сохранением контекста, так что ее выполнение может быть продолжено с точки приостановления. Это средство называется сопрограммой, к знакомству с ними мы и переходим.
 
 
 

2.3. Сопрограммы
 
Понятие "сопрограмма" не настолько широко известно, как "подпрограмма". Сопрограммы представляют собой средство моделирования и реализации параллелизма.
Суть сопрограмм лучше всего выявляется при их сопоставлении с процедурами. В процедурах определены отношение вложенности и пара несимметричных операций ВЫЗОВ и ВОЗВРАТ. Сопрограммы, так же как и процедуры могут вызывать друг друга, но между ними нет отношения вложенности, а вместо несимметричной пары операций ВЫЗОВА и ВОЗВРАТА существует одна операция, которую чаще всего называют ПЕРЕДАТЬ УПРАВЛЕНИЕ. Т.е., если определены сопрограммы р1 и р2, то определена операция ПЕРЕДАТЬ УПРАВЛЕНИЕ (от кого, кому). При передаче управления от одной сопрограммы к другой, выполнение первой сопрограммы приостанавливается, а ее состояние запоминается, таким образом, что когда приостановленной сопрограмме снова передается управление, она продолжает свое выполнение именно с той точки, на которой была приостановлена.
В различной литературе существуют разные нотации для описания сопрограмм. Существует термин "restart p" (Краковяк), существует термин "resume p" (Шоу). Мы в дальнейшем будем пользоваться обозначение Transfer (от кого, кому), которое используется при реализации сопрограмм в языке Модула-2, и которое мы использовали в библиотечном модуле, прилагаемом к материалам по лабораторным работам.
Внешне взаимодействие двух сопрограмм может быть описано следующей схемой:


 
 
 
 
 
 
 
 
 
 
 

Если мы эту схему преобразуем во временную диаграмму взаимодействия сопрограмм, то эта диаграмма будет выглядеть следующим образом:


 
 
 
 
 
 
 
 

Получаем моделирование параллельного выполнения двух программ.
При передаче управления выполнение сопрограммы не завершается, а только приостанавливается, и впоследствии может быть возобновлено с точки приостановки. Поэтому для реализации сопрограмм нужны средства сохранения и восстановления контекста.
Ранее мы говорили, что использование одного стека является причиной вложенности процедур, поэтому сопрограммы реализуются путем предоставления своего стека каждой из них.
Существует текущий стек, т.е. стек, на который указывают регистры ss:sp. Это стек той сопрограммы, которая выполняется в данный момент. Передача управления с помощью операции Transfer(p1,p2) - это по сути замена стека.
 
 
 
 
 

Свяжем с каждой из сопрограмм собственный стек.
 
 
 
 
 
 
 
 
 
 
 
 

Операция Transfer - это вызов процедуры, который происходит на текущем стеке. Вызов осуществляется инструкцией call. При вызове, в стек записываются cs и ip. Это точка в сопрограмме, находящаяся сразу же за вызовом Transfer. Теперь внутри процедуры Transfer переключим стек на другую область памяти - на стек другой сопрограммы. Будем считать, что он находится в состоянии, похожем на стек первой сопрограммы. Т.е. в нем находится адрес той точки сопрограммы, которая находится за вызовом Transfer, приостановившим выполнение сопрограммы когда-то раньше. Возврат из процедуры Transfer осуществляется инструкцией ret, но выполняется он уже на стеке новой сопрограммы. Инструкция ret выталкивает из этого нового стека в регистры cs и ip адрес той точки сопрограммы, с которой и надо продолжить выполнение.
 
Т.к. внутри процедуры Transfer мы устанавливаем новые значения регистров ss:sp, указывающие на область памяти – вершину стека возобновляющей выполнение сопрограммы, то, во-первых, необходимо откуда-то брать эти новые значения, а, во-вторых, нам необходимо где-то запоминать состояние области памяти – вершины стека приостанавливаемой сопрограммы. Отсюда появляется структура данных, которую назовем ДЕСКРИПТОР сопрограммы, и которая должна хранить адрес вершины стека сопрограммы, когда она приостанавливается. Термин ДЕСКРИПТОР очень распространен в науках об операционных системах. Таким термином описывают структуры данных, хранящие информацию об объектах среды. Как правило, через дескрипторы осуществляется доступ к объектам.
В нашем минимальном варианте дескриптор хранит адрес ячейки памяти, являющейся вершиной стека соответствующей сопрограммы. Тогда ясным становится следующее описание дескриптора:
 
Type
       TDescriptor = Record
              ssreg : Word;
              spreg : Word;
       End {TDescriptor};
 
В дальнейшем мы будем усложнять описание дескриптора, дополняя его данными при усложнении понятия сопрограммы. Таким образом процедура Transfer должна писать состояние регистров ss:sp в дескриптор приостанавливаемой сопрограммы, а устанавливать новые значения регистров ss:sp из дескриптора возобновляемой сопрограммы. Т.е. процедуре Transfer должны быть переданы адреса двух дескрипторов - дескриптора приостанавливаемой сопрограммы и дескриптора возобновляемой сопрограммы.

С учетом механизма передачи параметров в процедуру через стек, картинка со стеками сопрограмм немного усложнится:


 
 
 
 
 
 
 
 
 
 
 
 
 

Здесь через D1 и D2 обозначены дескрипторы сопрограмм р1 и р2. Таким образом сопрограмма помимо собственного стека характеризуется еще и наличием дескриптора, хранящего состояние этого стека.
Начальная инициализация сопрограмм
Мы рассмотрели установившийся режим работы сопрограмм, когда они возобновляют свое выполнение после приостановки с помощью вызова Transfer. "Возобновление" в первый раз, т.е. передача управления с помощью функции Transfer сопрограмме, которая до этого не приостанавливалась функцией Transfer, требует специальной инициализации.
Эта инициализация включает в себя:
 
1.      выделение памяти под дескриптор;
2.      выделение памяти под стек;
3.      занесение в стек точки входа в сопрограмму;
4.      занесение в дескриптор адреса вершины стека.
 
Поскольку первое "возобновление" все равно происходит под действием функции Transfer, необходимо в инициализируемом стеке предусмотреть операции, выполняемые этой функцией. Эти операции следующие:
 
1.      выталкивание из вершины стека слова в регистр bp;
2.      выполнение инструкции ret, выталкивающей следующие два слова (точку входа в сопрограмму) в ip и cs;
3.      сдвиг указателя стека вниз (в сторону старших адресов) на 4 слова (8 байтов) - освобождение области стека, через которую в установившемся режиме передаются адреса дескрипторов.

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


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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


Transfer

 
 
 
 
 
 
 
 
 
 
 

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

2.4. Примеры реализации сопрограмм в Си, в защищенном режиме процессора и в Win32
 
2.4.1. Пример реализации сопрограмм в Си
 
В языке Си существует предопределенная структура, имеющая следующее описание.
Имя типа jmp_buf. Это по сути дела запись, включающая 10 полей типа word. Имена полей имеют следующий вид:
 
j_sp,      j_cs,   j_bp,   j_si,
j_ss,      j_ip,   j_di,   j_ds.
j_flag,    j_es,
 
Чтобы сразу провести параллель с сопрограммами, заметим, что структура подобного типа может выступить в качестве дескриптора сопрограммы, причем даже более информативного, чем тот, который мы рассмотрели ранее.
Для работы со структурой jmp_buf существует пара функций, имеющая следующее описание:
 
1.      int setjmp(jmp_buf jmpb) - пишет состояние текущей задачи в буфер jmpb и возвращает 0;
2.      void longjmp(jmp_buf jmpb, int retval) – восстанавливает состояние задачи из jmpb так, что задача продолжает свое выполнение с той точки, в которую бы она пришла, если бы функция setjmp вернула не 0, а значение, равное retval.
 
Рассмотрим структуру сопрограмм и функции Transfer для данного случая.
 
void cor1(void) {                             void cor2(void) {
while (1) {                                   while (1) {
        ...                                           ...
        transfer(jmpc1,jmpc2);                        transfer(jmpc2,jmpc1);
}                                             }
}                                             }
 
void transfer(from, to)
jmp_buf from,to; {
 
        if (0 == setjmp(from)) {//setjmp пишет такое состояние в буфер
            longjmp(to,1);      //from, что когда будет вызов longjmp
        }                       //с этим буфером, управление передастся
        *****                   //в точку *****
}
 
Инициализация буфера (на примере cor1)
 
jmp_buf jmpc1;
unsigned stack1[1000];
struct SREGS segs;
segread(&segs);
jmpc1[0].j_sp   = FP_OFF(stack1) + 1982;
jmpc1[0].j_ss   = FP_SEG(stack1);
jmpc1[0].j_flag = 0x200; //прерывания разрешены
jmpc1[0].j_cs   = FP_SEG(cor1);
jmpc1[0].j_ip   = FP_OFF(cor1);
jmpc1[0].j_bp   = jmpc1[0].j_sp;
jmpc1[0].j_di   = 0;
jmpc1[0].j_es   = segs.es;
jmpc1[0].j_si   = 0;
jmpc1[0].j_ds   = segs.ds;
 
Функции setjmp и longjmp вместе со структурой jmp_buf являются чрезвычайно удобным средством реализации сопрограмм. Во-первых, мы не спускаемся на уровень ассемблера, а во-вторых, запись состояния регистров в буфер и восстановление их из буфера происходит в режиме запрета прерываний, что обеспечивает высокую надежность переключения задач.
 
 
2.4.2. Пример реализации сопрограмм в защищенном режиме
 
Защищенный режим процессора архитектурно создан для организации многозадачности. У защищенного режима много аспектов, здесь мы рассмотрим именно вопросы создания и переключения задач, которые по сути дела эквивалентны нашему понятию сопрограмм.
Пример, который реализует переключение задач в защищенном режиме, полностью написан на ассемблере. Я буду приводить здесь только фрагменты примера. Если кому-то нужен полный текст примера, то с ним можно также ознакомиться. 
Доступ к памяти в защищенном режиме осуществляется не непосредственно по адресу, а через специальные таблицы - таблицы дескрипторов. В таблице дескрипторов хранятся строки, описывающие отдельные участки памяти - страницы, если размер участка постоянен и известен, или сегменты, размер которых может меняться.
Для описания задач существует структура, которая называется "сегмент состояния задачи" TSS. Она имеет следующий вид:
 

Селектор LTD

Селектор DS

Селектор SS

Селектор CS

Селектор ES

Регистры AX, BX, CX, DX, SP, BP, SI, DI

Регистр флагов

IP

SS, SP для уровня привилегий 2

SS, SP для уровня привилегий 1

SS, SP для уровня привилегий 0

Указатель на следующий TSS

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

не используется

описание самой таблицы дескрипторов

дескриптор сегмента данных

дескриптор сегмента стека

дескриптор кодового сегмента

дескриптор задачи - main

дескриптор задачи 1

дескриптор задачи 2

 
Строка таблицы дескрипторов содержит следующие данные:
 

Размер сегмента

Адрес сегмента

Признак сегмента

 
 

В программу, реализующую переключение задач в защищенном режиме, вводятся:
 
1.      селекторы задач TASK1_SEL, TASK2_SEL, MAIN_TSK – смещения соответствующих дескрипторов в таблице GDT;
2.      выделяется память под стеки, например, так, tsk1_stack db 1024d (0);
3.      инициализируется таблица дескрипторов GDT - т.е. корректно заполняются все ее строки;
4.      инициализируются сегменты состояния задач, например, в поле ip пишется OFFSET Имя процедуры-задачи; в поле sp пишется OFFSET tsk1_stack + Size_Of_Stack;
5.      в регистр GDTR грузится адрес таблицы дескрипторов GDT;
6.      в регистр задач TR грузится селектор задачи - MAIN_TSK.
 
Таким образом, происходит выполнение задачи Main. Переключение на другую задачу производится инструкцией jmp, например, jmp TASK1_SEL.
По этой инструкции машина видит по признаку сегмента, что селектор указывает на дескриптор задачи. По селектору MAIN_TSK, находящемуся в регистре задач TR, через дескриптор задачи main, находится сегмент состояния задачи main и в него списывается состояние машины в соответствие со структурой сегмента.
Селектор новой задачи TASK1_SEL грузится в регистр задач TR, по селектору также через дескриптор находится сегмент состояния задачи 1, и из этого сегмента устанавливается новое состояние машины. В заключении представим схему переключения задач в защищенном режиме, из схемы видна аналогия с ранее рассмотренными сопрограммами. Если в Си для переключения задач потребовалось написать функцию, то в защищенном режиме переключение задач осуществляется одной инструкцией ассемблера.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Этапы создания задач-сопрограмм во всех трех примерах одни и те же. Это:
 
1.      выделение памяти под стеки (стек для main выделять не надо);
2.      выделение памяти под дескрипторы (в том числе и для main);
3.      необходимым образом выполненная инициализация стеков;
4.      необходимым образом выполнение инициализации дескрипторов;
5.      переход из задачи main в одну из сопрограмм;
6.      переключение сопрограмм по требуемому алгоритму;
7.      возвращение в задачу main.

2.4.3. Пример реализации сопрограмм в Win32

В Win32 существует средство, называемое Fiber (нить).

Нити могут быть использованы для достижения следующих целей:

 

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

 

API нитей включает 6 функций:

 

  1. ConvertThreadToFiber – включение возможности использовать нити и превращение main-потока в главную нить.
  2. CreateFiber - Создание дополнительных нитей.
  3. GetFiberData – получение данных, сформированных при создании нити.
  4. GetCurrentFiber – получение идентификатора активной нити (собственного идентификатора).
  5. SwitchToFiber – переключение на другую нить.
  6. DeleteFiber – удаление нити.

 

Нити имеют собственные стеки.

 

Существует два способа управления нитями:

 

  1. Управление «ведущий-ведомый», когда все нити передают управление главной нити, а та в свою очередь решает, какой из нитей передать управление.
  2. Равноправное управление, когда сама нить определяет, какая из нитей будет выполняться следующей.

 

Ниже приведен текст примера, использующего нити Win32.


#include <windows.h>

#include <stdio.h>

 

#define FIBER_COUNT 3 // total number of fibers (including primary)

#define MAIN_FIBER     0          // array index to main fiber

#define FIRST_FIBER    1          // array index to first fiber

#define SECOND_FIBER   2          // array index to second fiber

 

LPVOID lpFiber[FIBER_COUNT];

//===================================================================

VOID __stdcall FirstFiberFunc(LPVOID lpParameter) {

int i = 0;

while (1) {

printf("FirstFiberFunc; i = %d\n",i);

i++;

if (i > 11) {

break;

}

SwitchToFiber(lpFiber[SECOND_FIBER]);

}

printf("FirstFiberFunc: return to main fiber\n");

SwitchToFiber(lpFiber[MAIN_FIBER]);

}

//===================================================================

VOID __stdcall SecondFiberFunc(LPVOID lpParameter){

int i = 0;

while (1) {

printf("SecondFiberFunc; i = %d\n",i);

i++;

if (i > 10) {

break;

}

SwitchToFiber(lpFiber[FIRST_FIBER]);

}

printf("SecondFiberFunc: return to main fiber\n");

SwitchToFiber(lpFiber[MAIN_FIBER]);

}

//====================================================================

int __cdecl main(int argc,char *argv[]){

 

lpFiber[MAIN_FIBER]   = ConvertThreadToFiber(NULL);

lpFiber[FIRST_FIBER]  = CreateFiber(0, FirstFiberFunc, NULL);

lpFiber[SECOND_FIBER] = CreateFiber(0, SecondFiberFunc, NULL);

SwitchToFiber(lpFiber[FIRST_FIBER]);

DeleteFiber(lpFiber[FIRST_FIBER]);

DeleteFiber(lpFiber[SECOND_FIBER]);

printf("main fiber\n");

return 0;

}

//====================================================================

 
 

2.5. Процедуры ОС
 
Процедуры ОС - это средство синхронной передачи управления, которое приводит к перестановке контекста процессора. По своему внешнему виду вызов процедуры ОС очень похож на вызов обычной процедуры в программе.
Отличие процедуры ОС от обычной процедуры состоит в более глубокой модификации контекста.
Процедуры операционной системы – это процедуры с более высоким приоритетом, чем пользовательские.
Ранее рассматривали примитивы - базовые операции некоторого логического уровня среды, предоставляемые вышележащему уровню. Вызов такого примитива с этого вышележащего уровня и есть вызов процедуры ОС.
Пример. Вызов write(a) внешне очень похож на вызов обычной процедуры. Однако, это не обычный вызов, т.к. в нем идет обращение к функциям ОС.
Пример 2 обращения к процедуре ОС - вызов функции int 21h в ДОС.
Как и в обычных процедурах создается некоторый протокол передачи параметров в процедуры ОС, например, в рабочих регистрах передается номер запрашиваемых действий.
Процедуры ОС обычно предоставляются пользователю как уже готовые в форме API - интерфейса прикладного программирования.
 
Отличия процедур ОС от обычных процедур состоят в следующем:
 
1.      на входе в такую процедуру осуществляется проверка прав пользователя на ее вызов;
2.      контекст переносится в область памяти, недоступную пользователю;
3.      после выполнения процедуры возврат не всегда осуществляется в точку вызова.
 
Вызовы процедур ОС в форме API можно трактовать как расширения языка программирования, но только более аппаратно-зависимые.
Как осуществлять с низкоприоритетного уровня вызов процедуры более высокого уровня приоритета? Для этого архитектура защищенного режима опять предоставляет специальное средство, называемое коммутатор или шлюз. Вспомним, что обращение к памяти в защищенном режиме осуществляется через дескрипторы, находящиеся в специальной таблице. Вместо адреса памяти в инструкцию помещается селектор – адрес дескриптора.
Селектор содержит поле RPL - запрашиваемый уровень привилегий.
 
Смещение в таблице дескрипторов
RPL
 
Дескриптор содержит поле DPL - уровень привилегий дескриптора. Если при запросе RPL = DPL, то запрос будет обслужен. Дескрипторы задач ОС - это дескрипторы с большим уровнем привилегий DPL, чем уровень RPL запросов пользователя.
Поэтому для согласования уровней существует специальное средство - шлюз.
Шлюз - это специальный дескриптор с уровнем привилегий DPL, низким, равным пользовательскому уровню, но содержащий в поле адреса новый селектор с уровнем RPL, более высоким, чем уровень пользователя. А этот новый селектор содержит RPL, соответствующий уровню привилегий дескриптора, указывающего на процедуру ОС.
 

 
 


Селектор пользователя с уровнем привилегий 2 обращается к шлюзу, имеющему уровень привилегий 2, но содержащему селектор с уровнем привилегий 1

 
 
 

2.6. Прерывания как асинхронный метод замены контекста
 
Мы сказали ранее, что к асинхронным методам замены контекста относятся такие методы, которые используются, когда момент замены контекста заранее неизвестен. Такая ситуация имеет место, когда инициатором замены контекста является внешняя по отношению к компьютеру среда. Вообще существует три способа преодоления асинхронности, т.е. неопределенности момента появления сигналов внешней среды:
 
1.      активное ожидание;
2.      периодический опрос;
3.      прерывания.
 
Приведем пример. Мы находимся дома и знаем, что должен придти гость. Чтобы не пропустить момент прихода гостя, надо:
 
1.      сидеть у дверей и ждать его (активное ожидание) - недостаток этого метода в том, что много времени теряется зря;
2.      делать свою работу, но периодически подходить к двери и проверять, не пришел ли гость (периодический опрос) – недостаток этого метода состоит в том, что можно пропустить момент прихода гостя;
3.      установить звонок и спокойно заниматься своей работой, когда гость придет, он сам звонком сообщит о своем приходе (прерывания). Для реализации этого метода необходимы специальные средства - звонок.
 
В компьютере в качестве звонка от внешней среды и выступает система прерываний.
Система прерываний - это сложная система, представляющая собой совокупность программных и аппаратных средств. Кратко напомню содержание этих средств.
 
Аппаратные компоненты:


Микропроцессор

 

сигналы от внешней среды

 
 



Таймер

 

Память

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

 
Номер прерывания надо умножить на 4, чтобы узнать смещение в таблице векторов, по которому находится адрес обработчика прерывания с этим номером.
 
 
 
 
 
 
 
 
 
 
Если прерывания могут вызываться несколькими причинами, используется один из двух механизмов различения причины прерывания:
 
1.      обработчик на все причины один, но причина передается дополнительным параметром, например, вызов прерывания ДОС, когда номер функции передается в регистре;
2.      с каждой причиной связывается свой обработчик, так обслуживаются аппаратные прерывания. В этом случае говорят о многоуровневой системе прерываний.
 
В многоуровневой системе появляются понятия приоритета уровней и маскирования уровней.
Проблемы приоритетов и маскирования решаются аппаратно на уровне контроллера. Приоритетность организуется следующим протоколом:
сигнал на входе с меньшим номером закрывает входы с большими номерами.
Маскирование - это программное закрытие некоторого входа контроллера прерываний. Для запоминания маски контроллер содержит специальный регистр.
Вообще контроллер представляется внешним устройством, доступ к которому осуществляется как к портам ввода/вывода с помощью инструкций in и out.
 
Программные средства доступа к системе прерываний
 
Ассемблер. Можно использовать функции ДОС 25h и 35h для установки и чтения векторов прерываний. Обработчик прерываний должен сохранять в стеке все регистры на входе, восстанавливать их на выходе, завершать свою работу командой конца прерывания (для аппаратного прерывания) и инструкцией iret.
 
Паскаль. Паскаль имеет библиотечные процедуры для чтения и установки векторов прерываний - GetIntVec и SetIntVec. Кроме того, присвоение специального атрибута "interrupt" процедуре автоматически обеспечивает ее оформление как обработчика прерываний с сохранением и восстановлением всех регистров и возвратом по iret.
 
Общая схема обработки прерывания
 
Классический вариант:
 
1.      Выполнение текущей программы приостанавливается;
2.      Начинается выполнение программы-обработчика прерывания;
3.      Обработка прерывания;
4.      После завершения обработки процессор возвращается к прерванной программе.

Нас будет интересовать несколько иная последовательность действий. А именно:
 
3.      Обработка прерывания заключается в определении, какую программу следует запустить или продолжить. Это не обязательно должна быть прерванная программа.
4.      Установление контекста запускаемой программы
 
 
 
Логическая схема обработки прерывания
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Временная диаграмма обработки прерывания


Прерывания от таймера

 
 



P

 

Q

 

Обработчик

 
 
Каким образом можно вернуться в другую программу из прерывания?
Рассмотрим этот вопрос для сопрограмм. Чтобы вернуться в другую сопрограмму после прерывания одной из сопрограмм, необходимо в обработчик включить вызов процедуры Transfer, переключающей текущий стек на стек другой сопрограммы.
Посмотрим, как будут выглядеть стеки сопрограмм при прерываниях.
 
           Стек Р                                                    Стек Q 
 
BP
 
sp перед переключением
 
BP
 
IP
точка в обработчике после Transfer
 
IP*
 
CS
 
 
CS*
 
Ofs Q
 
 
Ofs P
 
Seg Q
 
 
Seg P
 
Ofs P
 
 
Ofs Q
 
Seg P
 
 
Seg Q
 
AX BX CX DX SI DI DS ES BP
 
 
AX BX    ES BP
 
IP
адрес возврата в сопрограмму
 
IP#
 
CS
 
 
CS#
 
FL
 
 
FL
указатель до прерывания
 
Временная диаграмма переключения сопрограмм при прерываниях выглядит следующим образом:


 
 
 
 
 
 
 
 
 
 
 

В стеке возобновляемой сопрограммы символами * обозначен адрес возврата из процедуры Transfer в обработчик прерывания. Т.е. после выхода из процедуры Transfer продолжают выполняться коды обработчика. Последней инструкцией обработчика является инструкция iret, выталкивающая из стека адрес возврата в сопрограмму, обозначенный символами #.
Имеет особенность первый запуск сопрограммы из обработчика.
Из процедуры Transfer мы сразу попадаем в сопрограмму, а не возвращаемся в обработчик, т.к. стек сопрограммы инициализирован так, что на позициях, обозначенных *, установлен адрес точки входа в сопрограмму.
Если набор команд для контроллера, генерирующих сигнал окончания прерывания, будет помещен в обработчике после вызова Transfer, то многозадачная программа окажется неработоспособной, т.к. сигнал окончания прерывания EOI контроллеру при входе в сопрограмму, запущенную первый раз, передан не будет.
Дополнительной особенностью работы сопрограмм в условиях прерываний является необходимость выполнения функции Transfer в режиме с запрещенными прерываниями, т.к. переключение стека происходит за две инструкции mov ss,.. и mov sp, то есть опасность попадания прерывания на момент времени между этими инструкциями. Пара ss:sp в это время будет указывать неправильное положение.
Познакомившись с механизмами работы сопрограмм в сочетании с прерываниями, мы практически изучили механизм реализации временного разделения или многозадачности.
Зная этот механизм, можно будет переходить к изучению механизмов параллельного выполнения программ - разделу 3.
 
 

2.7. Исключения
 
2.7.1. Самая общая характеристика исключений
 
Исключения отнесены нами к асинхронным методам замены контекста, т.к. неизвестен момент возникновения исключения. Это свойство сближает исключения с прерываниями. Различие состоит в том, что прерывание вызывается внешней причиной - это сигнал от внешней среды, а исключение – это внутренняя причина смены контекста, связанная с ходом выполнения инструкций программы. Под исключением понимают сигнал об отклонении в выполнении инструкции. Исключения могут активизироваться по следующим причинам:
 
1.      неправильные данные (например, деление на ноль);
2.      попытка выполнения инструкции, не соответствующей уровню защиты;
3.      невыполняемая инструкция (например, адрес вне памяти).
 
При появлении исключения процессор выдает сигнал прерывания с номером, соответствующим номеру причины исключения. Т.е. исключения обрабатываются как прерывания, а именно, по номеру исключения процессор обращается к таблице прерываний и через нее находит процедуру - обработчик исключения. Отличие исключений от прерываний состоит в том, что после выполнения обработчика исключения управление, как правило, не возвращается в точку возбуждения исключения. Механизм исключений исходно существует на уровне архитектуры процессора и далее развит для программных сред языков высокого уровня. Рассмотрим сначала исключения на низком уровне.
 
2.7.2 Исключения на низком уровне
 
Исключения на низком уровне рассмотрим на примере архитектуры 386 процессора. Архитектурно существует ряд ошибочных ситуаций, в которых процессор возбуждает прерывания, интерпретируемые как исключения. Ниже приведен перечень этих ситуаций:
 
Номер исключения и причина:
 
      1.            Ошибка деления
      2.            Пошаговое прерывание при отладке
      3.            Немаскируемое прерывание
      4.            Останов при отладке
      5.            Переполнение
      6.            Выход за границы массива
      7.            Недопустимый код операции
      8.            Отсутствие сопроцессора
      9.            Двойная ошибка
  10.            Превышение сегмента сопроцессором
  11.            Неверный сегмент состояния задачи
  12.            Сегмент отсутствует
  13.            Ошибка стека
  14.            Общая ошибка защиты
  15.            Страничная ошибка
  16.            Резерв
  17.            Ошибка сопроцессора
 
Операционная система берет на себя обработку исключений. Если это ошибка программы, например, деление на ноль, то программа аварийно завершается. В некоторых случаях, когда возможно исправление ошибки, после ее исправления программа продолжает выполняться. Типичным примером является исключение 11 по отсутствию сегмента. В этом случае отсутствующий сегмент ищется на диске, загружается в память и программа продолжает работу.
 
В случае возникновения ошибки, не предусмотренной исключениями 0-12 и 14-16, возникает исключение 13 – общая ошибка защиты. Это печально известная ошибка - General protection fault, часто возникающая в программах под Windows. Внешние причины этой ошибки могут быть самыми разнообразными. Ниже приведен перечень типичных причин возникновения этого исключения:
 
      1.            передача управления неисполняемому сегменту;
      2.            загрузка регистра SS селектором исполняемого сегмента или сегмента без разрешения записи:
      3.            загрузка SS, DS, ES, FS, GS селектором системного сегмента;
      4.            загрузка DS, ES, FS, GS селектором исполняемого сегмента, который не является читаемым;
      5.            передача управления с нарушением правил привилегий;
      6.            загрузка CS, SS, DS, ES, FS, GS селектором, указывающим на локальную таблицу дескрипторов, когда эта таблица не определена;
      7.            загрузка SS нулевым селектором;
      8.            превышение предела сегмента;
      9.            превышение предела таблицы;
  10.            попытка записи в сегмент, разрешенный только для чтения;
  11.            попытка чтения без разрешения из исполняемого сегмента;
  12.            попытка доступа к памяти, когда используемый сегментный регистр содержит нуль-селектор;
  13.            попытка установить страничный механизм в реальном режиме;
  14.            неправильная обработка прерывания в режиме V86;
  15.            превышение максимальной длины команды.
 
После таких ошибок программа аварийно завершает свою работу. Иногда, особенно в интерактивных программах, есть возможность исправить ошибку и вернуться к повторению действия, например, при делении на ноль, может быть, есть возможность изменить делитель. В этом случае нецелесообразно аварийно завершать работу программы. Наличие подобных ситуаций является первой причиной появления программных средств обработки исключений.
Второй причиной появления программных средств обработки исключений является стремление повысить наглядность программы, т.е. исключения рассматриваются как одно из средств структуризации программного обеспечения. Ниже рассмотрим программные средства обработки исключительных ситуаций.
 
2.7.3. Исключения в программных средах
 
Важно отметить, что речь далее пойдет только о тех исключительных ситуациях, которые программист знает, как исправить. Если программист не знает, как исправить исключительную ситуацию (это, как правило, относится к исключениям типа GPF и ко всем исключениям низкого уровня, кроме деления на ноль), то обработку этого исключения необходимо оставить системе.
Кроме того, речь здесь пойдет о новой (относительно) концепции обработки чисто программных ошибок - обработке на уровне исключений, когда о наличии ошибки программа сигнализирует исключительной ситуацией, что повышает наглядность и структурированность программы.
Для того чтобы программа могла создавать исключительные ситуации и уметь их обрабатывать система программирования должна обладать специальными средствами, о которых речь пойдет ниже.
Такими средствами обладают языки Ада, Модула-2, Си++ и Delphi.
 
Дадим общую характеристику исключений с точки зрения языка высокого уровня.
При возникновении исключения нормальное выполнение программы прекращается и инициируется работа обработчика исключения. После завершения обработки исключения выполнение программы не возобновляется в точке возникновения исключения. Процесс установления связи между конкретной ошибочной ситуацией и соответствующим обработчиком называется возбуждением исключения. При возбуждении операторы, описанные в обработчике, выполняются. Сам обработчик исключений помещается в конце блока программы. Если в данном блоке не описан обработчик исключительной ситуации, то обработка исключения передается в блок, внешний по отношению к тому блоку, в котором возбуждено исключение, и т.д. Такая передача исключения называется распространением исключения. Если и в самом внешнем блоке программы отсутствует обработчик, то обработка исключения передается среде и выполнение программы в этом случае аварийно завершается.
 
Традиционно для проверки наличия ошибочной ситуации используются явные проверки, которые загромождают программу.
 
Приведем пример работы с файлом в Паскале. Последовательность работы с файлом выглядит следующим образом:
 
Procedure FileWork;
Begin
        Открыть файл;
        Прочитать данные из файла;
        Записать данные в файл;
        Закрыть файл;
End {FileWork};
 
На каждом шаге может возникнуть ошибка ввода-вывода, приводящая к аварийному завершению программы. Например, файл отсутствует или читаем данные за концом файла.
Чтобы программа не завершалась аварийно, надо отключить проверку правильности выполнения операций ввода-вывода средой Паскаля (ключ компилятора {$I-}) и после каждой операции проверять значение переменной IOResult. То есть оформлять программу следующим образом:
 
Procedure FileWork;
Begin
        Открыть файл;
        If IOResult <> 0 Then Begin
               Writeln('Error!');
               Exit;
        End {If};
        Прочитать данные из файла;
        If IOResult <> 0 Then Begin
               Writeln('Error!');
               Exit;
        End {If};
        Записать данные в файл;
        If IOResult <> 0 Then Begin
               Writeln('Error!');
               Exit;
        End {If};
        Закрыть файл;
        If IOResult <> 0 Then Begin
               Writeln('Error!');
                Exit;
        End {If};
End {FileWork};
 
После каждой операции ввода-вывода необходимо делать явные проверки. Если таких операций много, то громоздкость программы становится очевидной и наглядность резко снижена.
Обработчик исключений позволяет отделить операторы работы с файлом от операторов проверки ошибочной ситуации и скомпоновать проверки в отдельной части программы, не перемежая их с операторами, в которых могут возникать ошибки. В общем виде это будет выглядеть для нашего примера следующим образом:
 

Procedure FileWork;
Begin
        Открыть файл;
        Прочитать данные из файла;
        Записать данные в файл;
        Закрыть файл;
        Exception :
                              Операторы обработки исключения, например, сообщение пользователю об ошибке;
End {FileWork};
 
Впервые подобный способ обработки ошибочных ситуаций был предложен в языке высокого уровня Ада и далее был распространен на более широко используемые языки. Для иллюстрации общности технологии обработки исключений в различных языках высокого уровня сначала кратко опишем обработку исключений в Аде, а затем более подробно рассмотрим принципы реализации обработки исключений в Delphi.
 
2.7.4. Обработка исключений в Аде
 
Работа с исключениями включает в себя ряд этапов.
 
1) Описание исключения:
 
Имя_исключения : exception;
Например: My_error : exception;
Существует набор предопределенных исключений, использование которых не требует предварительного описания:
CONSTRAINT_ERROR Ошибка выхода за границы массива или ссылка на неопределенный указатель
NUMERIC_ERROR Арифметическая ошибка
PROGRAM_ERROR Необрабатываемая ошибка
STORAGE_ERROR Ошибка памяти
TASKING_ERROR Ошибка взаимодействия задач
 
2) Возбуждение исключения
 
Возбуждение исключения производится с помощью оператора raise:
raise Имя_исключения;
Например,
if Ошибка then
      raise My_error;
end if;
 
3) Описание обработчика исключения
 
Обработчик исключения помещается в конце подпрограммы вслед за ключевым словом exception. Обработчик исключения имеет следующую форму:
when Имя_исключения => Последовательность операторов;
Например,
when My_error => Вывод на экран сообщения об ошибке;
Вместо имени исключения после слова when может появиться ключевое слово others, которое задает все остальные исключения, не обработанные ранее.
Например, совокупность обработчиков исключений может выглядеть следующим образом:

Procedure Example;
Begin
Последовательность операторов, которые могут возбудить исключение;
exception
when My_error => Вывод на экран сообщения об ошибке My_error;
                 Обработка ошибки My_error;
when others => Вывод на экран сообщения о неизвестной ошибке;
               raise;
End Example;
 
При правильном выполнении программы операторы, следующие за словом exception, не выполняются. При возбуждении исключения каким-либо оператором, не выполняются операторы, следующие до слова exception, а вместо них начинается выполнение операторов обработчика. Если обработчик отсутствует, то исключение возбуждается в охватывающем блоке. Если известно, как обработать исключительную ситуацию, то она обрабатывается. Если не известно, как это имеет место в случае перехода к оператору "when others", то выводится сообщение об ошибке и производится повторное возбуждение исключения для передачи обработки во внешний блок.
 
В заключении приведем пример защиты от исключительной ситуации при работе с файлом. Например, мы открыли файл и производим с ним операции ввода-вывода, а затем закрываем его. Если во время выполнения операций ввода-вывода возбуждается исключение, то поскольку естественная последовательность действий прекращается, файл может остаться незакрытым, и какие-то данные могут оказаться потерянными. Аналогичная ситуация может иметь место с динамической памятью - память выделяется, затем происходит исключение и память оказывается невозвращенной системе, т.к. нарушен естественный порядок следования операторов программы.
 
Следующий пример показывает вариант защиты от подобной ситуации с помощью обработчика исключений.
 
Procedure File_Work;
Begin
        Открытие файла; -- 1
Работа с файлом; -- 2
exception
               when others =>
               Закрытие файла; -- 4
               raise;
end;
Закрытие файла; -- 3
End File_Work;
 
Если исключение не возбуждается в процедуре, то порядок следования операторов следующий: 1, 2, 3. Если возбуждается исключение среди операторов 2, то оператором 4 закрывается файл и происходит повторное возбуждение исключения для передачи его на обработку во внешние блоки. Таким образом, файл будет закрываться как при отсутствии исключений, так и при их наличии.
Как мы сейчас увидим, те же самые концепции исключений заложены и в программном средстве Delphi.
 
2.7.5. Обработка исключений в Delphi
 
Delphi является одной из наиболее широко используемых систем программирования, поэтому рассмотрим обработку исключений в Delphi более подробно. Исключение в Delphi носит двоякий характер. С одной стороны - это традиционное понятие - сигнал об ошибочном отклонении в выполнении операторов, а с другой стороны - это объект, содержащий информацию о характере и месте ошибки. То есть здесь на понятие исключения накладывается объектно-ориентированная концепция. При возникновении исключения создается экземпляр объекта-исключения с помощью метода-конструктора, а после обработки экземпляр объекта-исключения разрушается методом-деструктором. Причем разрушение всегда производится автоматически самой средой.
На самом верхнем уровне вводится понятие надежного приложения. Надежное приложение не вылетает в среду при каждой ошибке, а пытается исправить ее внутри себя. Чтобы приложение было надежным, необходимо, чтобы оно было способно распознавать исключительные ситуации и реагировать на них. Следующим понятием является защищенный блок кода. Это блок, на котором определен отклик на исключения. Если в блоке отклик на исключение не определен, а исключение возникло, то оно передается на обработку во внешний блок и т.д. Если ни в одном из блоков исключение не определено, то оно передается на обработку среде, а приложение аварийно завершает выполнение. Защищенный блок кода описывается зарезервированными словами try ... end. Внутри этого блока помещаются операторы, на которых желательно обработать исключения, и сами операторы обработки исключений.
 
2.7.5.1. Защита ресурсов
 
Поскольку при возбуждении исключения нарушается естественный порядок следования операторов, то наиболее типичной ситуацией в этом случае является не освобождение ранее захваченных ресурсов. К таким ресурсам в первую очередь относятся файлы и память. Delphi предоставляет возможность возврата ранее захваченных ресурсов при возбуждении исключения без выяснения причин самого исключения. Для этого используется первый вариант из двух вариантов защищенного блока. Структура блока кода с защищенным от исключения выделением ресурса приведена ниже:
 
Выделение ресурса; {1}
try
Операторы, использующие ресурс, на которых может возникнуть исключение; {2}
finally
Освобождение ресурса; {3}
end;
 
Защищенный блок работает следующим образом:
 
1.      если исключения нет, то последовательность выполнения операторов следующая - 1, 2, 3;
2.      если на участке try ... finally возникает исключение, то управление немедленно передается за finally, где выполняется оператор 3. Операторы за finally называются кодом очистки.
 
Приведенный вариант защищенного блока try...finally...end является вариантом именно защиты от исключительной ситуации, а не обработкой ее. Код очистки не имеет даже информации о том, каким способом программа подошла к его выполнению – естественным порядком или через исключение.
Данный пример можно сравнить с последним примером из Ады, решающим ту же самую задачу.
Для выполнения именно обработки исключения необходимо знать причину исключения. Библиотека Delphi содержит большой набор предопределенных исключений, краткая характеристика которых представлена ниже.
 
2.7.5.2. Предопределенные исключения
 
Библиотека Delphi обладает средствами обработки следующих видов исключительных ситуаций:
 
1.      исключения ввода-вывода;
2.      исключения динамической памяти;
3.      исключения целой математики;
4.      исключения вещественной математики;
5.      исключения несовпадения типов;
6.      исключения преобразования;
7.      аппаратные исключения.
 
Рассмотрим кратко каждый из видов исключений. В большинстве случаев имя исключения понятным образом разъясняет причину исключения.
 
1.      Исключение ввода-вывода - EInOutError.
2.      Исключение динамической памяти - EOutOfMemory, EInvalidPointer.
3.      Исключение целой математики - EIntError, имеет наследников - EDivByZero, ERangeError, EIntOverflow.
4.      Исключение вещественной математики - EMathError, имеет наследников - EInvalidOp, EZeroDivide, EOverflow, EUnderflow.
5.      Исключение несовпадения типов - EInvalidCast.
6.      Исключение преобразования - EConvertError.
7.      Аппаратные исключения - EProcessorException, имеет наследников - EGPFault, EStackFault, EPageFault, EInvalidOpCode, EBreakPoint, ESingleStep.
 
Отметим, что все приведенные исключения являются наследниками объекта Exception.
 
2.7.5.3. Исключения как объекты
 
Уже говорилось о том, что при возникновении исключительной ситуации среда Delphi создает экземпляр объекта-исключения. Все объекты-исключения являются наследниками базового класса Exception. Этот базовый класс имеет следующее примерное описание:
 
Type
Exception = class(TObject)
        property Message : string read GetMessage write SetMessage;
private
        function GetMessage : string;
        procedure SetMessage(const Value : string);
public
        constructor Create(const Msg : string);
        destructor Destroy; override;
End;
 
               Свойство Message представляет собой сообщение, которое появляется в специальном диалоговом окне, когда появляется исключение, если отсутствует специальный обработчик исключения. Все перечисленные виды исключений являются наследниками Exception, т.е. описываются следующим образом, например:
 
Type
EInOutError = class(Exception)
public
        ErrorCode : Integer;
end;
 
где ErrorCode - код ошибки, указывающий на ту или иную конкретную причину, вызвавшую исключение. Собственное исключение тоже лучше определить как наследника класса Exception:
 
Type
EMyException = class(Exception);

2.7.5.4. Обработка исключений
 
Для обработки исключений следует использовать второй вариант защищенного блока следующего вида:
 
try
Операторы, которые могут вызвать исключение;
except
Операторы обработки исключения;
end;
 
Приложение выполняет операторы в except части, только если исключение имеет место при выполнении операторов в try части. Когда оператор в try части возбуждает исключение, выполнение немедленно передается в except часть.
В приведенном выше варианте в части except будут обрабатываться все исключения, даже те, на которые эта обработка не рассчитана. Поэтому в части except необходимо уточнить виды обрабатываемых исключений. Это делается следующим образом:
 
except
on <тип исключения> do <операторы>
end;
 
Рассмотрим пример процедуры с обработкой исключения:
 
function Division(Dlm, Dlt : Integer) : Integer;
begin
try
               Result := Dlm div Dlt;
except
               on EDivByZero do begin
                       Result := 0;
                       write('EDivByZero exception!');
               end;
end;
end;
 
При обработке исключения в приведенном примере можно учесть, что EDivByZero - это тип исключения, а может существовать и экземпляр исключения. Эту особенность можно использовать для чтения полей объекта-исключения, например:
 
except
        on E : EDivByZero do begin
               Result := 0;
               write(E.Message);
end;
end;
 
Здесь Е - временная переменная для хранения экземпляра исключения.
 
Еще раз напомним, что обрабатывать нужно только те исключения, которые вы хотите и знаете, как обрабатывать. Если блок не управляет некоторыми видами исключений, то при их возникновении управление передается во внешний блок с возбужденным исключением. Если и там нет обработчика, то процесс повторяется вплоть до уровня приложения. На уровне приложения выполнение аварийно завершается.

2.7.5.5. Определение собственного исключения
 
Кроме использования стандартных исключений можно создавать свои собственные исключения и их обработчики. Вообще механизм исключений должен стать стилем надежного программирования, т.е. программирования с обработкой ошибок. Определение собственного исключения необходимо выполнить в три этапа.
 
1) Описание исключения. Для создания собственного исключения вспомним, что одним из аспектов исключения является то, что это объект. Поэтому лучше всего создавать собственное исключение как наследника библиотечного класса, т.е.:
 
Type
        EMyException = class(Exception);
 
               В этом случае все свойства класса-предка принадлежат и классу-наследнику.
 
2) Возбуждение исключения. Возбуждение исключения в случае неправильных действий производится оператором следующего вида:
 
Raise EMyException.Create('EMyException!');
 
3) Обработка исключения. Обработка исключения производится аналогичным стандартному методом:
 
except
        on EMyException do begin
               write('Перекрываем собственное исключение!');
        end;
end;
 
Примеры
 
1. Пример обработки файла
 
procedure ButtonClick;
var
        F : TextFile;
S : String;
begin
AssignFile(F, 'aaa.txt');
try
               Reset(F);
               Readln(F, S);
               CloseFile(F);
except
               on E : EInOutError do begin
                       case E.ErrorCode Of
                         2: Message('Reset Error!');
                       103: Message('CloseFile Error!');
                       else
                            Message('Unrecognize Error!');
                       end {case};
               end;
end {try .. except};
end {proc};
 
Объект EInOutError содержит поле ErrorCode, по значению которого можно уточнить причину исключения.

2. Пример собственного обработчика
Type
EMyException = class(Exception);
Const
               i : integer = 0;
Procedure Action;
begin
               inc(i);
               if i mod 2 = 0 then begin
                       Raise EMyException.Create('My exception!');
               end;
end;
Procedure ButtonClick;
Begin
               Action;
End;
 
Если так написать процедуру ButtonClick, то на втором нажатии левой кнопки мыши возникнет исключение, на экране появится сообщение 'My exception!' и программа прекратит свое выполнение.
Если написать процедуру ButtonClick так, как это сделано ниже, то на каждом втором нажатии будет возбуждаться исключение и появляться сообщение 'Мой обработчик исключения!', а программа не будет прекращать свое выполнение:
 
Procedure ButtonClick;
Begin
       try
              Action;
       except
              on EMyException do
              Message('Мой обработчик исключения!');
       end;
End;
 
2.7. 6. Заключение по параграфу 2.7
 
1.      При рассмотрении исключений в Delphi не рассмотрели ряд свойств, таких как Тихие исключения, повторное возбуждение исключений, вложенные обработчики исключений.
2.      Исключения обеспечивают мощное и гибкое средство обработки ошибочных ситуаций в приложениях, которое гарантирует корректное возвращение системных ресурсов при неожиданном прекращении выполнения или дает возможность исправить ошибочную ситуацию в случае интерактивной программы.
 
Заключение по разделу 2
 
Еще раз сопоставим все пять перечисленных методов замены контекста.
 
1.      Процедуры - самый простой способ замены контекста на основе одного стека.
2.      Сопрограммы - отличаются от процедур наличием собственных стеков каждая.
3.      Процедуры ОС выполняют еще более глубокую замену контекста с включением проверки прав пользователя на этот вызов.
4.      Прерывания - это асинхронный способ замены контекста, обусловленный внешней по отношению к процессору причиной, с необязательным возвратом в прерванную программу.
5.      Исключения - это также асинхронный способ замены контекста, но обусловленный внутренними причинами - ошибками в ходе выполнения операций программы.
 
 
 
Используются технологии uCoz