[Назад] [Далее]

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

Мы будем пользоваться различными дескрипторами по мере надобности, а для начала выполним переключение в 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

п»ї
"target=_blank><\/a>") //-->