P ; Разрешение трансляции всех (в том
Числе и привилегированных команд
Процессоров 386 и 486
Структура для дескриптора сегмента
Descr struc
Lim dw 0 ; Граница (биты 0 - 15)
Base_1 dw 0 ; База (биты 0 - 15)
Base_m db 0 ; База (биты 16 - 23)
Attr_1 db 0 ; Байт атрибутов 1
Attr_2 db 0 ; Граница (биты 16 - 19)
И атрибуты 2
Base_h db 0 ; База (биты 24 - 31)
Descr ends
Data segment use16
Таблица глобальных дескрипторов GDT
Селектор 0 – обязательный нулевой дескриптор
21 gdt_null descr <0,0,0,0,0,0>
Селектор 8 - сегмент данных
23 gdt_data descr <data_size-1,0,0,92h,0,0>
Селектор 16 - сегмент кода
25 gdt_code descr <code_size-1,0,0,98h,0,0>
Селектор 24 – сегмент стека
27 gdt_stack descr <255,0,0,92h,0,0>
Селектор 32 - видеобуфер
29 gdt_screen descr <4095,8000h,0bh,92h,0,0>
30 gdt_size=$-gdt_null ; Размер GDT
31 ;======================================================
Поля данных программы
Pdescr dq 0 ; Псевдодескриптор для команды lgdt
Sym db 1 ; Символ для вывода на экран
Attr db 1ah ; Атрибут символа
36 mes db 27,'[31;42mReal mode now',27,’[0m’,10,13,'$'
Mes1 db 26 dup(32),'A message in protected mode',27 dup(32),0
38 data_size=$-gdt_null ; Размер сегмента данных
Data ends
40 ;=========================================================
Text segment 'code' use16 ; По умолчанию 16-разрядный режим
Assume cs:text,ds:data
Main proc
Xor eax,eax ; Очистка 32-разр. EAX
Mov ax,data ; Инициализация сегментного регистра
Mov ds,ax ; для реального режима
Вычисление 32-битного линейного адреса сегмента данных и загрузка
Его в дескриптор (в EAX уже находится его сегментный адрес).
Для умножения его на 16 сдвинем его влево на 4 разряда
Shl eax,4 ; В ЕAX - линейный базовый адрес
Mov ebp,eax ; Сохранение его в EBP
Mov bx,offset gdt_data ; В ВХ адрес дескриптора
53 mov [bx].base_1,ax ; Мл. часть базы
Rol eax,16 ; Обмен старшей и младшей половины EAX
55 mov [bx].base_m,al ; Средняя часть базы
Вычисление и загрузка 32-битного линейного адреса сегмента команд
Xor eax,eax ; Очистка 32-разр. EAX
Mov ax,cs ; Адрес сегмента команд
Shl eax,4 ; В ЕAX - линейный базовый адрес
Mov bx,offset gdt_code ; В ВХ адрес дескриптора
61 mov [bx].base_1,ax ; Мл. часть базы
Rol eax,16 ; Обмен старшей и младшей половины EAX
63 mov [bx].base_m,al ; Средняя часть базы
Вычисление и загрузка 32-битного линейного адреса сегмента стека
Xor eax,eax
Mov ax,ss
Shl eax,4
Mov bx,offset gdt_stack
69 mov [bx].base_1,ax
Rol eax,16
71 mov [bx].base_m,al
Подготовка псевдодескриптора и загрузка его в регистр GDTR
73 mov dword ptr pdescr+2,ebp ; База GDT (0-31)
Mov word ptr pdescr,gdt_size-1 ; Граница GDT
Lgdt pdescr ; Загрузка регистра GDTR
Подготовка к переходу в защищенный режим
Cli ; Запрет маскир. прерываний
Mov al,80h ; Запрет NMI
Out 70h,al ; Порт КМОП микросхемы
Переход в защищенный режим
Mov eax,cr0 ; Чтение регистра состояния
Or eax,1 ; Взведение бита 0
Mov cr0,eax
84 ;*************************************************
85 ;* Теперь процессор работает в защищенным режиме *
86 ;*************************************************
Загрузка в CS селектор сегмента кода, а в IP смещения следующей
Команды (при этом и очищается очередь команд)
Db 0eah ; Код команды far jmp
Dw offset continue ; Смещение
Dw 16 ; Селектор сегмента команд
92 continue:
Инициализация селектора сегмента данных
Mov ax,8 ; Селектор сегмента данных
Mov ds,ax
Инициализация селектора сегмента стека
Mov ax,24 ; Селектор сегмента стека
Mov ss,ax
Инициализация селектора ES и вывод символов на экран
Mov ax,32 ; Селектор сегмента видеобуфера
Mov es,ax
Mov ebx,800 ; Начальное смещение на экране
Вывод сообщения на экран
Lea esi,mes1
Mov ah,attr
106 screen:
107 mov al,[esi]
Or al,al
Jz scend ; Выход, если нуль (терминатор сообщения)
110 mov es:[bx],ax ; Вывод символа в видеобуфер
Add ebx,2 ; Следующий адрес на экране
Inc esi ; Следующий символ
Jmp screen ; Цикл
Scend: ; Конец вывода сообщения
Подготовка перехода в реальный режим
116 ;*****************************************************
Формирование и загрузка дескрипторов для реального режима
Mov gdt_data.lim,0ffffh ; Запись значения
Mov gdt_code.lim,0ffffh ; границы в 4 ис-
Mov gdt_stack.lim,0ffffh ; пользуемых нами
Mov gdt_screen.lim,0ffffh ; дескриптора
Для перенесения этих значений в теневые регистры необходимо
Записать в сегментные регистры соответствующие селекторы
Mov ax,8 ; Загрузка теневого регистра
Mov ds,ax ; сегмента данных
Mov ax,24 ; Загрузка теневого регистра
Mov ss,ax ; сегмента стека
Mov ax,32 ; Загрузка теневого регистра
Mov es,ax ; дополнительного сегмента
Сегментный регистр CS программно недоступен, поэтому его
Загрузку опять выполняем косвенно с помощью искусственно
Сформированной команды дальнего перехода
Db 0eah ; Код команды дальнего перехода
Dw offset go ; Смещение
Dw 16 ; Селектор сегмента кода
Переключение режима процессора
Go: mov eax,cr0 ; Чтение cr0
And eax,0fffffffeh ; Сброс бита 0
Mov cr0,eax ; Запись cr0
Db 0eah ; Код команды дальнего перехода
Dw return ; Смещение
Dw text ; Сегмент кода
143 ;*****************************************************
144 ;* Теперь процессор опять работает в реальном режиме *
145 ;*****************************************************
Восстановление операционной среды реального режима
147 return:
Mov ax,data ; Инициализация сегментных регистров
Mov ds,ax ; данных и
Mov ax,stk ; стека
Mov ss,ax ; в реальном режиме
Мы не восстанавливает содержимое SP, так как при таком мягком (без
Сброса) переходе в реальный режим SP не разрушается
Разрешение всех прерываний
Sti ; Разрешение маск. прерываний
Mov al,0 ; Сброс бита 7 порта 70 КМОП -
Out 70h,al ; разрешение NMI
Вывод сообщения в реальном режиме
Mov ah,9 ; Вывод сообщения
Mov dx,offset mes ; функцией DOS
Int 21h
Ожидание нажатия клавиши
Xor ah,ah
Int 16h
Завершение программы
Mov ax,4c00h
Int 21h
Main endp
169 code_size=$-main
Text ends
171 stk segment stack 'stack'
Db 256 dup(0)
Stk ends
End main
К тексту программы добавлены номера строк, которые облегчат описание отдельных команд в тексте. Следует иметь в виду, что перед трансляцией показанного текста, необходимо удалить все номера строк, так как компилятор на каждый номер будет выдавать ошибку.
32-разрядные микропроцессоры отличаются расширенным набором команд, часть которых относится к привилегированным. Для того, чтобы разрешить транслятору обрабатывать эти команды, в текст программы необходимо включить директиву ассемблера .386Р.
Программа начинается с описания структуры дескриптора сегмента. В отличие от реального режима, в котором сегменты определяются их базовыми адресами, задаваемыми программистом в явной форме, в защищенном режиме для каждого сегмента программы должен быть определен дескриптор – 8-байтовое поле, в котором в определенном формате записываются базовый адрес сегмента, его длина и некоторые другие характеристики (рисунок 1.8) [8].
Рисунок 1.8 – Дескриптор сегмента
Рисунок 1.9 – Селектор дескриптора
Теперь для обращения к требуемому сегменту программист заносит в сегментный регистр не сегментный адрес, а так называемый селектор (рисунок 1.9), в состав которого входит номер (индекс) соответствующего сегмента дескриптора.
Процессор по этому номеру находит нужный дескриптор, извлекает из него базовый адрес сегмента и, прибавляя к нему указанное в конкретной команде смещение (относительный адрес), формирует адрес ячейки памяти. Индекс дескриптора (0, 1, 2 и т.д.) записывается в селектор, начиная с бита 3, что эквивалентно умножению его на 8. Таким образом, можно считать, что селекторы последовательных дескрипторов представляют собой числа 0, 8, 16, 24 и т.д. Другие поля селектора, которые для нашего случая принимают значения 0, будут описаны ниже.
Структура descr (строка 8 листинга 1.1) предоставляет шаблон для дескрипторов сегментов, облегчающий их формирование. Сравнивая описание структуры descr в программе с рисунком 1.8, нетрудно заметить их соответствие друг другу.
Рассмотрим вкратце содержимое дескриптора. Граница (limit) сегмента представляет собой номер последнего байта сегмента. Так, для сегмента размером 375 байт граница равна 374. Поле границы состоит из 20 бит и разбито на две части. Как видно из рисунка 1.8, младшие 16 бит границы занимают байты 0 и 1 дескриптора, а старшие 4 бита входят в байт атрибутов 2, занимая в нем биты 0...3. Получается, что размер сегмента ограничен величиной 1 Мбайт. На самом деле это не так. Граница может указываться либо в байтах (и тогда, действительно, максимальный размер сегмента равен 1 Мбайт), либо в блоках по 4 Кбайт (и тогда размер сегмента может достигать 4 Гбайт). В каких единицах задается граница, определяет старший бит байта атрибутов 2, называемый битом дробности. Если он равен 0, граница указывается в байтах; если 1 – в блоках по 4 килобайта.
База сегмента (32 бита) определяет начальный линейный адрес сегмента в адресном пространстве процессора. Линейным называется адрес, выраженный не в виде комбинации сегмент-смещение, а просто номером байта в адресном пространстве. Казалось бы, линейный адрес – это просто другое название физического адреса. Для нашего примера это так и есть, в нем линейные адреса совпадают с физическими. Однако если в процессоре включен блок страничной организация памяти, то процедура преобразования адресов усложняется. Отдельные блоки размером 4 Кбайт (страницы) линейного адресного пространства могут произвольным образом отображаться на физические адреса, в частности и так, что большие линейные адреса отображаются на начало физической памяти, и наоборот. Страничная адресация осуществляется аппаратно (хотя для ее включения требуются определенные программные усилия) и действует независимо от сегментной организации программы. Поэтому во всех программных структурах защищенного режима фигурируют не физические, а линейные адреса. Если страничная адресация выключена, эти линейные адреса совпадают с физическими, если включена – могут и не совпадать.
Страничная организация повышает эффективность использования памяти программами, однако практически она имеет смысл лишь при выполнении больших по размеру задач, когда объем адресного пространства задачи (виртуального адресного пространства) превышает наличный объем памяти. В рассмотренном примере используется чисто сегментная адресация без деления на страницы, и линейные адреса совпадают с физическими.
Поскольку в дескриптор записывается 32-битовый линейный базовый адрес (номер байта), сегмент в защищенном режиме может начинаться на любом байте, а не только на границе параграфа, и располагаться в любом месте адресного пространства 4 Гбайт.
Поле базы, как и поле границы, разбито на 2 части: биты 0...23 занимают байты 2, 3 и 4 дескриптора, а биты 24...31 – байт 7. Для удобства программного обращения в структуре descr база описывается тремя полями: младшим словом (base_l – строка 10 листинга) и двумя байтами: средним (base_m – строка 11 листинга) и старшим (base_h – строка 15 листинга).
В байте атрибутов 1 задается ряд характеристик сегмента. Не вдаваясь пока в подробности этих характеристик, укажем, что в рассмотренном примере используются сегменты двух типов: сегмент команд, для которого байт attr_1 (строка 12 листинга) должен иметь значение 98h, и сегмент данных (или стека) с кодом 92h.
Некоторые дополнительные характеристики сегмента указываются в старшем полубайте байта attr_2 (в частности, бит дробности). Для всех наших сегментов значение этого полубайта равно 0.
Сегмент данных data (строка 18 листинга), который для удобства изучения функционирования программы расположен в начале программы, до сегмента команд, объявлен с типом использования use16 (так же будет объявлен и сегмент команд). Этот описатель объявляет, что в данном сегменте будут по умолчанию использоваться 16-битовые адреса. Если бы мы готовили нашу программу для работы под управлением операционной системы защищенного режима, реализующей все возможности микропроцессора, тип использования был бы use32. Однако наша программа будет запускаться под управлением DOS, которая работает в реальном режиме с 16-битовыми адресами и операндами.
Сегмент данных начинается с описания важнейшей системной структуры – таблицы глобальных дескрипторов. Как уже отмечалось выше, обращение к сегментам в защищенном режиме возможно только через дескрипторы этих сегментов. Таким образом, в таблице дескрипторов должно быть описано столько дескрипторов, сколько сегментов использует программа. В нашем случае в таблицу включены, помимо обязательного нулевого дескриптора, всегда занимающего первое место в таблице, четыре дескриптора для сегментов данных, команд, стека и дополнительного сегмента данных, который мы наложим на видеобуфер, чтобы обеспечить возможность вывода в него символов. Порядок дескрипторов в таблице (кроме нулевого) не имеет значения.
Помимо единственной таблицы глобальных дескрипторов, обозначаемой GDT от Global Descriptor Table, в памяти может находиться множество таблиц локальных дескрипторов (LDT от Local Descriptor Table). Разница между ними в том, что сегменты, описываемые глобальными дескрипторами, доступны всем задачам, выполняемым процессором, а к сегментам, описываемым локальными дескрипторами, может обращаться только та задача, в которой эти дескрипторы описаны. Поскольку пока мы имеем дело с однозадачным режимом, локальная таблица нам не нужна.
Поля дескрипторов для наглядности заполнены конкретными данными явным образом, хотя объявление структуры descr с нулями во всех полях позволяет описать дескрипторы несколько короче [8], например:
gdt_null descr<>; Селектор 0 – обязательный нулевой дескриптор
gdt_data descr<data_size-1,,,92h> ; Селектор 8 – сегмент данных
В дескрипторе gdt_data (строка 23 листинга), описывающем сегмент данных программы, заполняется поле границы сегмента (фактическое значение размера сегмента data_size будет вычислено компилятором, строка 30 листинга), а также байт атрибутов 1. Код 92h говорит о том, что это сегмент данных с разрешением записи и чтения. Базу сегмента, т.е. физический адрес его начала, придется вычислить программно и занести в дескриптор уже на этапе выполнения.
Дескриптор gdt_code (строка 25 листинга) сегмента команд заполняется схожим образом. Код атрибута 98h обозначает, что это исполняемый сегмент, к которому, между прочим, запрещено обращение с целью чтения или записи. Таким образом, сегменты команд в защищенном режиме нельзя модифицировать по ходу выполнения программы.
Дескриптор gdt_stack (строка 27 листинга) сегмента стека имеет, как и любой сегмент данных, код атрибута 92h, что разрешает его чтение и запись, и явным образом заданную границу 255 байтов, что соответствует размеру стека. Базовый адрес сегмента стека так же будет вычислен на этапе выполнения программы.
Последний дескриптор gdt_screen (строка 29 листинга) описывает страницу 0 видеобуфера. Размер видеостраницы, как известно, составляет 4096 байтов, поэтому в поле границы указано число 4095. Базовый физический адрес страницы известен, он равен B8000h. Младшие 16 разрядов базы (число 8000h) заполняют слово base_l дескриптора, биты 16... 19 (число 0bh) – байт base_m. Биты 20...31 базового адреса равны 0, поскольку видеобуфер размещается в первом мегабайте адресного пространства.
Перед переходом в защищенный режим процессору надо будет сообщить физический адрес таблицы глобальных дескрипторов и ее размер (точнее, границу). Размер GDT определяется на этапе трансляции в строке 30.
Назначение оставшихся строк сегмента данных станет ясным в процессе рассмотрения программы.
Сегмент команд text (строка 41 листинга) начинается, как и всегда, оператором segment, в котором указывается тип использования use16, так как мы составляем 16-разрядное приложение. Указание описателя usel6.He запрещает использовать в программе 32-битовые регистры.
Фактически вся программа примера, кроме ее завершающих строк, а также фрагмента, выполняемого в защищенном режиме, посвящена подготовке перехода в защищенный режим. Прежде всего, надо завершить формирование дескрипторов сегментов программы, в которых остались незаполненными базовые адреса сегментов. Базовые (32-битовые) адреса определяются путем умножения значений сегментных адресов на 16. Сначала производится очистка 32-разрядного аккумулятора (строка 44), так как в нем будет сформирован позднее базовый 32-разрядный адрес (фактически эта команда нужна для очистки старшего слова расширенного аккумулятора). После обычной инициализации сегментного регистра DS (строки 45, 46), которая позволит нам обращаться к полям данных программы (в реальном режиме!) выполняется сдвиг на 4 разряда содержимого регистра EАХ. Эта операция выполняется командой shl eax,4. Команда сдвигает влево содержимое 32-разрядного аккумулятора на указанное константой число бит (4). Следующая команда сохраняет получившееся 32-разрядное значение адреса в регистре ebp. После этого в регистр bx помещается смещение дескриптора сегмента кода. Следующими тремя командами (строки 53 – 55) младшее и старшее слова регистра EAX отправляется в поля base_l и base_m дескриптора gdt_data, соответственно. Аналогично вычисляются 32-битовые адреса сегментов команд и стека, помещаемые в дескрипторы gdt_code (строки 57 – 63) и gdt_stack (строки 65 – 71).
Следующий этап подготовки к переходу в защищенный режим – загрузка в регистр процессора GDTR (Global Descriptor Table Register, регистр таблицы глобальных дескрипторов) информации о таблице глобальных дескрипторов. Эта информация включает в себя линейный базовый адрес таблицы и ее границу и размещается в 6 байтах поля данных, называемого псевдодескриптором. Для загрузки GDTR предусмотрена специальная привилегированная команда lgdt (load global descriptor table, загрузка таблицы глобальных дескрипторов), которая требует указания в качестве операнда имени псевдодескриптора. Формат псевдодескриптора приведен на рисунке 1.10.
Рисунок 1.10 – Формат псевдодескриптора
В нашем примере заполнение псевдодескриптора упрощается вследствие того, что таблица глобальных дескрипторов расположена в начале сегмента данных, и ее базовый адрес совпадает с базовым адресом всего сегмента, который уже был вычислен и помещен в дескриптор gdt_data. В строках 73, 74 базовый адрес и граница помещаются в требуемые поля pdescr, а в строке 75 командой lgdt загружается регистр GDTR, сообщая, таким образом, процессору о местонахождение и размер GDT.
В принципе теперь можно перейти в защищенный режим. Однако мы запускаем нашу программу под управлением DOS (а как ее еще можно запустить?) и естественно завершить ее также обычным образом, чтобы не нарушить работоспособность системы. Но в защищенном режиме запрещены любые обращения к функциям DOS или BIOS. Причина этого совершенно очевидна – и DOS, и BIOS являются программами реального режима, в которых широко используется сегментная адресация реального режима, т.е. загрузка в сегментные регистры сегментных адресов. В защищенном же режиме в сегментные регистры загружаются не сегментные адреса, а селекторы. Кроме того, обращение к функциям DOS и BIOS осуществляется с помощью команд int с определенными номерами, а в защищенном режиме эти команды приведут к совершенно другим результатам. Следовательно, программу, работающую в защищенном режиме, нельзя завершить средствами DOS. Сначала ее надо вернуть в реальный режим.
Возврат в реальный режим можно осуществить сбросом процессора. Действия процессора после сброса определяются одной из ячеек КМОП-микросхемы – байтом состояния отключения, располагаемым по адресу Fh. В частности, если в этом байте записан код Ah, после сброса управление немедленно передается по адресу, который извлекается из двухсловной ячейки 40h:67h, расположенной в области данных BIOS. Таким образом, для подготовки возврата в реальный режим необходимо в ячейку 40h:67h записать адрес возврата, а в байт Fh КМОП-микросхемы занести код Ah. Приведенный способ возврата в реальный режим использовался в процессорах i286 (так как другого способа возврата в нем предусмотрено не было).
В процессорах, начиная с i386, переход из реального режима в защищенный и обратно может осуществляться с использованием управляющего регистра CR0. Так как встретить сейчас «живой» 286 процессор практически не реально, воспользуемся именно таким способом.
Всего в микропроцессорах i386 и выше имеется 4 программно адресуемых управляющих регистра CR0, CR1, CR2 и CR3 [8]. Регистр CR1 зарезервирован, регистры CR2 и CR3 управляют страничным преобразованием, которое в рассматриваемой программе не используется, а регистр CR0 содержит набор управляющих битов, из которых нам интересны биты 0 (включение и выключение защищенного режима) и 31 (разрешение страничного преобразования).
После сброса процессора оба эти бита сброшены, благодаря чему процессор начинает работать в реальном режиме с выключенным страничным преобразованием. Установка младшего бита CR0 в 1 переводит процессор в защищенный режим, а сброс его возвращает процессор в реальный режим. Следует отметить, что младшая половина регистра CR0 совпадает со словом состояния 286 процессора, поэтому команды чтения и записи в регистр CR0 аналогичны по результату командам smsw и lmsw, которые сохранены в старших процессорах из соображений совместимости.
Еще один важный шаг, который необходимо выполнить перед переходом в защищенный режим, заключается в запрете всех аппаратных прерываний. Дело в том, что в защищенном режиме процессор выполняет процедуру обработки прерывания иначе, чем в реальном. При поступлении сигнала прерывания процессор не обращается к таблице векторов прерываний в первом килобайте памяти, как в реальном режиме, а извлекает адрес программы обработки прерывания из таблицы дескрипторов прерываний, построенной аналогично таблице глобальных дескрипторов и располагаемой в программе пользователя (или в операционной системе). В нашем примере такой таблицы нет, и на время работы программы прерывания придется запретить. Запрет всех аппаратных прерываний осуществляется командой cli (строка 77).
В строках 78, 79 в порт 70h засылается код 80h, который запрещает немаскируемые прерывания (которые не запрещаются командой cli).
В строках 81...83 осуществляется перевод процессора в защищенный режим. Этот перевод можно выполнить различными способами. В рассматриваемом примере для этого используется команда mov. Сначала содержимое управляющего CR0 регистра считывается в аккумулятор EAX, затем его младший бит устанавливается в 1 с помощью команды or, затем содержимое аккумулятора опять записывается в управляющий регистр CR0 с уже модифицированным младшим битом. Все последующие команды выполняются уже в защищенном режиме.
Хотя защищенный режим установлен, однако действия по настройке системы еще не закончены. Действительно, во всех используемых в программе сегментных регистрах хранятся не селекторы дескрипторов сегментов, а базовые сегментные адреса, не имеющие смысла в защищенном режиме.
Рисунок 1.11 – Сегментные и теневые регистры
Отсюда можно сделать вывод, что после перехода в защищенный режим программа не должна работать, так как в регистре CS пока еще нет селектора сегмента команд, и процессор не может обращаться к этому сегменту. В действительности это не совсем так.
В процессоре для каждого из сегментных регистров имеется так называемый теневой регистр дескриптора, который имеет формат дескриптора (рисунок 1.11). Теневые регистры недоступны программисту. Они автоматически загружаются процессором из таблицы дескрипторов каждый раз, когда процессор загружает соответствующий сегментный регистр. Таким образом, в защищенном режиме программист имеет дело с селекторами, т.е. номерами дескрипторов, а процессор – с самими дескрипторами, хранящимися в теневых регистрах. Именно содержимое теневого регистра (в первую очередь, линейный адрес сегмента) определяет область памяти, к которой обращается процессор при выполнении конкретной команды.
В реальном режиме теневые регистры заполняются не из таблицы дескрипторов, а непосредственно самим процессором. В частности, процессор заполняет поле базы каждого теневого регистра линейным базовым адресом сегмента, полученным путем умножения на 16 содержимого сегментного регистра, как это и положено в реальном режиме. Поэтому после перехода в защищенный режим в теневых регистрах находятся правильные линейные базовые адреса, и программа будет выполняться правильно, хотя с точки зрения правил адресации защищенного режима содержимое сегментных регистров лишено смысла.
Тем не менее, после перехода в защищенный режим, прежде всего, следует загрузить в используемые сегментные регистры (и, в частности, в регистр CS) селекторы соответствующих сегментов. Это позволит процессору правильно заполнить все поля теневых регистров из таблицы дескрипторов. Пока эта операция не выполнена, некоторые поля теневых регистров (в частности, границы сегментов) могут содержать неверную информацию.
Загрузить селекторы в сегментные регистры DS, SS и ES не представляет труда (строки 93 – 101). Но как загрузить селектор в регистр CS? Для этого можно воспользоваться искусственно сконструированной командой дальнего перехода, которая, как известно, приводит к смене содержимого и IP, и CS. Строки 89 – 91 демонстрируют эту методику. В реальном режиме мы поместили бы во второе слово адреса сегментный адрес сегмента команд, в защищенном же мы записываем в него селектор этого сегмента (число 16).
Команда дальнего перехода, помимо загрузки в CS селектора, выполняет еще одну функцию – она очищает очередь команд в блоке предвыборки команд процессора. Как известно, в современных процессорах с целью повышения скорости выполнения программы используется конвейерная обработка команд программы, позволяющая совместить во времени фазы их обработки. Одновременно с выполнением текущей (первой) команды осуществляется выборка операндов следующей (второй), дешифрация третьей и выборка из памяти четвертой команды. Таким образом, в момент перехода в защищенный режим уже могут быть расшифрованы несколько следующих команд и выбраны из памяти их операнды. Однако эти действия выполнялись, очевидно, по правилам реального, а не защищенного режима, что может привести к нарушениям в работе программы. Команда перехода очищает очередь предвыборки, заставляя процессор заполнить ее заново уже в защищенном режиме.
Следующий фрагмент примера программы (строки 100 – 113) является чисто иллюстративным. В нем инициализируется (по правилам защищенного режима!) сегментный регистр ES и в видеобуфер выводится сообщение 'A message in protected mode' (зеленым цветом на синем фоне), так, что оно располагается в середине пятой строки экрана, чем подтверждается правильное функционирование программы в защищенном режиме.
Как уже отмечалось выше, для того, чтобы не нарушить работоспособность DOS, процессор следует вернуть в реальный режим, после чего можно будет завершить программу обычным образом. Перейти в реальный режим можно разными способами. Можно, например, осуществить сброс процессора, заслав команду FEh в порт 64h контроллера клавиатуры. Эта команда возбуждает сигнал на одном из выводов контроллера клавиатуры, который, в конечном счете, приводит к появлению сигнала сброса на выводе RESET микропроцессора. Этот способ (единственный для 286 процессора) неудобен тем, что для выполнения сброса необходимо несколько микросекунд. Если выполнять таким образом переход в реальный режим достаточно часто, можно потерять много времени.
В нашем примере переход в реальный режим осуществляется простым сбросом младшего бита в управляющем регистре CR0.
Если просто перейти в реальный режим сбросом бита 0 в регистре CR0, в теневых регистрах останутся дескрипторы защищенного режима, и при первом же обращении к любому сегменту программы возникнет исключение общей защиты, так как ни один из определенных ранее сегментов не имеет границы FFFh. Так как обработка исключений не производится, произойдет сброс процессора и перезагрузка компьютера, так как в данном случае не настраивался байт состояния перезагрузки, и не заполнялись соответствующие ячейки области данных BIOS. Следовательно, перед переходом в реальный режим необходимо исправить дескрипторы всех используемых сегментов: кодов, данных, стека и видеобуфера. Сегментные регистры FS и GS не использовались, поэтому о них можно не заботиться.
В строках 118 – 121 в поля границ всех четырех дескрипторов записывается значение FFFFh, а в строках 124 – 129 выполняется загрузка селекторов в сегментные регистры, что приводит к перезаписи содержимого теневых регистров. Так как сегментный регистр CS программно недоступен, его загрузку приходится опять выполнять с помощью искусственно сформированной команды дальнего перехода (строки 133 – 135).
После настройки всех использованных в защищенном режиме сегментных регистров, можно сбрасывать бит 0 управляющего регистра CR0 (строки 137 – 139). После перехода в реальный режим опять надо выполнить искусственно сформированную команды дальнего перехода для того, чтобы очистить очередь команд в блоке предвыборки процессора и загрузить в регистр CS вместо хранящегося там селектора обычный сегментный адрес регистра команд (строки 140 – 142).
Искусственно сформированная команда дальнего перехода передает управление на метку return.
Теперь процессора опять работает в реальном режиме. При этом, хотя в сегментных регистрах DS, ES и SS остались недействительные для реального режима селекторы, программа пока работает корректно, так как в теневых регистрах находятся правильные линейные адреса (оставшиеся от защищенного режима) и законные для реального режима границы (загруженные в строках 118 – 121). Однако, если в программе встретится любая команда сохранения или восстановления какого-либо сегментного регистра, нормальное выполнение программы нарушится, так как в сегментном регистре окажется не сегментный адрес, как это должно быть в реальном режиме, а селектор. Это значение будет трактоваться процессором как сегментный адрес, что приведет в дальнейшем к неверной адресации соответствующего сегмента.
Если даже в оставшемся тексте программы и нет команд чтения или записи сегментных регистров, неприятности все равно возникнут, так как простой вызов DOS'овского прерывания int 21h приведет к этим неприятностям, так как диспетчер DOS выполняет сохранение и восстановление регистров (в том числе и сегментных) при выполнении функций DOS.
Поэтому после перехода в реальный режим необходимо загрузить в используемые далее сегментные регистры соответствующие сегментные адреса. В строках 148 – 151 в регистры DS и SS записываются сегментные адреса соответствующих сегментов.
При рассмотренном варианте возврата в реальный режим (без сброса процессора) не надо сохранять кадр стека, так как содержимое регистра SP в этом случае не разрушается, а регистр SS мы уже инициализировали.
Для восстановления работоспособности системы следует также разрешить прерывания (маскируемые – строка 155, немаскируемые – строки 156, 157), после чего программа может продолжаться уже в реальном режиме. В рассмотренном примере для проверки работоспособности системы в этом режиме на экран выводится сообщение ‘Real mode now’ с помощью функции DOS 09h. Для наглядности в сообщение включены Esc последовательности для смены цвета символов и фона (красные символы на зеленом фоне). Осуществлена смена цвета символов и фона может быть лишь в том случае, если в DOS установлен драйвер ANSI.SYS. Если после перехода в реальный режим при установленном драйвере ANSI.SYS сообщение будет выведено без изменения цветов, это может говорить об ошибках защищенного режима.
Перед завершением программы, она ожидает ввода с клавиатуры (функция 0 прерывания int 16h), чтобы можно было успеть увидеть содержимое экрана.
Программа завершается обычным образом функцией DOS 4Ch. Нормальное завершение программы и переход в DOS тоже в какой-то мере свидетельствует о ее правильности.
1.3 Вопросы для самопроверки
1. Какие режимы работы поддерживают 32-разрядные процессоры х86?
2. Какие регистры в 32-разрядных микропроцессорах х86 являются 16-разрядными?
3. Какие новые флаги добавились у 32-разрядных микропроцессоров х86?
4. Какие разряды управляющего регистра CR0 микропроцессора указывают состояние и режимы работы процессора?
5. Что такое бит страничного преобразования?
6. Что такое бит сопроцессора?
7. Для чего нужен бит переключения задачи?
8. Что такое бит эмуляции сопроцессора?
9. Для чего нужен бит присутствия сопроцессора?
10. Что такое бит разрешения защиты?
11. Какие регистры микропроцессора используются для поддержки страничного преобразования?
12. Что такое регистры системных адресов?
13. Для чего нужен регистр таблицы глобальных дескрипторов?
14. Для чего нужен регистр таблицы дескрипторов прерываний?
15. Для чего нужен регистр таблицы локальных дескрипторов?
16. Для чего нужен регистр состояния задачи?
17. Какие действия надо выполнить, чтобы в компилируемой программе можно было использовать 32-разрядные операнды?
18. Какие команды появились в 32-разрядных микропроцессорах (примеры)?
19. Каково главное ограничение реального режима работы процессора?
20. Какие дополнительные возможности появляются в защищенном режиме работы микропроцессора?
2 Использование 32-разрядной адресации в реальном режиме