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

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

При создании микропроцессора i80386 разработчики столкнулись с очень серьезной проблемой в реализации страничного механизма. Дело в том, что микропроцессор имел широкую шину адреса (32 бит), и возник вопрос о разбиении всего адреса на поле страницы и поле индекса. Если большое количество битов адреса отвести под индекс, то страницы станут очень большими, что повлечет значительные потери и на фрагментацию, и на операции ввода-вывода, связанные с замещением страниц. Хотя количество страниц стало бы при этом меньше, и накладные расходы на их поддержание тоже уменьшились бы. Если же размер страницы уменьшить, то большое поле номера страницы привело бы к потенциально громадному количеству страниц, и пришлось бы либо вводить какие-то механизмы контроля за номерами страниц (с тем, чтобы они не выходили за размеры таблицы страниц), либо создавать эти таблицы максимального размера. Разработчики пошли по пути, при котором размер страницы выбран небольшим (212 = 4096 = 4 Кбайт), а поле номера страницы величиной в 20 бит, в свою очередь, разбивается на два поля и осуществляется двухэтапная страничная трансляция.

Для описания каждой страницы создается соответствующий дескриптор. Длина дескриптора выбрана равной 32 бит: 20 бит линейного адреса определяют номер страницы (по существу — ее адрес, поскольку добавление к нему 12 нулей приводит к определению начального адреса страницы), а остальные биты разбиты на поля, показанные на рис. 4.7. Как видно, три бита дескриптора зарезервировано для использования системными программистами при разработке подсистемы организации виртуальной памяти. С этими битами микропроцессор сам не работает.

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

Старшие 10 бит линейного адреса определяют номер таблицы страниц (Page Table Entry, РТЕ), из которой посредством вторых 10 бит линейного адреса выбирается соответствующий дескриптор виртуальной страницы. И уже из этого дескриптора выбирается номер физической страницы, если данная виртуальная страница отображена на оперативную память. Эта схема определения физического адреса из линейного изображена на рис. 4.8.

Первая таблица, которую мы индексируем первыми (старшими) десятью битами линейного адреса, названа таблицей каталога таблиц страниц (Page Directory Entry, PDE). Ее адрес в оперативной памяти определяется старшими двадцатью битами управляющего регистра CRO.

