10.4.4. Пример программы

Мы будем пользоваться различными дескрипторами по мере надобности, а для начала выполним переключение в 32-битную модель памяти flat, где все сегмен­ты имеют базу 0 и лимит 4 Гб. Нам потребуются два дескриптора - один для кода и один для данных - и два 16-битных дескриптора с лимитами 64 Кб, чтобы заг­рузить их в CS и DS перед возвратом в реальный режим.

В комментариях к примеру pmO.asm мы заметили, что его можно выполнять в DOS-окне Windows 95, хотя программа запускается уже в защищенном режиме. Это происходит потому, что Windows 95 перехватывает обращения к контрольным регистрам и позволяет программе перейти в защищенный режим, только с ми­нимальным уровнем привилегий. Все следующие наши примеры в этом разделе будут рассчитаны на работу с максимальными привилегиями, поэтому добавим в программу проверку на запуск из-под Windows (функция 1600h прерывания мультиплексора INT 2Fh).

Еще одно дополнительное действие, которое мы выполним при переключении

в защищенный режим, - управление линией А20. После запуска компьютера для

совместимости с 8086 используются 20-разрядные адреса (работают адресные линии АО - А19), так что попытка записать что-то по линейному адресу lOOOOOh

приведет к записи по адресу OOOOh. Этот режим отменяется установкой бита 2 в порту 92h и снова включается сбрасыванием этого бита в 0. (Существуют и дру­гие способы, зависящие от набора микросхем, которые используются на материн­ской плате, но они необходимы в том случае, если требуется максимально возмож­ная скорость переключения.)

I pml.asm

Программа, демонстрирующая работу с сегментами в защищенном режиме. Переключается в модель flat,   выполняет вывод на экран и возвращается в DOS.

; Компиляция: ; TASM:

/m pml.asm

;    tlink /х /3 pml.obj

MASM:

ml /c pml.asm

;    link pml.obj, ,NUL,,, ; WASM;

;   wasm pm1.asm

file pml.obj form DOS

, 32-битный защищенный режим появился в 80386.

;  16-битный сегмент,   в котором находится код для входа и выхода из защищенного режима.

RM_seg segment para public "code" use16 assume CS:RM_seg,SS:RM_stack

start:

; Подготовить сегментные регистры.

push

pop

Проверить, mov

test

jz

не cs ds

находимся eax, crO al,l no V86

ли мы уже в РМ.

Сообщить и выйти.

dx, offset v86_msg

err exit:

v86_msg win_msg mov ah,9

int 2lh

mov ah,4Ch

int 21h

db db "Процессор в режиме V86 - нельзя переключиться в РМ$" "Программа запущена под Windows - нельзя перейти в кольцо 0$"

; Может быть, это Windows

no_V86:

95 делает вид,  что РЕ

mov ах,1600п

int 2Fh

test al.al

jz no_windows Сообщить и выйти,  если мы под Windows,

mov dx, offset win_msg

jmp short err_exit.

O?

; Функция l6OOh

; прерывания ;   Если AL = 0,

Windows не запущена.

находимся в реальном режиме.

; Итак, мы точно no_windows:

;  Если мы собираемся работать с 32-битной памятью,  стоит открыть А20.

in al,92h

or al,2

out 92h,al ;  Вычислить линейный адрес метки PM_entry.

xor eax,eax

mov ax,PM_seg

shl eax,4

add eax,offset PM_entry

mov dword ptr pm_entry_off, eax Вычислить базу для GDTj6bitCS и GDT_16bitDS.

xor eax,eax

mov

shl eax, 4

push eax

mov word ptr GDT_l6bitCS+2,ax-

АХ - сегментный адрес РМ^ед. ЕАХ - линейный адрес РМ_эед, ЕАХ - линейный адрес РМ_епігу. Сохранить его.

AX - сегментный адрес

EAX - линейный адрес RM_seg.

Биты l5-O

Сегментная адресацviя

mov       word ptr GDTJ6bitDS+2,ax shr eax,16

