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].

P ; Разрешение трансляции всех (в том - student2.ru

Рисунок 1.8 – Дескриптор сегмента

P ; Разрешение трансляции всех (в том - student2.ru

Рисунок 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.

P ; Разрешение трансляции всех (в том - student2.ru

Рисунок 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 с уже модифицированным младшим битом. Все после­дующие команды выполняются уже в защищенном режиме.

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

P ; Разрешение трансляции всех (в том - student2.ru

Рисунок 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-разрядной адресации в реальном режиме

Наши рекомендации