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

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

Теперь воспользуемся интерфейсом DPMI для переключения в защищенный режим и вывода строки на экран. Этот пример будет работать только в системах, предоставляющих DPMI для запускаемых в них DOS-программ, например в Windows 95.

; dpmiex.asm
; Выполняет переключение в защищенный режим средствами DPMI
        .386                        ; 32-битный защищенный режим появился в 80386
; 16-битный сегмент - в нем выполняется подготовка и переход в защищенный режим
RM_seg  segment byte   public use16
        assume cs:RM_seg, ds:PM_seg, ss:RM_stack

; точка входа программы
RM_entry:
; проверить наличие DPMI
        mov        ax,1687h         ; номер 1678h
        int        2Fh              ; прерывание мультиплексора,
        test       ax,ax            ; если АХ не ноль,
        jnz        DPMI_error       ; произошла ошибка (DPMI отсутствует),
        test       bl,1             ; если не поддерживается 32-битный режим,
        jz         DPMI_error       ; нам тоже нечего делать

; подготовить  базовые адреса для будущих дескрипторов
        mov        eax,PM_seg
        mov        ds,ax            ; DS - сегментный адрес PM_seg
        shl        eax,4            ; EAX - линейный адрес начала сегмента PM_seg
        mov        dword ptr PM_seg_addr,eax
        or         dword ptr GDT_flatCS+2,eax    ; дескриптор для CS
        or         dword ptr GDT_flatDS+2,eax    ; дескриптор для DS

;  сохранить адрес процедуры переключения DPMI
        mov        word ptr DPMI_ModeSwitch,di
        mov        word ptr DPMI_ModeSwitch+2,es

; ES должен указывать на область данных для переключения режимов,
; у нас она будет совпадать с началом будущего 32-битного стека
        add        eax,offset DPMI_data  ; добавить к EAX смещение
        shr        eax,4                 ; и превратить в сегментный адрес
        inc        ax
        mov        es,ax

; перейти в защищенный режим
        mov        ах, 1                 ; АХ = 1 - мы будем 32 приложением
ifdef _WASM_
        db         67h                   ; поправка для wasm
endif
        call       dword ptr DPMI_ModeSwitch
        jc         DPMI_error       ; если переключения не произошло - выйти

; теперь мы находимся в защищенном режиме, но лимиты всех сегментов
; установлены на 64 Кб, а разрядности сегментов - на 16 бит.
; Нам надо подготовить два
; 32-битных селектора с лимитом 4 Гб - один для кода и один для данных
        push       ds
        pop        es               ; ES вообще был сегментом PSP с лимитом 100h
        mov        edi,offset GDT   ; EDI - адрес таблицы GDT
; цикл по всем дескрипторам в нашей GDT,
        mov        edx,1            ; которых всего два (0, 1) sel_loop:
        xor        ах,ах            ; функция DPMI 00
        mov        cx,1
        int        31h              ; создать локальный дескриптор
        mov        word ptr selectors[edx*2],ax   ; сохранить селектор
        mov        bx,ax                          ; в таблицу selectors
        mov        ax,000Ch         ; функция DPMI OCh
        int        31h              ; установить селектор
        add        di,8             ; EDI - следующий дескриптор
        dec        dx
        jns        sel_loop

; загрузить селектор сегмента кода в CS при помощи команды RETF
        push       dword ptr Sel_flatCS           ; селектор для CS
ifdef _WASM_
        db         066h
endif
        push       offset PM_entry  ; EIP
        db         066h             ; префикс размера операнда
        retf                        ; выполнить переход в 32-битный сегмент

; сюда передается управление, если произошла ошибка при инициализации DPMI
; (обычно, если DPMI просто нет)
DPMI_error:
        push       cs
        pop        ds
        mov        dx,offset nodpmi_msg
        mov        ah,9h            ; вывод строки на экран
        int        21h
        mov        ah,4Ch           ; конец ЕХЕ-программы
        int        21h
nodpmi_msg         db    "Ошибка DPMI$"
RM_seg  ends

; сегмент PM_seg содержит код, данные и стек для защищенного режима
PM_seg  segment byte   public use32
        assume  cs:PM_seg,ds:PM_seg,ss:PM_seg
; таблица дескрипторов
GDT     label      byte
; дескриптор для CS
GDT_flatCS         db    0FFh,0FFh,0h,0h,0h,0FAh,0CFh,0h
; дескриптор для DS
GDT_flatDS         db    0FFh,0FFh,0h,0h,0h,0F2h,0CFh,0h

; точка входа в 32-битный режим - загружен только CS
PM_entry:
        mov        ax,word ptr Sel_flatDS   ; селектор для данных
        mov        ds,ax                    ; в DS
        mov        es,ax                    ; в ES
        mov        ss,ax                    ; и в SS
        mov        esp,offset PM_stack_bottom ; и установить стек
; отсюда начинается текст собственно программы,
; программа работает в модели памяти flat с ненулевой базой,
; база CS, DS, ES и SS совпадает и равна линейному адресу начала PM_seg
; все лимиты - 4 Гб
        mov        ах,0300h                ; функция DPMI 0300h
        mov        bx,0021h                ; прерывание DOS 21h
        xor        есх,есх                 ; стек не копировать
        mov        edi,offset v86_regs     ; ES:EDI - адрес v86_regs
        int        31h                     ; вызвать прерывание

        mov        ah,4Ch                  ; Это единственный способ
        int        21h                     ; правильно завершить DPMI-программу

hello_msg          db      "Hello world из 32-битного защищенного режима!$"

v86_regs:                  ; значения регистров для функции DPMI ОЗООп
                   dd      0,0,0,0,0        ; EDI, ESI, EBP, 0, ЕВХ
v_86_edx           dd      offset hello_msg ; EDX
                   dd      0                ; ЕСХ
v86_eax            dd      0900h            ; EAX (AH = 09h, вывод строки на экран)
                   dw      0,0              ; FLAGS, ES
v86_ds             dw      PM_seg           ; DS
                   dw      0,0,0,0,0,0      ; FS, GS, 0, 0, SP, SS

; различные временные переменные, нужные для переключения режимов
DPMI_ModeSwitch    dd      ?           ; точка входа DPMI
PM_seg_addr        dd      ?           ; линейный адрес сегмента PM_seg

; значения селекторов
selectors:
Sel_flatDS         dw      ?
Sel_flatCS         dw      ?

; стек для нашей 32-битной программы
DPMI_data:                 ; и временная область данных DPMI одновременно
                   db      16384 dup (?)
PM_stack_bottom:
PM_seg  ends

; стек 16-битной программы, который использует DPMI-сервер при переключении режимов
; Windows 95 требует 16 байт
; CWSDPMI требует 32 байта
; QDPMI требует 96 байт
; мы выберем по максимуму
RM_stack  segment byte stack "stack" use16
                  db       96 dup (?)
RM_stack  ends
        end        RM_entry         ; точка входа для DOS - RM_entry

Несмотря на то что DPMI разрешает пользоваться многими прерываниями напрямую и всеми через функцию 0300h, он все равно требует некоторой подготовки для переключения режимов. Кроме того, программа, использующая DPMI для переключения режимов, должна сочетать в себе 16-битный и 32-битный сегменты, что неудобно с точки зрения практического программирования. На самом деле для написания приложений, идущих в защищенном режиме под DOS, никто не применяет переключение режимов вообще — это делают специальные программы, называющиеся расширителями DOS.


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