Каждая из таблиц (PDE и РТЕ) состоит из 1024 элементов (2'°= 1024). В свою очередь, каждый элемент (дескриптор страницы) имеет длину 4 байт (32 бит), поэтому размер этих таблиц как раз соответствует размеру страницы. Оценим теперь эту схему трансляции с позиций расхода памяти. Каждый дескриптор описывает страницу размером 4 Кбайт. Следовательно, одна таблица страниц, содержащая 1024 дескриптора, описывает пространство памяти в 4 Мбайт. Если задача пользуется виртуальным адресным пространством, например, в 55 Мбайт (предположим, что речь идет о некотором графическом редакторе, который обрабатывает изображение, состоящее из большого количества пикселов4), то для описания этой памяти необходимо иметь 14 страниц (14 х 4 Мбайт = 56 Мбайт), содержащих таблицы РТЕ. Кроме того, нам потребуется для этой задачи еще одна таблица PDE (тоже размером в одну страницу), в которой 14 дескрипторов будут указывать на местонахождение упомянутых таблиц РТЕ. Остальные дескрипторы PDE не требуются. Итого, для описания 55 Мбайт адресного пространства задачи потребуется всего 15 страниц, то есть 60 Кбайт памяти, что можно считать приемлемым.

Если бы не был использован такой двухэтапный механизм трансляции, то потери памяти на описание адресного пространства могли бы составить 4 Кбайт х 210 = 4 Мбайт! Очевидно, что это уже неприемлемое решение.

Итак, микропроцессор для каждой задачи, для которой у него есть TSS, позволяет иметь таблицу PDE и некоторое количество таблиц РТЕ. Поскольку это дает возможность адресоваться к любому байту из 232, а шина адреса как раз и позволяет использовать физическую память с таким объемом, то можно как бы отказаться от сегментного способа адресации. Другими словами, если считать, что задача состоит из одного единственного сегмента кода и одного сегмента данных, которые, в свою очередь, разбиты на страницы, то фактически мы получаем только один страничный механизм работы с виртуальной памятью. Этот подход получил название плоской модели памяти. При использовании плоской модели памяти упрощается создание и операционных систем, и систем программирования, кроме того, уменьшаются расходы памяти на поддержку системных информационных структур. Поэтому в абсолютном большинстве современных 32-разрядных операционных систем, создаваемых для микропроцессоров i80x86, используется плоская модель памяти. Более того, появление новых 64-разрядных микропроцессоров во многом определено желанием получить большее адресное пространство, чем его имеют 32-разрядные процессоры, при сохранении возможности работать только с плоской моделью памяти.

Режим виртуальных машин для исполнения приложений реального режима

Разработчики рассматриваемого семейства микропроцессоров в своем стремлении обеспечить максимально возможную совместимость архитектуры пошли не только на то, чтобы за счет введения реального режима работы обеспечить возможность программам, созданным для первых 16-разрядных персональных компьютеров, без проблем выполняться на компьютерах с более поздними моделями микропроцессоров. Они обеспечили возможность выполнения 16-разрядных приложений реального режима при условии, что сам процессор функционирует в защищенном режиме работы, и операционная система, используя соответствующие аппаратные средства микропроцессора, организует мультипрограммный (мультизадачный) режим. Другими словами, микропроцессоры i80x86 поддерживают возможность создания операционных сред реального режима при работе микропроцессора в защищенном режиме. Если условно назвать 16-разрядные приложения DOS-приложениями (поскольку в абсолютном большинстве случаев это именно так), то можно сказать, что введена поддержка виртуальных DOS-машин, работающих вместе с обычными 32-разрядными приложениями защищенного режима. Это нашло отражение в названии такого режима работы микропроцессоров i80x86 (его называют режимом виртуального процессора i8086, иногда для краткости —режимом V86, или просто виртуальным режимом), когда в защищенном режиме работы может исполняться код DOS-приложения. Мультизадачное^ при выполнении нескольких программ реального режима поддерживается аппаратными средствами защищенного режима. Переход в виртуальный режим осуществляется посредством изменения бита VM (Virtual Mode) в регистре EFLAGS. Когда процессор находится в виртуальном режиме, для адресации памяти используется схема реального режима работы (сегмент плюс смещение) с размером сегментов до 64 Кбайт, которые могут располагаться в адресном пространстве размером в 1 Мбайт, однако полученные адреса считаются не физическими, а линейными. В результате страничной трансляции осуществляется отображение виртуального адресного пространства 16-разрядного приложения на физическое адресное пространство. Это позволяет организовать параллельное выполнение нескольких задач, разработанных для реального режима, да еще совместно с обычными 32-разрядными приложениями, требующими защищенного режима работы.

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

Вопрос, связанный с операциями ввода-вывода, которые недоступны для обычных приложений (см. следующий раздел), решается аналогично. При попытке выполнить недопустимые команды (ввода-вывода) возникают прерывания, и необходимые операции выполняются операционной системой, хотя задача об этом и «не подозревает». При выполнении команд IN, OUT, INS, OUTS, CLI, STI процессор, находящийся в виртуальном режиме и исполняющий код на уровне привилегий третьего (самого нижнего) кольца защиты, за счет возникающих вследствие этого прерываний переводится на выполнение высоко привилегированного кода операционной системы.

Таким образом, операционная система может полностью виртуализировать аппаратные5 и программные ресурсы компьютера, создавая полноценную операционную среду, отличную от себя самой, ибо существуют так называемые нативные приложения, создаваемые по собственным спецификациям данной операционной системы. Очень важным моментом для организации полноценной виртуальной машины является виртуализация не только программных, но и аппаратных ресурсов. Так, например, в Windows NT эта задача выполнена явно неудачно, тогда как в OS/2 имеется полноценная виртуальная машина как для DOS-приложений, так и для приложений, работающих в среде спецификаций Win 16. Правда, в последнее время это перестало быть актуальным, поскольку появилось большое количество приложений, работающих по спецификациям Win32 API.

Защита адресного пространства задач

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

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

Уровни привилегий для защиты адресного пространства задач

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

В микропроцессорах i80x86 режим супервизора и режим пользователя непосредственно связаны с так называемыми уровнями привилегий, причем имеется не два, а четыре уровня привилегий. Для указания уровня привилегий используются два бита, поэтому код 0 обозначает самый высший уровень, а код 3 — самый низший. Самый высший уровень привилегий предназначен для операционной системы (прежде всего для ядра ОС), самый низший — для прикладных задач пользователя. Промежуточные уровни привилегий введены для большей свободы системных программистов в организации надежных вычислений при создании операционной системы и иного системного программного обеспечения. Предполагалось, что уровень с номером (кодом) 1 может быть использован, например, для системного сервиса — программ обслуживания аппаратуры, драйверов, работающих с портами ввода-вывода. Уровень привилегий с кодом 2 может быть использован для создания пользовательских интерфейсов, систем управления базами данных и прочими, то есть для реализации специальных системных функций, которые по отношению к супервизору операционной системы ведут себя как обычные приложения. Так, например, в системе OS/2 доступны три уровня привилегий: с нулевым уровнем привилегий исполняется код супервизорной части операционной системы, на втором уровне исполняются системные процедуры подсистемы ввода-вывода, на третьем уровне исполняются прикладные задачи пользователей. Однако на практике чаще всего задействуются только два уровня — нулевой и третий. Таким образом, упомянутый режим супервизора для микропроцессоров i80x86 соответствует выполнению кода с уровнем привилегий 0, обозначаемый как PLO (Privilege Level 0 — уровень привилегий 0). Подводя итог, можно констатировать, что именно уровень привилегий задач определяет, какие команды в них можно использовать и какое подмножество сегментов и/или страниц в их адресном пространстве они могут обрабатывать.

Основными системными объектами, которыми манипулирует процессор при работе в защищенном режиме, являются дескрипторы. Именно дескрипторы сегментов содержат информацию об уровне привилегий соответствующего сегмента кода или данных. Уровень привилегий исполняющейся задачи определяется значением поля привилегий, находящегося в дескрипторе ее текущего кодового сегмента. Напомним (см. рис. 4.3), что в байте прав доступа каждого дескриптора сегмента имеется поле DPL (Descriptor Privilege Level — уровень привилегий сегмента, определяемый его дескриптором), которое и определяет уровень привилегий связанного с ним сегмента. Таким образом, поле DPL текущего сегмента кода становится полем текущего уровня привилегий (Current Privilege Level, CPL), или уровня привилегий задачи. При обращении к какому-нибудь сегменту в соответствующем селекторе указывается (см. рис. 4.4) запрашиваемый уровень привилегий (Requested Privilege Level, RPL)6.

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

Процессор сравнивает упомянутые значения CPL, RPL, DPL и на основе понятия эффективного уровня привилегий (Effective Privilege Level, EPL)7 ограничивает возможности доступа к сегментам по следующим правилам, в зависимости от того, идет ли речь об обращении к коду или к данным.

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

Если целевой сегмент является сегментом стека, то правило проверки имеет вид:

CPL = DPL = RPL

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

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

Механизм шлюзов для передачи управления на сегменты кода с другими уровнями привилегий

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

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

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

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

Введены следующие правила использования шлюзов:

·  значение DPL шлюза вызова должно быть больше или равно значению текущего уровня привилегий С PL;

·  значение DPL шлюза вызова должно быть больше или равно значению поля RPL селектора шлюза;

·  значение DPL шлюза вызова должно быть больше или равно значению DPL целевого сегмента кода;

·  значение DPL целевого сегмента кода должно быть меньше или равно значению текущего уровня привилегий CPL

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

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

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

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

·  Легко реализуется вызов программных модулей с более привилегированного уровня.

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

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

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

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

·  Необходимо проверить 8- и 16-разрядные параметры, передаваемые в 32-разрядных регистрах. Когда процедуре передается короткий параметр, его следует расширить знаковым разрядом или нулем для заполнения всего 32-разрядного регистра.

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

·  Процедура никогда не должна воспринимать как параметр код или указатель на код.

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

·  Заключительная команда RET или RET n в процедуре должна точно соответствовать полю WC (Word Counter — счетчик слов) шлюза вызова; при этом n = 4 х WC, так как счетчик задает число двойных слов, а п соответствует байтам.

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

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

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

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

Система прерываний 32-разрядных микропроцессоров i80x86

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

Работа системы прерываний в реальном режиме

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

Итак, каждый вектор прерывания состоит из четырех байтов, или двух слов: первые два содержат новое значение для регистра IP, а следующие два — новое значение для регистра CS. Таблица векторов прерывания занимает 1024 байт. Таким образом, в ней может быть задано 256 векторов прерываний. В процессоре i8086 эта таблица располагается на адресах OOOOOH-003FFH. Расположение этой таблицы в процессорах i80286 и в более поздних определяется значением регистра IDTR (Interrupt Descriptor Table Register — регистр таблицы дескрипторов прерываний). При включении или сбросе процессора i80x86 этот регистр обнуляется. Однако при необходимости можно в регистре IDTR указать смещение и таким образом перейти на новую таблицу векторов прерываний.

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

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

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

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

Внутренние прерывания, как мы уже знаем, возникают в результате работы процессора в ситуациях, которые нуждаются в специальном обслуживании, или при выполнении специальных команд (INT, INTO). Это следующие прерывания:

·  прерывание при делении на ноль (номер прерывания 0);

·  прерывание по флагу TF (Trap Flag — флаг трассировки)9 обычно используется специальными программами отладки типа DEBUG (номер прерывания 1);

·  прерывания, возникающие при выполнении команд INT (Interrupt — прерывание) и INTO (Interrupt if Overflow — прерывание по переполнению), называются программными.

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

Внешние прерывания возникают по сигналу какого-нибудь внешнего устройства. Существует два специальных внешних сигнала среди входных сигналов процессора, при помощи которых можно прервать выполнение текущей программы и тем самым переключить работу центрального процессора. Это сигналы Л/М/(Мо Mask Interrupt — немаскируемое прерывание) и INTR (Interrupt Request — запрос на прерывание). Соответственно, внешние прерывания подразделяются на немаскируемые и маскируемые.

Маскируемые прерывания генерируются контроллером прерываний по заявке определенных периферийных устройств10. Контроллер прерываний (его обозначение 18259А) поддерживает восемь уровней (линий) приоритета; к каждому уровню «привязано» одно периферийное устройство11. Маскируемые прерывания часто называют аппаратными прерываниями.

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

1.  В стек помещается регистр флагов PSW.

2.  Флаг включения-выключения прерываний IF и флаг трассировки TF, находящиеся в регистре PSW, обнуляются для блокировки других маскируемых прерываний и исключения пошагового режима исполнения команд.

3.  Значения регистров CS и IP сохраняются в стеке вслед за PSW.

4.  Вычисляется адрес вектора прерывания и из вектора, соответствующего номеру прерывания, загружаются новые значения IP и CS.

Когда системная подпрограмма принимает управление, она может разрешить снова маскируемые прерывания командой STI (Set Interrupt Flag — установить флаг прерываний), которая переводит флаг IF в состояние 1, что разрешает микропроцессору вновь реагировать на прерывания, инициируемые внешними устройствами, поскольку стековая организация допускает вложение прерываний друг в друга.

Закончив работу, подпрограмма обработки прерывания должна выполнить команду IRET (Interrupt Return), которая извлекает из стека три 16-разрядных значения и загружает их в указатель команд IP, регистр сегмента команд CS и регистр PSW соответственно. Таким образом, процессор сможет продолжить работу с того места, где он был прерван.

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

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

2.  Микропроцессор проверяет флаг IF в регистре PSW. Если он установлен в 1, то переходим к шагу 3. В противном случае работа процессора не прерывается. Часто говорят, что прерывания замаскированы, хотя правильнее говорить, что они отключены. Маскируются (запрещаются) отдельные линии запроса на прерывания посредством программирования контроллера прерываний.

3.  Микропроцессор генерирует сигнал INTA (подтверждение прерывания). В ответ на этот сигнал контроллер прерываний посылает по шипе данных номер прерывания. После этого выполняется описанная ранее процедура передачи управления соответствующей программе обработки прерывания.

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

Работа системы прерываний в защищенном режиме

В защищенном режиме работы система прерываний микропроцессора i80x86 работает совершенно иначе. Прежде всего, вместо таблицы векторов, о которой мы говорили выше, она имеет дело с таблицей дескрипторов прерываний (Interrupt Descriptor Table, IDT). Дело здесь не столько в названии таблицы, сколько в том, что таблица IDT представляет собой не таблицу с адресами обработчиков прерываний, а таблицу со специальными системными структурами данных (дескрипторами), доступ к которой со стороны пользовательских (прикладных) программ невозможен. Только сам микропроцессор (его система прерываний) и код операционной системы могут получить доступ к этой таблице, представляющей собой специальный сегмент, адрес и длина которого содержатся в регистре IDTR (см. рис. 4.2). Этот регистр аналогичен регистру GDTR в том отношении, что он инициализируется один раз при загрузке системы. Интересно заметить, что в реальном режиме работы регистр IDTR также указывает на адрес таблицы прерываний, но при этом, как и в процессоре i8086, каждый элемент таблицы прерываний (вектор) занимает всего 4 байт и содержит 32-разрядный адрес в формате селектор-.смещение (CS:IP). Начальное значение этого регистра равно нулю, но в него можно занести и другое значение. В этом случае таблица векторов прерываний будет находиться в другом месте оперативной памяти. Естественно, что перед тем, как занести в регистр IDTR новое значение, необходимо подготовить саму таблицу векторов. В защищенном режиме работы загрузку регистра IDTR может произвести только код с максимальным уровнем привилегий.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6