mov byte ptr GDTj6bitCS+4,al; mov       byte ptr GDTJ6bitDS+4,al

; Вычислить абсолютный адрес метки GDT.

pop       eax ;

add       ax,offset GDT ;

mov       dword ptr gdtr+2,eax ; ; Загрузить таблицу глобальных дескрипторов.

Igdt      fword ptr gdtr ;  Запретить прерывания.

cli

; Запретить немаскируемое прерывание. al,70h al,80h 70h,al в защищенный eax,crO al,1 crO,eax новый селектор в db 66h db OEAh pm_entry_off       dd ?

dw SEL_.flatCS

in

or out Преключиться mov

or mov

Загрузить

режим.

регистр CS.

биты 23-16.

EAX - линейный адрес

EAX - линейный адрес GDT. Записать его для GDTR.

Префикс изменения разрядности операнда. Код команды дальнего ]тр. 32-битное смещение. Селектор.

RM_return:

; Переключиться в реальный режим.

mov

and al.OFEh

mov

Сбросить очередь предвыборки и

Сюда передается управление при выходе из защищенного режима.

загрузить

db dw dw

OEAh

$+4

RM_seg

СВ реальным сегментным адресом. Код дальнего ]тр. Адрес следующей команды, Сегментный адрес КМ_зед,

Разрешить in -

and out

а1,70п а1,07РН 70г), а1

Разрешить другие прерывания.

Подождать нажатия любой клавиши. mov       ал, О

16Ь

Выйти из программы. тот ап,4Сп

М 21Ъ

Текст сообщения с

message ' db

db db

атрибутами, который мы будем выводить на -экран. ,Н,,71.'е',7,'1',7,'1',7,,о',7,' \7,'и',7,' '3' ,7, '2' ,7, ' -' ,7, 'б' ,7, 'и' .7, 'т', 7, 'н' ,7,' ' О' , 7, ' ' ,7, 'Р' .7, 'М'

Длина в байтах. Длина оставшейся части экрана

.7, .7,''

7

,7

message_1 = $-message rest_scr = (80*25*2-message_l)/4 ; Таблица глобальных дескрипторов. GDT label- byte

;  Нулевой дескриптор  (обязательно должен быть на первом месте).

db 8 dup(O)

;   4-гигабайтный код,   DPL = 00:

GDT_flatCS db OFFh,OFFh,0,0,0,10011010b,11001111b,О'

;    4-гигабайтные данные,    DPL  = 00:

GDT_flatDS db OFFh,OFFh,О,О, О, 10010010b,11001111b, О

; 64-килобайтный код,   DPL = 00:

GDT_16bitCS     - db ,      OFFh, OFFh, 0,0,0,10011010b, О, О

;   64-килобайтные данные,   DPL =00:

GDT_160itDS db OFFh, OFFh, 0,0, 0,10010010b, 0,0

GDT_1 = $-GDT ;   Размер GDT.

в двойных словах.

gdtr

; Названия для

SELjlatCS SEL_flatDS SEL_16bitCS SEL_16bitDS

ends

dw 60T_1-1   ;   16-битный лимит GDT.

dd ? ; Здесь будет 32-битный линейный адрес GDT. селекторов  (все селекторы для GOT,   с RPL = 00).

equ 00001000b

equ 000100000

equ 00011000b

equ 00100000b

; 32-битный сегмент, содержащий код, который будет PM_seg segment para public "CODE" use32 assume cs:PM_seg

PM_entry:

;  Загрузить сегментные регистры (кроме SS). mov ax,SEL_16bitDS mov ds,ax mov        ax,SEL_flatOS mov es.ax

исполняться в защищенном режиме.

Вывод на экран.

mov mov mov rep mov mov rep

Загрузить

esi, offset message edi,0B8OO0h ecx,message_l movsb

eax,07200720h ecx, rest_scr stosd

08:Е51 - сообщение. Е8:Е01 - видеопамять. ЕСХ - длина. Вывод на экран.

Два символа 20п с, атрибутами 07.

