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
И все - теперь процессор находится в реальном режиме с неограниченными сегментами.