|
| [Назад] [Далее] | |
Мы будем пользоваться различными дескрипторами по мере надобности, а для начала выполним переключение в 32-битную модель памяти flat, где все сегменты имеют базу 0 и лимит 4 Гб. Нам потребуются два дескриптора — один для кода и один для данных. Кроме того, нужны два 16-битных дескриптора с лимитами 64 Кб, чтобы загрузить их в CS и DS перед возвратом в реальный режим.
В комментариях к примеру pm0.asm мы заметили, что его можно выполнять в DOS-окне Windows 95, хотя программа и запускается уже в защищенном режиме. Это происходит потому, что Windows 95 перехватывает обращения к контрольным регистрам и позволяет программе перейти в защищенный режим, но только с минимальным уровнем привилегий. Все следующие наши примеры в этом разделе будут рассчитаны на работу с максимальными привилегиями, поэтому добавим в программу проверку на запуск из-под Windows (функция 1600h прерывания мультиплексора INT 2Fh).
Еще одно дополнительное действие, которое будем теперь выполнять при переключении в защищенный режим, — управление линией А20. После запуска компьютера для совместимости с 8086 используются 20-разрядные адреса (работают адресные линии А0 – А19), так что попытка записать что-то по линейному адресу 100000h приведет к записи по адресу 0000h. Этот режим отменяется установкой бита 2 в порту 92h и снова включается сбрасыванием этого бита в 0. (Существуют и другие способы, зависящие от набора микросхем, используемых на материнской плате, но они бывают необходимы, только если требуется максимально возможная скорость переключения.)
; pm1.asm
; Программа, демонстрирующая работу с сегментами в защищенном режиме,
; переключается в модель flat, выполняет вывод на экран и возвращается в DOS
;
; Компиляция: TASM:
; tasm /m pm1.asm
; tlink /x /3 pm1.obj
; MASM:
; ml /c pm1.asm
; link pm1.obj,,NUL,,,
; WASM:
; wasm pm1.asm
; wlink file pm1.obj form DOS
.386p ; 32-битный защищенный режим появился в 80386
; 16-битный сегмент, в котором находится код для входа
; и выхода из защищенного режима
RM_seg segment para public "code" use16
assume CS:RM_seg,SS:RM_stack
start:
; подготовить сегментные регистры
push cs
pop ds
; проверить, не находимся ли мы уже в РМ
mov еах,cr0
test al,1
jz no_V86
; сообщить и выйти
mov dx,offset v86_msg
err_exit:
mov ah,9
int 21h
mov ah,4Ch
int 21h,
v86_msg db "Процессор в режиме V86 - нельзя переключиться в РМ$"
win_msg db "Программа запущена под Windows - нельзя перейти в кольцо 0$"
; может быть, это Windows 95 делает вид, что РЕ = 0?
no_V86:
mov ax,1600h ; Функция 1600h
int 2Fh ; прерывания мультиплексора,
test al,al ; если AL = 0,
jz no_windows ; Windows не запущена
; сообщить и выйти, если мы под Windows
mov dx,offset win_msg
jmp short err_exit
; итак, мы точно находимся в реальном режиме
no_windows:
; если мы собираемся работать с 32-битной памятью, стоит открыть А20
in al,92h
or al,2
out 92h,al
; вычислить линейный адрес метки PM_entry
xor еах,еах
mov ax,PM_seg ; АХ - сегментный адрес PM_seg
shl eax,4 ; ЕАХ - линейный адрес PM_seg
add eax,offset PM_entry ; EAX - линейный адрес PM_entry
mov dword ptr pm_entry_off,eax ; сохранить его
; вычислить базу для GDT_16bitCS и GDT_16bitDS
xor eax,eax
mov ax,cs ; AX - сегментный адрес RM_seg
shl eax,4 ; ЕАХ - линейный адрес RM_seg
push eax
mov word ptr GDT_16bitCS+2,ax ; биты 15 - 0
mov word ptr GDT_16bitDS+2,ax
shr eax,16
mov byte ptr GDT_16bitCS+4,al ; и биты 23 - 16
mov byte ptr GDT_16bitDS+4,al
; вычислить абсолютный адрес метки GDT
pop eax ; EAX - линейный адрес RM_seg
add ax,offset GDI ; EAX - линейный адрес GDT
mov dword ptr gdtr+2,eax ; записать его для GDTR
; загрузить таблицу глобальных дескрипторов
lgdt fword ptr gdtr
; запретить прерывания
cli
; запретить немаскируемое прерывание
in al,70h
or al,80h
out 70h,al
; переключиться в защищенный режим
mov eax,cr0
or al,1
mov cr0,eax
; загрузить новый селектор в регистр CS
db 66h ; префикс изменения разрядности операнда
db 0EAh ; код команды дальнего jmp
pm_entry_off dd ? ; 32-битное смещение
dw SEL_flatCS ; селектор
RM_return: ; сюда передается управление при выходе из защищенного режима
; переключиться в реальный режим
mov еах,cr0
and al,0FEh
mov cr0,eax
; сбросить очередь предвыборки и загрузить CS реальным сегментным адресом
db 0EAh ; код дальнего jmp
dw $+4 ; адрес следующей команды
dw RM_seg ; сегментный адрес RM_seg
; разрешить NMI
in al,70h
and al,07Fh
out 70h,al
; разрешить другие прерывания
sti
; подождать нажатия любой клавиши
mov ah,0
int 16h
; выйти из программы
mov ah,4Ch
int 21h
; текст сообщения с атрибутами, который мы будем выводить на экран
message db 'Н',7,'е',7,'l',7,'l',7,'о',7,' ',7,'и',7,'з',7,' ',7
db '3',7,'2',7,'-',7,'б',7,'и',7,'т',7,'н',7,'о',7,'г',7
db 'о',7,' ',7,'Р',7,'М'
message_l = $ - message ; длина в байтах
rest_scr = (80*25*2-message_l)/4 ; длина оставшейся части экрана
; в двойных словах
; таблица глобальных дескрипторов
GDT label byte
; нулевой дескриптор (обязательно должен быть на первом месте)
db 8 dup(0)
; 4-гигабайтный код, DPL = 00:
GDT_flatCS db 0FFh,0FFh,0,0,0,10011010b,11001111b,0
; 4-гигабайтные данные, DPL = 00:
GDT_flatDS db 0FFh,0FFh,0,0,0,10010010b,11001111b,0
; 64-килобайтный код, DPL = 00:
GDT_16bitCS db 0FFh,0FFh,0,0,0,10011010b,0,0
; 64-килобайтные данные, DPL = 00:
GDT_16bitDS db 0FFh,0FFh,0,0,0,10010010b,0,0
GDT_l = $ - GDT ; размер GDT
gdtr dw GDT_l-1 ; 16-битный лимит GDI
dd ? ; здесь будет 32-битный линейный адрес GDT
; названия для селекторов (все селекторы для GDT, с RPL = 00)
SEL_flatCS equ 00001000b
SEL_flatDS equ 00010000b
SEL_16bitCS equ 00011000b
SEL_16bitDS equ 00100000b
RM_seg ends
; 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_flatDS
mov es,ax
; вывод на экран
mov esi,offset message ; DS:ESI - сообщение
mov edi,0B8000h ; ES:EDI - видеопамять
mov ecx,message_l ; ECX - длина
rep movsb ; вывод на экран
mov eax,07200720h ; два символа 20h с атрибутами 07
mov ecx,rest_scr ; остаток экрана / 2
rep stosd ; очистить, остаток экрана
; загрузить в CS селектор 16-битного сегмента RM_seg
db 0EAh ; код дальнего 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