Задание для самостоятельного выполнения
Лабораторная работа №4. Защищенный режим работы процессора
Цель и порядок работы
Цель работы – ознакомиться с защищенным режимом работы процессора и научиться разрабатывать программы, используя этот режим.
Порядок выполнения работы:
- ознакомиться с описанием лабораторной работы;
- изучить возможности работы с защищённым режимом;
- получить задание у преподавателя;
- написать программу, ввести программу, отладить и решить ее на ЭВМ;
- оформить отчет.
Теоретические основы
Перевод микропроцессора в защищенный режим позволяет полностью реализовать все возможности, заложенные в его архитектуру и недоступные в реальном режиме. Сюда можно отнести:
- увеличение адресуемого пространства до 16 Мбайт для МП 286 и до 4 Гбайт для МП 386;
- возможность работать в виртуальном адресном пространстве, превышающем максимально возможный объем физической памяти. Для МП 286 виртуальное пространство составляет 1 Гбайт, а для МП 386 и 486 – 64 Гбайт. Правда, для реализации виртуального режима необходимы, помимо дисков большой емкости, еще и соответствующая операционная система, которая хранит все сегменты выполняемых программ в большом дисковом пространстве, автоматически загружая в оперативную память те или иные сегменты по мере необходимости;
- организация многозадачного режима с параллельным выполнением нескольких программ (процессов). Многозадачный режим организует многозадачная операционная система, однако, микропроцессор предоставляет необходимый для этого режима мощный и надежный механизм защиты задач друг от друга с помощью четырехуровневой системы привилегий;
|
|
- страничная организация памяти, повышающая уровень защиты задач друг от друга и эффективность их выполнения.
При включении микропроцессора в нем автоматически устанавливается режим реального адреса. Переход в защищенный режим осуществляется программно путем выполнения соответствующей последовательности команд. Реальный и защищенный режимы не совместимы!
Архитектура современного микропроцессора необычайно сложна. Столь же сложными оказываются и программы, использующие средства защищенного режима. Отдельные архитектурные особенности защищенного режима оказываются в достаточной степени замкнутыми и не зависящими друг от друга. Так, при работе в однозадачном режиме отпадает необходимость в изучении многообразных и замысловатых методов взаимодействия задач; во многих случаях можно отключить (или, точнее, не включать) механизм страничной организации памяти; часто нет необходимости использовать уровни привилегий. Начнем изучение защищенного режима с рассмотрения простейшей программы, которая, будучи запущена обычным образом под управлением MS-DOS, переключает процессор в защищенный режим, выводит на экран для контроля несколько символов, переходит назад в реальный режим и завершается стандартным для DOS образом.
|
|
Перед запуском программ защищенного режима следует выгрузить драйверы обслуживания расширенной памяти (типа ЕММ386.ЕХЕ) и оболочку Windows.
Для проверки работы программы удобно использовать специальные программы для эмуляции аппаратного обеспечения различных платформ. Наиболее распрстранены такие виртуальные машины как VMware, Bochs, Parallels Workstation, QEMU, Virtual PC, VirtualBox. Данные виртуальные машины являются гипервизорами (эмуляторами аппаратного обеспечения) и не оеспечивают виртуализацию приложений, как например Java Virtual Machine. В качестве основы для проведения лабораторных работ предлагается свободнораспространяемая виртуальная машина qemu с установленной операционной системой MS DOS 6.22.
Пример 4.1 – Переход в защищенный режим и обратно
;В защищенном режиме вывод фиксированных символов на экран
IDEAL ; (1) Включение режима IDEAL
P386 ; (2) Разрешение трансляции всех, в том числе
; привилегированных команд МП 386 и 486
model small ; (3)
STACK 100h ; (4) Оперделение сегмента стека и его размера
;Структура для описания дескрипторов сегментов
struc descr ; (5)
limit dw 0 ; (6)
base_l dw 0 ; (7)
base_m db 0 ; (8)
attr_1 db 0 ; (9)
attr_2 db 0 ; (10)
base_h db 0 ; (11)
ends descr ; (12)
DATASEG ; (13) Начало сегмента данных
;Таблица глобальных дескрипторов GDT
gdt_null descr <0,0,0,0,0,0> ;(14) Селектор = 0
gdt_data descr <data_size-1,0,0,92h,0,0> ;(15) Селектор = 8
gdt_code descr <code_size-1,0,0,98h,0,0> ;(16) Селектор = 16
gdt_stack descr <100h-1,0,0,92h,0,0> ;(17) Селектор = 24
gdt_screen descr <4095,8000h,0Bh,92h,0,0> ;(18) Селектор = 32
gdt_size = $-gdt_null ;(19)
;Поля данных программы
pdescr dp 0 ;(20)
sym db 1 ;(21)
attr db 1Eh ;(22)
mes db 10,13,'Real mode',"$" ;(23)
|
|
data_size = $-gdt_null ;(24)
ends ;(25)
CODESEG ; (26) Начало сегмента команд
assume cs: @code, ds:@data ; (27)
sttt equ $ ; (28)
start: ; (29)
xor eax, eax ; (30)
mov ax, @data ; (31) Инициализация реального
mov ds, ax ; (32) режима
;Вычислим 32-битовый линейный адрес сегмента данных и загрузим его в дескриптор
;сегмента данных в таблице GDT. В регистре AX уже находится сегментный адрес.
;Умножим его на 16 сдвигом влево на 4 бита с размещением результата в регистрe EAX
shl eax, 4 ; (33) Сдвинем влево EАХ на 4 бита (равносильно умножению на 16)
; Теперь в EAX 32-битовый линейный адрес сегмента данных
mov ebp, eax ; (34) Сохраняем в EBP начало сегмента
mov bx, offset gdt_data ; (35) В ВХ адрес дескриптора
mov [(descr ptr bx).base_l], ax; (36) Загрузим младшую часть базы
rol eax, 16 ; (37) Поместим циклическим сдвгом старшие биты линейного адреса
; (находящиеся в битах [19...16] регистра EАХ) в AL
mov [(descr ptr bx).base_m], al; (38) Загрузим среднюю часть базы
;Вычислим 32-битовый линейный адрес сегмента команд и загрузим его
;в дескриптор сегмента команд в таблице глобальных дескрипторов
xor eax, eax ; (39) Очистка EAX
mov ax, cs ; (40) В AX - адрес сегмента команд
shl eax, 4 ; (41) Та же процедура умножения сегментного адреса на 16 сдвигом
mov bx, offset gdt_code ; (42) BX = адрес дескриптора
mov [(descr ptr bx).base_l], ax; (43) 3агруэка младшей части базы
rol eax, 16 ; (44)
mov [(descr ptr bx).base_m], al; (45) 3агруэка средней частей базы
;Аналогично для адреса сегмента стека
xor eax, eax ; (46)
mov ax, ss ; (47)
shl eax, 4 ; (48)
mov bx, offset gdt_stack ; (49) BX = адрес дескриптора стека
mov [(descr ptr bx).base_l], ax; (50)
rol eax, 16 ; (51)
mov [(descr ptr bx).base_m], al; (52)
;Подготовим псевдодескриптор pdescr и загрузим регистр GDTR
mov [dword ptr pdescr+2], ebp ; (53) Занесем в pdescr адрес GDT
; (совпадает с адресом начала сегмента данных)
mov [word ptr pdescr], gdt_size-1 ; (54) Граница GDT
lgdt [pword ptr pdescr] ; (55) Загрузим регистр GDTR
;Подготовимся к переходу в защищенный режим
cli ; (56) Запрет аппаратных прерываний
in al, 70h ; (57) Запрет немаскируемых прерываний (NMI) через
or al, 80h ; (58) индексный порт CMOS (70h) путем установки
out 70h, al ; (59) бита 7 (10000000b=80h)
|
|
jmp $+2 ; (60) Задержка (в данном случае необязательно)
;Переход в защищенный режим
;Слово состояния машины == младшее слово регистра CR0
mov eax, CR0 ; (61) Получим слово состояния машины
or eax, 1 ; (62) Установим бит РЕ
mov CR0, eax ; (63) Запишем назад слово состояния
;Теперь процессор работает в защищённом режиме
;Загружаем в CS:IP селектор:смещение точки continue и заодно очищаем очередь команд
db 0EAh ; (64) Код команды far jmp
dw offset continue ; (65) Смещение
dw 16 ; (66) Селектор сегмента команд
continue: ; (67)
;Делаем адресуемыми данные
mov ax, 8 ; (68) Селектор сегмента данных
mov ds, ax ; (69)
;Делаем адресуемым стек
mov ax, 24 ; (70) Селектор сегмента стека
mov ss, ax ; (71)
;Инициализируем ES селектором видеобуфера и выводим символы
mov ax, 32 ; (72) Селектор сегмента видеобуфера
mov es, ax ; (73)
;Подготовка к выводу на экран
mov bx, 800 ; (74) Начальное смещение на экране
mov cx, 640 ; (75) Число выводимым символов
mov ax, [word ptr sym] ; (76) Начальный символ с атрибутом
screen: ; (77)
mov [es:bx], ax ; (78) Вывод в видеобуфер
add bx, 2 ; (79) Сместимся в видеобуфере
inc ax ; (80) Следующий символ
loop screen ; (81) Цикл вывода на экран
;Вернемся в реальный режим
mov eax, CR0 ; (82) Получим слово состояния машины
and al, 0FEh ; (83) Сбросим бит PE
mov CR0, eax ; (84) Запишем назад слово состояния
;Теперь процессор работает в реальном режиме
;Загружаем в CS:IP селектор:смещение точки return
db 0EAh ; (85) Код команды far jmp
dw offset return ; (86) Смещение точки возврата
dw @code ; (87) Сегментный адрес сегмента кода
return: ; (88)
;Восстановим операционную среду реального режима
mov ax, @data ; (89) Восстановим адресуемость
mov ds, ax ; (90) данных
mov ax, @stack ; (91) Восстановим
mov ss, ax ; (92) адресуемость
mov sp, 100h ; (93) стека
;Разрешим аппаратные и немаскируемые прерывания
sti ; (94) Разрешение прерываний
in al, 70h ; (95)
and al, 07Fh ; (96) Сброс бита 7 в порте CMOS
out 70h, al ; (97) разрешение NMI
;Проверим выполнение функций DOS после возврата в реальный режим
mov ah, 09h ; (98) Функция вывода на экран строки
mov dx, offset mes ; (99) Адрес строки
int 21h ; (100) Выэов DOS
mov ax, 4C00h ; (101) Завершим программу обычным
int 21h ; (102) образом
ends ; (103) Конец сегмента команд
code_size=$-sttt ; (104) Размер сегмента команд
end start ; (105) Конец программы
end ; (106)
32-разрядные микропроцессоры отличаются от предыдущих расширенным набором команд, часть которых относится к привилегированным. Для того чтобы разрешить транслятору обрабатывать эти команды, в текст программы необходимо включить директиву ассемблера P386.
Программа начинается с описания структуры дескриптора сегмента. В защищенном режиме для каждого сегмента программы (т.е. для сегментов команд, данных и стека) в программе должен быть определен дескриптор – 8-байтовое поле, в котором в определенном формате записываются базовый адрес сегмента, его длина и некоторые другие характеристики (рис. 4.1).
Рисунок 4.1 – Дескриптор сегмента.
Теперь для обращения к требуемому сегменту программист заносит в сегментный регистр не сегментный адрес, а так называемый селектор (рис. 4.2), в состав которого входит номер (индекс) соответствующего сегменту дескриптора. Процессор по этому номеру находит нужный дескриптор, извлекает из него базовый адрес сегмента и, прибавляя к нему указанное в конкретной команде смещение (относительный адрес), формирует адрес ячейки памяти. Индекс дескриптора (0, 1, 2 и т.д.) записывается в селектор начиная с бита 3, что эквивалентно умножению его на 8. Таким образом, можно считать, что селекторы последовательных дескрипторов представляют собой числа 0, 8, 16, 24 и т.д. (см. предложения 14…18).
Рисунок 4.2 – Селектор дескриптора.
Структура descr предоставляет шаблон для дескрипторов сегментов, облегчающий их формирование. Сравнивая описание структуры descr в программе с рис. 6.1, нетрудно проследить их соответствие друг другу.
Граница (limit) сегмента представляет собой номер последнего байта сегмента. Так, для сегмента размером 375 байт граница равна 374. Поле границы состоит из 20 бит и разбито на две части. Младшие 16 бит границы занимают байты 0 и 1 дескриптора, а старшие 4 бита входят в байт атрибутов 2, занимая в нем биты 0…3. Получается, что размер сегмента ограничен величиной 1 Мбайт. На самом деле это не так. Граница может указываться либо в байтах (и тогда, действительно, максимальный размер сегмента равен 1 Мбайт), либо в блоках по 4 Кбайт (и тогда размер сегмента может достигать 4 Гбайт). В каких единицах задается граница, определяет самый старший бит байта 7 (атрибуты 2). Этот бит называется битом дробности (гранулярности). Если он равен 0, граница указывается в байтах; если 1 - в блоках по 4 Кбайт.
База сегмента (32 бита) определяет начальный линейный адрес сегмента в адресном пространстве процессора. Линейным называется адрес, выраженный не в виде комбинации сегмент:смещение, а просто номером байта в адресном пространстве. Казалось бы, линейный адрес - это просто другое название физического адреса. Для нашего примера это так и есть, в нем линейные адреса совпадают с физическими. Если, однако, в процессоре включен блок страничной организация памяти, то процедура преобразования адресов усложняется. Отдельные блоки размером 4 Кбайт (страницы) линейного адресного пространства могут произвольным образом отображаться на физические адреса, в частности и так, что большие линейные адреса отображаются на начало физической памяти и наоборот. Страничная адресация осуществляется аппаратно (хотя для ее включения требуются определенные программные усилия) и действует независимо от сегментной организации программы. Поэтому во всех программных структурах защищенного режима фигурируют не физические, а линейные адреса. Если страничная адресация выключена, эти линейные адреса совпадают с физическими, если включена – могут и не совпадать.
Страничная организация повышает эффективность использования памяти программами, однако, практически она имеет смысл лишь при выполнении больших по размеру задач, когда объем адресного пространства задачи (виртуального адресного пространства) превышает наличный объем памяти. В рассматриваемых в лабораторных работах примерах используется чисто сегментная адресация без деления на страницы, и линейные адреса совпадают с физическими.
Поскольку в дескриптор записывается 32-битовый линейный базовый адрес (номер байта), сегмент в защищенном режиме может начинаться на любом байте, а не только на границе параграфа, и располагаться в любом месте адресного пространства 4 Гбайт.
Поле базы, как и поле границы, разбито на 2 части: биты 0…23 занимают байты 2, 3 и 4 дескриптора, а биты 24…31 - байт 7. Для удобства программного обращения в структуре descr база описывается тремя полями: младшим словом (base_l) и двумя байтами: средним (base_m) и старшим (base_h).
В байте атрибутов 1 задастся рад характеристик сегмента. Не вдаваясь пока в подробности этих характеристик, укажем, что в примере 4.1 используются сегменты двух типов: сегмент команд, для которого байт attr_1 должен иметь значение 98h, и сегмент данных (или стека) с кодом 92h.
Некоторые дополнительные характеристики сегмента указываются в старшем полубайте байта attr_2 (в частности, тип дробности). Для всех наших сегментов значение этого полубайта равно 0.
Сегмент данных @data, который для удобства изучения функционирования программы расположен в начале программы, до сегмента команд, начинается с описания важнейшей системной структуры – таблицы глобальных дескрипторов. Как уже отмечалось выше, обращение к сегментам в защищенном режиме возможно исключительно через дескрипторы этих сегментов. Таким образом, в таблице дескрипторов должно быть описано столько дескрипторов, сколько сегментов использует программа. В нашем случае в таблицу включены, помимо обязательного нулевого дескриптора, всегда занимающего первое место в таблице, четыре дескриптора для сегментов данных, команд, стека и дополнительного сегмента данных, который мы наложим на видеобуфер, чтобы обеспечить возможность вывода в него символов. Порядок дескрипторов в таблице (кроме нулевого) не имеет значения.
Помимо единственной таблицы глобальных дескрипторов (она часто называется GDT от Global Descriptor Table) в памяти может находиться множество таблиц локальных дескрипторов (LDT от Local Descriptor Table). Разница между ними в том, что сегменты, описываемые глобальными дескрипторами, доступны всем задачам, выполняемым процессором, а к сегментам, описываемым локальными дескрипторами, может обращаться только та задача, в которой эта дескрипторы описаны. Поскольку пока мы имеем дело с однозадачным режимом, локальная таблица нам не нужна.
Поля дескрипторов для наглядности заполнены конкретными данными явным образом, хотя объявление структуры descr с нулями во всех полях позволяет описать дескрипторы несколько короче, например:
gdt_null descr <> ;Селектор 0 - обязательный нулевой дескриптор
gdt_data descr <data_size-1,,,92h,,> ;Селектор 8 - сегмент данных
В дескрипторе gdt_data, описывающем сегмент данных программы, заполняется поле границы сегмента (фактическое значение размера сегмента data_size будет вычислено транслятором, см. предложение 24), а также байт атрибутов 1. Код 92h говорит о том, что это сегмент данных с разрешением записи и чтения. База сегмента, т.е. физический адрес его начала, в явной форме в программе отсутствует, поэтому ее придется программно вычислить и занести в дескриптор уже на этапе выполнения.
Дескриптор gdt_code сегмента команд заполняется схожим образом. Код атрибута 98h обозначает, что это исполняемый сегмент, к которому, между прочим, запрещено обращение с целью чтения или записи. Таким образом, сегменты команд в защищенном режиме нельзя модифицировать по ходу выполнения программы.
Дескриптор gdt_stack сегмента стека имеет, как и любой сегмент данных, код атрибута 92h, что разрешает его чтение и запись, и явным образом заданную границу – 255 байт (100h-1), что соответствует размеру стека. Базовый адрес сегмента стека так же придется вычислить на этапе выполнения программы.
Последний дескриптор gdt_screen описывает страницу 0 видеобуфера. Размер видеостраницы, как известно, составляет 4096 байт, поэтому в поле границы указано число 4095. Базовый физический адрес страницы известен, он равен 0B8000h. Младшие 16 бит базы (число 8000h) заполняют слово base_l дескриптора, биты 16…19 (число 0Bh) - байт base_m. Биты 20…31 базового адреса равны 0, поскольку видеобуфер размещается в первом мегабайте адресного пространства.
Перед переходом в защищенный режим процессору надо будет сообщить физический адрес таблицы глобальных дескрипторов и её размер (точнее, границу). Размер GDT определяется на этапе трансляции в предложении 19.
Сегмент команд @code начинается, объявлением CODESEG. Этот описатель объявляет, что в данном сегменте будут по умолчанию использоваться 16-битовые адреса и операнды. Наша программа будет запускаться под управлением DOS, которая работает в реальном режиме с 16-битовыми адресами и операндами. Указание описателя use16 не запрещает использовать в программе 32-битовые регистры.
Фактически вся программа примера 3.1, кроме ее завершающих строк, а также фрагмента, выполняемого в защищенном режиме, посвящена подготовке перехода в защищенный режим. Прежде всего, надо завершить формирование дескрипторов сегментов программы, в которых остались незаполненными базовые адреса сегментов. Базовые (32-битовые) адреса определяются путем умножения значений сегментных адресов на 16. Перед обычной инициализацией сегментного регистра DS (предложения 31-32), которая позволит нам обращаться к полям данных программы (в реальном режиме) выполняется очистка регистра EAX (предложение 30). В АХ содержится сегментный адрес сегмента данных. Командой shl (предложение 33) содержимое регистра EAX также сдвигается на 4 бита, что соответствует умножению на 16. После этого в EAX будет располагаться линейный адрес сегмента данных, совпадающий в нашем случае с физическим.
Следующей командой (предложения 36) содержимое АХ отправляется в поле base_l дескриптора gdt_data. После этого нам необходимо получить старшую часть линейного адреса сегмента (находящуюся в старшей части EAX). Для этого используется команда rol, циклически обращающая биты регистра. При сдвиге на 16 бит влево старшая и младшая (AX) части 32-х битного регистра меняются местами. Таким образом, необходимая нам часть адреса окажется в AL, откуда мы помещаем ее в поле base_m (предложение 38).
Поскольку нам еще понадобится линейный адрес сегмента данных, сохраним его предварительно в регистре EBP (предложение 34).
Аналогично вычисляются 32-битовые адреса сегментов команд и стека, помещаемые в дескрипторы gdt_code и gdt_stack.
Следующий этап подготовки к переходу в защищенный режим – загрузка в регистр процессора GDTR (Global Descriptor Table Register, регистр таблицы глобальных дескрипторов) информации о таблице глобальных дескрипторов. Эта информация включает в себя линейный базовый адрес таблицы и ее границу и размещается в 6 байтах поля данных, называемого иногда псевдодескриптором. Для загрузки GDTR существует специальная привилегированная команда lgdt (load global descriptor table, загрузка таблицы глобальных дескрипторов), которая требует указания в качестве операнда имени псевдодескриптора. Формат псевдодескриптора приведен на рисунке 4.3.
Рисунок 4.3 – Формат псевдодескриптора.
Заполнение псевдодескриптора упрощается вследствие того, что таблица глобальных дескрипторов расположена в начале сегмента данных, и ее базовый адрес совпадает с базовым адресом всего сегмента, который уже был вычислен и помещен в дескриптор gdt_data. В предложении 53 компоненты базового адреса переносятся из дескриптора в требуемые поля pdescr, а в предложении 54 заполняется поле границы. Команда lgdt загружает регистр GDTR и сообщает процессору о местонахождении и размере GDT.
Мы запускаем программу под управлением DOS, и естественно завершить ее также обычным образом, чтобы не нарушить работоспособность системы. Но в защищенном режиме запрещены любые обращения к функциям DOS или BIOS. Причина этого совершенно очевидна – и DOS, и BIOS являются программами реального режима, в которых широко используется сегментная адресация реального режима, т.е. загрузка в сегментные регистры сегментных адресов. В защищенном же режиме в сегментные регистры загружаются не сегментные адреса, а селекторы. Кроме того, обращение к функциям DOS и BIOS осуществляется с помощью команд программного прерывания int с определенными номерами, а в защищенном режиме эти команды приведут к совершенно иным результатам. Таким образом, программу, работающую в защищенном режиме, нельзя завершить средствами DOS. Сначала ее надо вернуть в реальный режим.
Возврат в реальный режим можно осуществить двумя способами.
Первый способ – программный сброс процессора. Действия процессора после сброса определяются одной из ячеек КМОП-микросхемы – байтом состояния отключения, располагаемым по адресу 0Fh. В частности, если в этом байте записан код 0Ah, после сброса управление немедленно передается по адресу, который извлекается из двухсловной ячейки 40h:67h, расположенной в области данных BIOS. Таким образом, для подготовки возврата в реальный режим мы должны в ячейку 40h:67h записать адрес возврата, а в байт 0Fh КМОП-микросхемы занести код 0Ah. Точка возврата может располагаться в любом месте программы.
Сброс процессора выполняется засылкой команды FEh в порт 64h контроллера клавиатуры. Эта команда возбуждает сигнал на одном из выводов контроллера клавиатуры, который приводит к появлению сигнала сброса на выводе RESET микропроцессора. Перед выполнением сброса текущее содержимое SP сохраняется в ячейке rеal_sp, чтобы после перехода в реальный режим можно было восстановить состояние стека.
После сброса процессор начинает работать в реальном режиме, причем управление передается программам BIOS. BIOS анализирует содержимое байта состояния отключения (0Fh) КМОП - микросхемы и, поскольку мы записали туда код 0Ah, осуществляет передачу управления по адресу, хранящемуся в ячейке 40h:67h области данных BIOS. В нашем случае переход осуществляется на метку return. Команда hlt (halt, останов) позволяет организовать ожидание сброса процессора, который выполняется не мгновенно. Вместо команды hlt можно было использовать бесконечный цикл. Если команду ожидания опустить, процессор до своего останова успеет выполнить несколько следующих команд, после чего все-таки передаст управление на адрес возврата.
Другой способ, выглядит несколько сложнее. Он заключается в восстановлении состояния процессора, его регистров, определенных областей памяти в исходное состояние. То есть, по сути, необходимо выполнить процесс обратный подготовке переходу в защищенный режим.
Еще одна важная операция, которую необходимо выполнить перед переходом в защищенный режим, заключается в запрете всех аппаратных прерываний. В защищенном режиме процессор выполняет процедуру прерывания не так, как в реальном. При поступлении сигнала прерывания процессор не обращается к таблице векторов прерываний в первом килобайте памяти, как в реальном режиме, а извлекает адрес программы обработки прерывания из таблицы дескрипторов прерываний, построенной схоже с таблицей глобальных дескрипторов и располагаемой в программе пользователя (или в операционной системе). В примере 6.1 такой таблицы нет, и на время работы программы прерывания придется запретить. Запрет всех аппаратных прерываний осуществляется командой cli (предложение 56). Однако желательно запретить еще и немаскируемые прерывания, которые поступают в процессор по отдельной линии (вход NMI микропроцессора) и не управляются битом IF регистра флагов. Немаскируемые прерывания обычно используются для обработки таких катастрофических событий, как сбой питания, ошибка памяти или ошибка четности на магистрали; в реальном режиме для них зарезервирован вектор 02h. Для запрета немаскируемых прерываний не предусмотрено никаких специальных команд, однако, это можно сделать, установив старший бит в адресном (индексном) порте 70h КМОП-микросхемы.
В предложениях 57-59 из порта 70h читается текущее состояние КМОП-микросхемы, и запрещает немаскируемые прерывания установкой старшего (7-ого) бита и отправкой значения обратно в порт.
Для перехода могут быть использованы команды smsw (Store Machine Status Word, запись слова состояния машины) и lmsw (Load Machine Status Word, загрузка слова состояния машины) работающие с младшими 16 битами регистра CR0, называемыми словом состояния машины. Однако они практически не используются и существуют для совместимости с процессором 80286. Вместо них всегда удобнее использовать mov cr0,еах (вместо lmsw) и mov еах,cr0 (вместо smsw).
Переход в защищенный режим осуществляется установкой в 1 бита 0 регистра CR0, называемого PE (Protection Enabled). Поскольку остальные биты этого слова нам могут быть не известны, сначала мы читаем в регистр EAX содержимое CR0, затем устанавливаем в нем бит 0 и, наконец, записываем модифицированное значение назад в регистр CR0 процессора. Все последующие команды выполняются уже в защищенном режиме.
В предложениях 61…63 осуществляется перевод процессора в защищенный режим.
Хотя защищенный режим установлен, однако действия по настройке системы еще не закончены. Действительно, во всех используемых в программе сегментных регистрах хранятся не селекторы дескрипторов сегментов, а базовые сегментные адреса, не имеющие смысла в защищенном режиме. Между прочим, отсюда можно сделать вывод, что после перехода в защищенный режим программа не должна работать, так как в регистре CS пока еще нет селектора сегмента команд, и процессор не может обращаться к этому сегменту. В действительности это не совсем так.
В процессоре для каждого из сегментных регистров имеется так называемый теневой регистр дескриптора, который имеет формат дескриптора. Теневые регистры недоступны программисту; они автоматически загружаются процессором из таблицы дескрипторов каждый раз, когда процессор загружает соответствующий сегментный регистр. Таким образом, в защищенном режиме программист имеет дело с селекторами, т.е. номерами дескрипторов, а процессор – с самими дескрипторами, хранящимися в теневых регистрах. Именно содержимое теневого регистра (в первую очередь, линейный адрес сегмента) определяет область памяти, к которой обращается процессор при выполнении конкретной команды.
В реальном режиме теневые регистры заполняются не из таблицы дескрипторов, а непосредственно самим процессором. В частности, процессор заполняет поле базы каждого теневого регистра линейным базовым адресом сегмента, полученным путем умножения на 16 содержимого сегментного регистра, как это и положено в реальном режиме. Поэтому после перехода в защищенный режим в теневых регистрах находятся правильные линейные базовые адреса, и программа будет выполняться правильно, хотя с точки зрения правил адресации защищенного режима содержимое сегментных регистров лишено смысла.
Тем не менее, после перехода в защищенный режим, следует загрузить в используемые сегментные регистры (и, в частности, в регистр CS) селекторы соответствующих сегментов. Это позволит процессору правильно заполнить все поля теневых регистров из таблицы дескрипторов. Пока эта операция не выполнена, некоторые поля теневых регистров (в частности, границы сегментов) могут содержать неверную информацию.
Загрузить селекторы в сегментные регистры DS, SS и ES не представляет труда (предложения 68…73). Но как загрузить селектор в программно недоступный регистр CS? Для этого можно воспользоваться искусственно сконструированной командой дальнего перехода, которая, как известно, приводит к смене содержимого и IP, и CS. Предложения 64…66 демонстрируют эту методику. В реальном режиме мы поместили бы во второе слово адреса в сегментный адрес сегмента команд, в защищенном же мы записываем в него селектор этого сегмента (число 16).
Команда дальнего перехода, помимо загрузки в CS селектора, выполняет еще одну функцию – она очищает очередь команд в блоке предвыборки команд процессора. Как известно, в современных процессорах с целью повышения скорости выполнения программы используется конвейерная обработка команд программы, позволяющая совместить во времени фазы их обработки. Одновременно с выполнением текущей (первой) команды осуществляется выборка операндов следующей (второй), дешифрация третьей и выборка из памяти четвертой команды. Таким образом, в момент перехода в защищенный режим уже могут быть расшифрованы несколько следующих команд и выбраны из памяти их операнды. Однако эти действия выполнялись, очевидно, по правилам реального, а не защищенного режима, что может привести к нарушениям в работе программы. Команда перехода очищает очередь предвыборки, заставляя процессор заполнить ее заново уже в защищенном режиме.
Следующий фрагмент примера (предложения 72…81) является чисто иллюстративным. В нем инициализируется (по правилам защищенного режима) сегментный регистр ES и в видеобуфер экрана выводится некоторое количество цветных символов, чем подтверждается правильное функционирование программы в защищенном режиме.
Чтобы не нарушить работоспособность DOS, процессор следует вернуть в реальный режим, после чего можно будет завершить программу обычным образом.
Для этого необходимо перевести процессор обратно в реальный режим адресации, сбросив бит PE регистра CR0 (предложения 82…84). После этого необходимо загрузить в сегментный регистр CS корректное значение сегментного адреса сегмента кода, а в регистр IP значение смещения точки возврата (предложения 85…87). В нашем случае переход осуществляется на метку return (предложение 88).
Поскольку сегментные регистры содержат селекторы защищенного режима, их следует инициализировать заново, что и выполняется в предложениях 89…93. Заметим, что сохранение и восстановление указателя стека SP не является обязательным, во всяком случае, в нашем примере, где работа программы до перехода в защищенный режим и после возврата из него протекает независимо. Поэтому после возврата в реальный режим можно инициализировать SP заново, выполнив команду mov SP, 256 (в предположении, что стек имеет размер 256 байт).
Для восстановления работоспособности системы следует разрешить маскируемые (предложение 94) и немаскируемые прерывания (предложения 95…97), после чего программа может продолжаться уже в реальной режиме. В рассматриваемом примере для проверки работоспособности системы на экран выводится некоторый текст с помощью функции DOS 09h.
Программа завершается обычным образом функцией DOS 4Ch. Нормальное завершение программы и переход в DOS тоже в какой-то мере свидетельствует о ее правильности.
У рассмотренной программы имеется серьезный недостаток – полное отсутствие средств отладки. Для отладки программ защищенного режима используется механизм прерываний и исключений, в нашей же программе этот механизм не активизирован. Поэтому всякие неполадки при работе в защищенном режиме, которые с помощью указанного механизма можно было бы обнаружить и проанализировать, в данном случае будут приводить к сбросу процессора. Однако после сброса программа, скорее всего, будет работать правильно: на экран будет выведена запланированная строка и программа завершится с передачей управления DOS. Таким образом, критерием программных ошибок защищенного режима может служить правильная в целом работа программы при отсутствии на экране цветных символов, которые должны выводиться в защищенном режиме.
Задание для самостоятельного выполнения
1. Изучить возможности работы в защищённом режиме.
2. Написать программу, переходящую в защищенный режим, выводящую в нем несколько символов на экран, выходящую из него и сигнализирующую об этом. При написании обратить внимание на следующие этапы:
2.1. Определение структуры дескриптора.
2.2. Определение таблицы дескрипторов и дескрипторов используемых сегментов.
2.3. Подготовка таблицы дескрипторов к переходу в защищенный режим.
2.4. Подготовка псевдодескриптора и инициализация регистра GDTR
2.5. Запрет аппаратных и немаскируемых прерываний
2.6. Непосредственно переход в защищенный режим
2.7. Подготовка к работе в защищенном режиме и вывод в видеобуфер
2.8. Подготовка к возврату и возврат в реальный режим
2.9. Восстановление операционной среды реального режима
3. Отладить и протестировать полученную программу.
4. Оформить отчёт.
Контрольные вопросы
1. Каков формат дескриптора?
2. Каков формат псевдодескриптора?
3. Как переключиться в защищенный режим?
4. Какова структура таблицы дескрипторов?
5. Какова модель памяти при работе в защищенном режиме?
Дата добавления: 2021-12-10; просмотров: 21; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!