Остаток экрана / 2. Очистить остаток экрана.

CS селектор 16-битного сегмента RM_seg.

db        OEAh ; Код дальнего jmp.-

dd        offset RM.return ; 32-битное смещение.

dw SEL_16bitCS ; Селектор.

PM_seg ends

;  Сегмент стека - используется как в 16-битном/ так и в 32-битном режимах;

; поскольку мы не трогали SS, он все время оставался 16-битным. RM.stack segment para stack "STACK" use16

db 100h dup(?)

RM_stack ends

end start 10.4.5. Нереальный режим

Как мы уже знаем, при изменении режима скрытые части сегментных регист­ров сохраняют содержимое своих дескрипторов и их разрешено применять. Мы воспользовались этой возможностью в нашем первом примере, когда значения, за­несенные в сегментные регистры в реальном режиме, загружались в защищенном. Возникает вопрос - а если поступить наоборот: в защищенном режиме загрузить сегментные регистры дескрипторами 4-гигабайтных сегментов с базой 0 и перей­ти в реальный режим? Оказывается, это прекрасно срабатывает, и мы попадаем в особый режим, который называется нереальным режимом (unreal mode), боль­шим реальным режимом (BRM) или реальным flat-режимом (RFM). Чтобы пе­рейти в нереальный режим, перед переходом в реальный режим надо загрузить в CS дескриптор 16-битного сегмента кода с базой 0 и лимитом 4 Гб и в осталь­ные сегментные регистры - точно такие же дескрипторы сегментов данных.

Теперь весь дальнейший код программы, написанный для реального режима, больше не ограничен рамками 64-килобайтных сегментов и способен работать

с любыми массивами. Можно подумать, что первый же обработчик прерывания

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

в сегментный регистр, сегмент будет иметь размер 4 Гб. Если попробовать вер­нуться в DOS - она по-прежнему будет работать. Можно запускать программы такого рода:

tiny

. code org

start:    xor      ах, ах

mov       ds,ax ;  DS = О

;   Вывести символ в видеопамять:

mov        word ptr ds:[OB8000h],8403h

ret

end start

и они тоже будут работать. Единственное, что отключает этот режим, - програм­мы, переходящие в защищенный режим и обратно, которые устанавливают гра­ницы сегментов в 64 Кб, например любые программы, использующие расширите­ли DOS.

Нереальный режим - идеальный вариант для программ, которым необходима 32-битная адресация и свободное обращение ко всем прерываниям BIOS и DOS (традиционный способ состоял бы в работе в защищенном режиме с переключе­нием в V86 для вызова BIOS или DOS, как это делается в случае с DPMI).

Для перехода в этот режим можно воспользоваться, например, такой процедурой:

; Область данных: GDT label byte

db 8 dup(O) ; Нулевой дескриптор.

І 16-битный 4 Гб сегмент:

db OFFh,OFFh,0,0,0,1001001b,11001111b,0

gdtr dw 16 ;  Размер GDT.

gdt_base dd ? ; Линейный адрес GDT.

І  Код программы.

; Определить линейный адрес GDT. хог еах,еах mov ax.cs

shl       еах,4     ■ ' add       ах,offset GDT ; Загрузить GDT из одного дескриптора (не считая нулевого). mov

lgdt      fword ptr gdtr

:  Перейти в защищенный режим. cli

mov еах.сгО or al,1 mov сгО.еах

jmp        start_PM ; Сбросить очередь предвыборки.

І  Intel рекомендует І делать jmp после каждой смены режима. І Загрузить все сегментные регистры дескриптором с лимитом 4 Гб.

mov      ах, 8 ;  8 - селектор нашего дескриптора.

mov       ds, ax

mov   • es.ax

mov

mov

І Перейти в реальный режим. mov

and al.OFEh mov

jmp exit_PM

exit_PM:

І Записать что-нибудь в каждый сегментный регистр. хог       ах,ах mov mov

mov fs.ax

sti

mov

mov ds,ax

И все - теперь процессор находится в реальном режиме с неограниченными сегментами.