logo
АрхВС

8) Исполнение программного кода. Переключение задач и виртуальные машины. Защищенный режим и виртуальная память

Программный код — это последовательность команд, или инструкций, каждая из которых определенным образом закодирована и расположена в целом числе смежных байтов памяти. Каждая инструкция обязательно имеет операционную часть, несущую процессору информацию о требуемых действиях. Операндная часть, указывающая процессору, где находится его «предмет труда» — операнды, — может присутствовать в явном или неявном виде и даже отсутствовать. Операндная часть может описывать от нуля до двух операндов, участвующих в выполнении данной инструкции (есть инструкции, в которых, помимо двух операндов, задается еще и параметр инструкции). Здесь могут быть сами значения операндов (непосредственные операнды); явные или неявные указания на регистры процессора, в которых находятся операнды; адрес (или его составная часть) ячейки памяти или порта ввода-вывода; регистры процессора, участвующие в формировании адреса, и разные комбинации этих компонентов. Инструкции могут предшествовать префиксы (к счастью, всегда однобайтные, но их может быть несколько), указывающие на изменение способа адресации, размера операнда или/и необходимость многократного (по счетчику и условию) повторения для данной инструкции. Адрес (логический) текущей исполняемой инструкции хранится в специальном регистре — указателе инструкций (Instruction Pointer, IP), который соответствует счетчику команд фон-неймановской машины. После исполнения так называемой линейной инструкции этот указатель увеличивает свое значение на ее длину, то есть указывает на начало следующей инструкции. Линейная инструкция не нарушает порядок выполнения инструкций, определяемый последовательностью их расположения в памяти (по нарастанию адреса). Помимо линейных инструкций существуют инструкции передачи управления, среди которых различают инструкции переходов и вызовов процедур. Эти инструкции в явном или неявном виде содержат информацию об адресе следующей выполняемой инструкции, который может указывать на относительно произвольную ячейку памяти. Инструкции переходов и вызовов могут быть безусловными (ни от чего не зависящими) и условными. Произойдет ли условный переход (вызов) или нет, зависит от состояния флагов (признаков) на момент исполнения данной инструкции. Если переход (вызов) не состоится, то исполняется инструкция, расположенная в памяти вслед за текущей. Вызов процедуры характерен тем, что перед ним процессор сохраняет в стеке (стек — это область ОЗУ) адрес следующей инструкции, и на этот адрес передается управление после завершения исполнения процедуры (этот адрес извлекается из стека при выполнении инструкции возврата). Переход выполняется безвозвратно.

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

Прерывания используют и для переключения задач в многозадачных системах. Пусть, например, имеются два потока инструкций (например, две прикладные программы), которые должны выполняться как бы одновременно (по-настоя- щему одновременно один фон-неймановский процессор их выполнить не может). Можно запустить один поток, а через некоторое время его работы при аппаратном прерывани (от таймера) сохранить в памяти образ его текущего состояния (все регистры, программно-доступные этому процессу) и запустить другой поток. Через некоторое время при следующем прерывании выполнить обратное переключение: сохранить состояние второго потока (в другом месте памяти), загрузить в регистры процессора образ состояния первого потока и продолжить его выполнение. Эти переключения задач следует выполнять в течение исполнения обоих программ с частотой, создающей у пользователя иллюзию непрерывности и одновременности. Понятно, что ресурсы процессора (производительность) в этом случае делятся между задачами пропорционально выделяемым им квантам времени. Чтобы пользователя такая производительность процессов удовлетворяла (а еще учтем накладные расходы на сохранение и восстановление образов при переключениях), у процессора должна быть достаточная мощность. Процессоры семейства х86, начиная со второго и особенно с третьего (386) поколения, имеют встроенные средства поддержки многозадачности (число задач почти не ограничено), работающие в защищенном режиме. Переключение задач производится по сигналу прерывания от таймера совершенно «прозрачно» для процессов, работающих псевдопараллельно. Благодаря этой прозрачности программисту, разрабатывающему прикладную программу, в большинстве случаев не надо заботиться об обеспечении многозадачности. В распоряжение его программы предоставляется виртуальная машина (тоже фон-неймановская), в которой управление передается последовательно этой программой, как будто она — единственная. Конечно, поддержка виртуальных машин требует определенных усилий со стороны многозадачной операционной системы, которой приходится распределять не только процессорное время, но и память, устройства хранения, ввода-вывода и коммуникационные устройства — то есть все ресурсы реального компьютера. В этом ей помогают специальные средства, введенные в процессоры х86 2-3-го поколений и постоянно развиваемые в следующих поколениях.

Широко известно, что первым микропроцессором, на базе которого была создана IBM PC, был Intel 8088. Этот микропроцессор отличался от первого 16-разрядного микропроцессора фирмы Intel -- 8086 -- прежде всего тем, что у него была 8-битовая шина данных, а не 16-битовая (как у 8086). Оба эти микропроцессора предназначались для создания вычислительных устройств, которые бы работали в однозадачном режиме, то есть специальных аппаратных средств для поддержки надежных и эффективных мультипрограммных ОС в них не было.

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

Подробно рассматривать архитектуру первых 16-битовых микропроцессоров i8086/i8088 мы не будем, поскольку этот материал должен изучаться в предыдущих дисциплинах учебного плана. Для тех же, кто с ним не знаком, можно рекомендовать такие книги, как [52, 73], и многие другие. Однако напомним, что в этих микропроцессорах (а значит, и в остальных микропроцессорах семейства i80x86 при работе их в реальном режиме) обращение к памяти с возможным адресным пространством в 1 Мбайт осуществляется посредством механизма сегментной адресации (рис. 3.1). Этот механизм был использован для увеличения количества разрядов, участвующих в указании адреса ячейки памяти, с которой в данный момент осуществляется работа, с 16 до 20 и тем самым увеличения объема памяти.

Конкретизируем задачу и ограничимся рассмотрением определения адреса команды. Для адресации операндов используется аналогичный механизм, только участвуют в этом случае другие сегментные регистры. Напомним, что для определения физического адреса команды содержимое сегментного регистра CS (code segment) умножается на 16 за счет добавления справа (к младшим битам) четырех нулей, после чего к полученному значению прибавляется содержимое указателя команд (регистр IP, instruction pointer). Получается двадцатибитовое значение, которое и позволяет указать любой байт из 220.

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

Для изучения этих возможностей рассмотрим сначала новые архитектурные возможности микропроцессоров i80x86.