6. Иерархия средств для работы в защищённом режиме

Вы уже, наверное, обратили внимание на то, что программирование для защищённого режима значительно сложнее, чем для реального, и требует учёта большого количества важных деталей. Даже для того, чтобы просто перевести процессор i80286 в защищённый режим, требуется провести основательную подготовительную работу. Процессоры i80386 и i80486 значительно сложнее, чем i80286. Использование всех возможностей этих процессоров доступно только высококвалифицированным системным программистам и требует подробного ознакомления с руководствами по процессорам, поставляемыми фирмой Intel.

К счастью, существует программное обеспечение, облегчающее процесс создания программ для защищённого режима. Это программное обеспечение выступает в качестве интерфейса между программой пользователя и некоторой управляющей программой (например, мультизадачной операционной системой защищённого режима).

Мы рассмотрим средства, доступные программистам в среде MS-DOS версии 5.0 и MS WINDOWS версий 3.0 и 3.1.

Существует несколько уровней программной поддержки защищённого режима и поддержки работы с расширенной памятью:

Интерфейсом самого низкого уровня является интерфейс BIOS, предоставляемый программам в виде нескольких функций прерывания BIOS INT 15h. Интерфейс BIOS позволяет программе перевести процессор из реального режима в защищённый, переслать блок памяти из стандартной памяти в расширенную или из расширенной в стандартную. Этим все его возможности и ограничиваются. Интерфейс BIOS используется для старта мультизадачных операционных систем защищённого режима (таких, как OS/2) или в старых программах, работающих с расширенной памятью в защищённом режиме (например, СУБД ORACLE версии 5.1).

Назначение драйвера HIMEM.SYS и его возможности были подробно описаны во второй части второго тома "Библиотеки системного программиста" (глава 10). С помощью функций, предоставляемых этим драйвером, программа может выполнять различные действия с блоками расширенной памяти, а также управлять адресной линией A20. Основное различие между способом работы с расширенной памятью драйвера HIMEM.SYS и интерфейсом прерывания BIOS INT 15h заключается в том, что первый выполняет выделение программе и внутренний учёт блоков расширенной памяти, а второй рассматривает всю расширенную память как один непрерывный участок.

Если в системе установлен драйвер HIMEM.SYS, ваша программа не должна пользоваться прерыванием INT 15h во избежание конфликта со схемой распределения расширенной памяти, используемой драйвером. Однако драйвер HIMEM.SYS не открывает для программ доступ к защищённому режиму. Он полностью работает в реальном режиме, а для обращения к расширенной памяти использует либо недокументированную машинную команду LOADALL (если используется процессор 80286), либо возможности процессора 80386, который позволяет адресовать расширенную память в реальном режиме (при соответствующей инициализации системных регистров и таблиц).

В приложении мы описали действия, выполняемы командой LOADALL. Вы убедитесь, что команда полностью оправдывает своё название! (Load All - загрузить всё).

Следующий уровень - интерфейс EMS/VCPI. Во второй части второго тома "Библиотеки системного программиста", в главе 11, мы подробно рассматривали интерфейс EMS, который используется для работы с дополнительной памятью. Там же разъясняются отличия между расширенной и дополнительной памятью. Используя трансляцию страниц, некоторые драйверы памяти (например, EMM386 или QEMM) могут эмулировать присуствие дополнительной памяти, используя расширенную память. При этом стандартный набор функций управления дополнительной памятью, реализованный в рамках прерывания INT 67h, дополнен ешё несколькими функциями для работы в защищённом режиме процессора.

Эти новые функции реализуют интерфейс виртуальной управляющей программы VCPI (Virtual Control Programm Interface). Они позволяют устанавливать защищённый и виртуальный режимы работы процессора, работать с расширенной памятью на уровне страниц и устанавливать специальные отладочные регистры процессора i80386. Интерфейс VCPI облегчает использование механизма трансляции страниц, освобождая программиста от необходимости работать с системными регистрами процессора.

Интерфейс DPMI (DOS Protected Mode Interface - интерфейс защищённого режима для DOS) реализуется модулем, называющимся сервером DPMI. Этот интерфейс доступен для тех программ, которые работают на виртуальной машине WINDOWS или OS/2 версии 2.0 (позже мы обсудим некоторые детали, связанные с использованием интерфейса DPMI в WINDOWS).

Интерфейс DPMI предоставляет полный набор функций для создания однозадачных программ, работающих в защищённом режиме. В этом интерфейсе имеются функции для переключения из реального режима в защищённый и обратно (!), для работы с локальной таблицей дескрипторов LDT, для работы с расширенной и стандартной памятью на уровне страниц, для работы с прерываниями (в том числе для вызова прерываний реального режима из защищённого режима), для работы с отладочными регистрами процессора i80386. Это наиболее развитый интерфейс из всех рассмотренных ранее.

Последний, самый высокий уровень программной поддержки защищённого режима - расширители DOS или DOS-экстендеры (DOS-extender). Они поставляются, как правило, вместе со средствами разработки программ (трансляторами) в виде библиотек и компонуются вместе с создаваемой программой в единый загрузочный модуль.

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

К формируемому с помощью DOS-экстендера загрузочному модулю добавляются процедуры, необходимые для инициализации защищённого режима. Эти процедуры первыми получают управление и выполняют начальную инициализацию таблиц GDT, LDT, IDT, содержат обработчики прерываний и исключений, систему управления виртуальной памятью и т.д.

Пример DOS-экстендера, поставляемого вместе с транслятором - 386-DOS/Extender фирмы Phar Lap.

6.1. Интерфейс BIOS

Этот интерфейс реализуется в рамках прерывания BIOS INT 15h в компьютерах моделей IBM AT на основе процессоров i80286, i80386 или i80486.

Определить размер расширенной памяти

Регистры на входе:
AH      88h
Регистры на выходе:
AX      Размер доступной расширенной памяти в килобайтах.




Эта функция предназначена для определения размера расширенной памяти, доступной для использования функциями прерывания INT 15h.

Учтите, что если в системе установлен драйвер HIMEM.SYS, функция 88h может вернуть нулевой размер доступной расширенной памяти. Некоторые программы (например, СУБД Oracle версии 5.1) могут оказаться несовместимыми с драйвером HIMEM.SYS, так как они работают с расширенной памятью средствами прерывания INT 15h. Аналогичные проблемы могут возникнуть и при использовании других драйверов расширенной памяти, например, QEMM.

Как правило, драйверы расширенной памяти позволяют зарезервировать часть расширенной памяти для программ, использующих интерфейс INT 15h. Для этого необходимо задать соответствующие параметры. Например, для драйвера HIMEM.SYS размер зарезервированной расширенной памяти можно указать следующим образом:

device=c:\dos\himem.sys /int15=xxxx




В этой строке "xxxx" - размер зарезервированной памяти в килобайтах.

Переслать блок расширенной памяти

Регистры на входе:
AH      87h
CX      Размер пересылаемого блока в словах.
ES:SI   Адрес таблицы GDT, подготовленной специальным образом.
Регистры на выходе:
CARRY = 0       Функция выполнилась без ошибки.
AX      00h
В случае ошибки:
CARRY = 1       Произошла ошибка при пересылке блока.
AH      Код ошибки:

        01h - ошибка чётности;
        02h - произошло исключение;
        03h - сбой адресной линии A20.




Перед вызовом этой функции необходимо подготовить GDT, состоящую из 6 дескрипторов. Два первых и два последних дескриптора должны содержать нули. Третий дескриптор должен указывать на начало области памяти, из которой будет выполняться копирование. Четвёртый дескриптор должен указывать на область памяти, в которую будет выполняться копирование блока данных.

В третьем и четвёртом дескрипторе необходимо заполнить поля предела для копируемого блока памяти (в них должно быть записано значение CX*2 - 1), и поле доступа (значение 93):

Таблица 6. GDT для пересылки блока памяти средствами BIOS.

Смещение байта Содержимое
00h - 0Fh Это поле должно содержать нули.
10h - 11h Предел сегмента (CX*2 -1).
12h - 14h 24-разрядный физический адрес исходного блока памяти.
15h Байт доступа, должен быть равен 93h.
16h - 17h Это поле должно содержать нули.
18h - 19h Предел сегмента (CX*2 -1).
1Ah - 1Ch 24-разрядный физический адрес результирующего блока памяти.
1Dh Байт доступа, должен быть равен 93h.
1Eh - 2Fh Это поле должно содержать нули.

Для пересылки блока функция 87h переводит процессор в защищённый режим, используя подготовленную таблицу GDT. Так как указываются 24-разрядные физические адреса исходного и результирующего блоков, возможна пересылка блоков из любого места памяти в любое место памяти. Размер блока, очевидно, ограничен 64 килобайтами.

Пересылка блока выполняется в защищённом режиме обычной командой MOVS, причём во время пересылки прерывания запрещены. Перед возвратом функция выполняет сброс процессора и установку реального режима.

Вся процедура пересылки оказывается достаточно длительной, так как необходимо выполнить сброс процессора. Из-за того, что во время пересылки прерывания запрещены, возможен конфликт с устройствами ввода/вывода (потеря прерываний).

Установить защищённый режим работы процессора

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

Регистры на входе:
AH      89h
BH      Номер прерывания для IRQ0, используется для перепрограммирования первого контроллера прерывания. Этот номер должен быть кратен 8.
BL      Номер прерывания для IRQ8, используется для перепрограммирования второго контроллера прерывания. Этот номер также должен быть кратен 8.
ES:SI   Адрес таблицы GDT, подготовленной специальным образом.

Регистры на выходе:
CARRY = 0       Функция выполнилась без ошибки.
AH      00h
CS, DS, ES, SS  В эти регистры заносятся значения в соответствии с подготовленной перед вызовом функции таблицей GDT, адрес которой задаётся в регистрах ES:SI.

В случае ошибки:
CARRY = 1       Произошла ошибка при входе в защищённый режим.
AH      FF




Подготовленная перед вызовом функции 89h таблица GDT должна состоять из восьми дескрипторов:

Таблица 7. GDT для перехода в защищённый режим средствами BIOS.

0 Пустой дескриптор, содержит нули во всех полях.
1 Дескриптор, описывающий таблицу GDT.
2 Дескриптор, описывающий таблицу IDT.
3 Дескриптор для сегмента данных, сответствует селектору, который будет загружен в регистр DS.
4 Дескриптор дополнительного сегмента данных (регистр ES).
5 Дескриптор сегмента стека (регистр SS).
6 Дескриптор сегмента кода (регистр CS).
7 Этот дескриптор инициализировать не надо, он будет использоваться функцией 89h для адресации сегмента данных BIOS.

В рамках прерывания INT 15h нет функции для возврата из защищённого режима в реальный. Почему?

Потому, что во-первых, в защищённом режиме прерывание 15h зарезервировано фирмой Intel, во-вторых, для работы в защищённом режиме вами подготавливается таблица IDT и определяются заново все обработчики прерываний. Обработчики прерываний BIOS рассчитаны на работу в реальном режиме и после перехода в защищённый режим становятся недоступны.

Пример использования интерфейса BIOS

Наш пример демонстрирует использование функции 89h прерывания INT 15h для установки защищённого режима работы процессора. Программа устанавливает защищённый режим, выдаёт первое сообщение и через некоторое время, выдав второе сообещние, возвращается в реальный режим. После того, как будет нажата любая клавиша, работа программы будет завершена.

Обратите внимание на то, как в файле tos.c подготавливается таблица GDT. Адрес подготовленной таблицы передаётся функции protected_mode(), которая передаёт его функции 89h прерывания INT 15h. Вызов этой функции выполняется в файле tossyst.asm.

Листинг 16. Определение констант и структур данных

Файл tos.h
-----------------------------------------------------------

#define word unsigned int

// Селекторы, определённые в GDT

#define GDT_SELECTOR        0x08 // 1 - селктор для GDT
#define IDT_SELECTOR        0x10 // 2 - селектор для IDT
#define DATA_SELECTOR       0x18 // 3 - селектор для DS

#define VID_MEM_SELECTOR    0x20 // 4 - селектор для ES,
                        //     будет использован для адресации видеопамяти

#define SS_SELECTOR         0x28 // 5 - селектор для SS
#define CODE_SELECTOR       0x30 // 6 - селектор для CS
#define BIOS_SELECTOR      0x38 // 7 - селектор для адресации
                                                     // области данных BIOS

#define COLOR_VID_MEM       0xb8000L
#define MONO_VID_MEM        0xb0000L
#define MONO_MODE           0x07
#define BW_80_MODE          0x02
#define COLOR_80_MODE       0x03

typedef struct descriptor {
        word limit;
        word base_lo;
        unsigned char base_hi;
        unsigned char type_dpl;
        unsigned reserved;
} descriptor;

typedef struct gate {
        word offset;
        word selector;
        unsigned char count;
        unsigned char type_dpl;
        word reserved;
} gate;

#define DESCRIPTOR_SIZE     (sizeof(descriptor))
#define GATE_SIZE           (sizeof(gate))
#define IDT_SIZE            (sizeof(idt))

#define TYPE_CODE_DESCR     0x18
#define TYPE_DATA_DESCR     0x10
#define TYPE_INTERRUPT_GATE 0x86
#define TYPE_TRAP_GATE      0x87
#define SEG_WRITABLE        0x02
#define SEG_READABLE        0x02
#define SEG_EXPAND_DOWN     0x04
#define SEG_ACCESSED        0x01
#define SEG_PRESENT_BIT     0x80

#define EOI                     0x20
#define MASTER8259A     0x20
#define SLAVE8259A      0xA0

#define MK_LIN_ADDR(seg,off) (((unsigned long)(seg))<<4)+(word)(off)

Листинг 17. Главная программа

Файл tos.c
-----------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>
#include "tos.h"

void    Init_And_Protected_Mode_Entry(void);
void    protected_mode(descriptor far *gdt_ptr);
void    real_mode(void);
void    init_gdt_descriptor(descriptor *descr, unsigned long base,
                                word limit, unsigned char type);
void vi_print(unsigned int x, unsigned int y, char *s, char attr);
void vi_hello_msg(void);

void  exception_0(void); //{ prg_abort(0); }
void  exception_1(void); //{ prg_abort(1); }
void  exception_2(void); //{ prg_abort(2); }
void  exception_3(void); //{ prg_abort(3); }
void  exception_4(void); //{ prg_abort(4); }
void  exception_5(void); //{ prg_abort(5); }
void  exception_6(void); //{ prg_abort(6); }
void  exception_7(void); //{ prg_abort(7); }
void  exception_8(void); //{ prg_abort(8); }
void  exception_9(void); //{ prg_abort(9); }
void  exception_A(void); //{ prg_abort(0xA); }
void  exception_B(void); //{ prg_abort(0xB); }
void  exception_C(void); //{ prg_abort(0xC); }
void  exception_D(void); //{ prg_abort(0xD); }
void  exception_E(void); //{ prg_abort(0xE); }
void  exception_F(void); //{ prg_abort(0xF); }
void  exception_10(void); //{ prg_abort(0x10); }
void  exception_11(void); //{ prg_abort(0x11); }
void  exception_12(void); //{ prg_abort(0x12); }
void  exception_13(void); //{ prg_abort(0x13); }
void  exception_14(void); //{ prg_abort(0x14); }
void  exception_15(void); //{ prg_abort(0x15); }
void  exception_16(void); //{ prg_abort(0x16); }
void  exception_17(void); //{ prg_abort(0x17); }
void  exception_18(void); //{ prg_abort(0x18); }
void  exception_19(void); //{ prg_abort(0x19); }
void  exception_1A(void); //{ prg_abort(0x1A); }
void  exception_1B(void); //{ prg_abort(0x1B); }
void  exception_1C(void); //{ prg_abort(0x1C); }
void  exception_1D(void); //{ prg_abort(0x1D); }
void  exception_1E(void); //{ prg_abort(0x1E); }
void  exception_1F(void); //{ prg_abort(0x1F); }

void iret0(void);
void iret1(void);

descriptor      gdt[8];

gate    idt[] = {
 { (word)&exception_0, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 0
 { (word)&exception_1, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1
 { (word)&exception_2, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 2
 { (word)&exception_3, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 3
 { (word)&exception_4, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 4
 { (word)&exception_5, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 5
 { (word)&exception_6, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 6
 { (word)&exception_7, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 7
 { (word)&exception_8, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 8
 { (word)&exception_9, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 9
 { (word)&exception_A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // A
 { (word)&exception_B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // B
 { (word)&exception_C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // C
 { (word)&exception_D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // D
 { (word)&exception_E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // E
 { (word)&exception_F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // F
 { (word)&exception_10, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 10
 { (word)&exception_11, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 11
 { (word)&exception_12, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 12
 { (word)&exception_13, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 13
 { (word)&exception_14, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 14
 { (word)&exception_15, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 15
 { (word)&exception_16, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 16
 { (word)&exception_17, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 17
 { (word)&exception_18, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 18
 { (word)&exception_19, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 19
 { (word)&exception_1A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1A
 { (word)&exception_1B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1B
 { (word)&exception_1C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1C
 { (word)&exception_1D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1D
 { (word)&exception_1E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1E
 { (word)&exception_1F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1F

 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 20
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 21
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 22
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 23
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 24
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 25
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 26
 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 27

 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 28
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 29
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2A
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2B
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2C
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2D
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2E
 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }  // 2F
};

word y=0;

void main(void) {

        textcolor(BLACK); textbackground(LIGHTGRAY); clrscr();
        Init_And_Protected_Mode_Entry();
        enable_interrupt();

        vi_hello_msg();

        y=3;
        vi_print(0, y++, " Вошли в защищённый режим", 0x7f);
        pause();
        vi_print(0, y++,
         " Для возврата в реальный режим нажмите любую клавишу", 0x7f);

        real_mode();
        getch();
        textcolor(WHITE); textbackground(BLACK); clrscr();
}

void init_gdt_descriptor(descriptor *descr,
                unsigned long base, word limit, unsigned char type) {

        descr->base_lo  = (word)base;
        descr->base_hi  = (unsigned char)(base >> 16);
        descr->type_dpl = type;
        descr->limit    = limit;
        descr->reserved = 0;
}

void Init_And_Protected_Mode_Entry(void) {

        union REGS r;
        word  crt_mode;
        extern word gv1_;

// Дескриптор, описывающий таблицу GDT

        init_gdt_descriptor(&gdt[1], MK_LIN_ADDR(_DS, &gdt),
                sizeof(gdt)-1,
                TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);

// Дескриптор, описывающий таблицу IDT

        init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, &idt),
                (unsigned long)IDT_SIZE-1,
                TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);

// Дескриптор сегмента данных

        init_gdt_descriptor(&gdt[3], MK_LIN_ADDR(_DS, 0),
                0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);

// Определяем текущий видеорежим

        r.h.ah=15;
        int86(0x10,&r,&r);
        crt_mode = r.h.al;

// Инициализация дескриптора для видеопамяти
// монохромного видеоадаптера

        if(crt_mode == MONO_MODE)
                init_gdt_descriptor(&gdt[4], MONO_VID_MEM,
                        3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);

// Инициализация дескриптора для видеопамяти
// цветного видеоадаптера

        else if(crt_mode == BW_80_MODE || crt_mode == COLOR_80_MODE)
                init_gdt_descriptor(&gdt[4], COLOR_VID_MEM,
                        3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);
        else {
                printf("\nИзвините, этот видеорежим недопустим.");
                exit(-1);
        }

// Дескриптор для сегмента стека

        init_gdt_descriptor(&gdt[5], MK_LIN_ADDR(_DS, 0),
                0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);

// Дескриптор для сегмента кода

        init_gdt_descriptor(&gdt[6], MK_LIN_ADDR(_CS, 0),
                0xffffL, TYPE_CODE_DESCR | SEG_PRESENT_BIT | SEG_READABLE);

// Входим в защищённый режим
// В качестве параметра передаём адрес подготовленной
// таблицы GDT

        protected_mode(gdt);
}

void prg_abort(int err);

void  exception_0(void) { prg_abort(0); }
void  exception_1(void) { prg_abort(1); }
void  exception_2(void) { prg_abort(2); }
void  exception_3(void) { prg_abort(3); }
void  exception_4(void) { prg_abort(4); }
void  exception_5(void) { prg_abort(5); }
void  exception_6(void) { prg_abort(6); }
void  exception_7(void) { prg_abort(7); }
void  exception_8(void) { prg_abort(8); }
void  exception_9(void) { prg_abort(9); }
void  exception_A(void) { prg_abort(0xA); }
void  exception_B(void) { prg_abort(0xB); }
void  exception_C(void) { prg_abort(0xC); }
void  exception_D(void) { prg_abort(0xD); }
void  exception_E(void) { prg_abort(0xE); }
void  exception_F(void) { prg_abort(0xF); }
void  exception_10(void) { prg_abort(0x10); }
void  exception_11(void) { prg_abort(0x11); }
void  exception_12(void) { prg_abort(0x12); }
void  exception_13(void) { prg_abort(0x13); }
void  exception_14(void) { prg_abort(0x14); }
void  exception_15(void) { prg_abort(0x15); }
void  exception_16(void) { prg_abort(0x16); }
void  exception_17(void) { prg_abort(0x17); }
void  exception_18(void) { prg_abort(0x18); }
void  exception_19(void) { prg_abort(0x19); }
void  exception_1A(void) { prg_abort(0x1A); }
void  exception_1B(void) { prg_abort(0x1B); }
void  exception_1C(void) { prg_abort(0x1C); }
void  exception_1D(void) { prg_abort(0x1D); }
void  exception_1E(void) { prg_abort(0x1E); }
void  exception_1F(void) { prg_abort(0x1F); }

void prg_abort(int err) {

        vi_print(1,y++,"---> Произошло исключение", 0xc);
        real_mode();
        gotoxy(1,24);
        cprintf("Исключение %X, нажмите любую клавишу", err);
        getch();

        textcolor(WHITE);       textbackground(BLACK); clrscr();
        exit(0);

}

void iret0(void) {
        asm {
                push    ax
                mov     al,EOI
                out     MASTER8259A,al
                pop     ax
                pop bp
                iret
        }
}

void iret1(void) {
        asm {
                push    ax
                mov     al,EOI
                out     MASTER8259A,al
                out     SLAVE8259A,al
                pop     ax
                pop bp
                iret
        }
}

void vi_putch(unsigned int x, unsigned int y ,char c, char attr) {

        register unsigned int offset;
        char far *vid_ptr;

        offset=(y*160) + (x*2);
        vid_ptr=MK_FP(VID_MEM_SELECTOR, offset);
        *vid_ptr++=c; *vid_ptr=attr;
}

void vi_print(unsigned int x, unsigned int y, char *s, char attr) {
        while(*s) vi_putch(x++, y, *s++, attr);
}

void vi_hello_msg(void) {

        vi_print(0, 0,
                " Protected mode monitor *TINY/OS*, "
                "v.1.11 for CPU 80286 ¦ © Frolov A.V., 1992 ", 0x30);

}

Листинг 18. Функции для перехода в защищённый режим и
        возврата в реальный режим.

Файл tossyst.asm
-----------------------------------------------------------

        IDEAL
        MODEL SMALL
        RADIX   16
        P286

        DATASEG

CMOS_PORT               EQU     70
PORT_6845               EQU     63h
COLOR_PORT              EQU     03d4h
MONO_PORT               EQU     03b4h
STATUS_PORT             EQU     64h
SHUT_DOWN               EQU     0feh
INT_MASK_PORT           EQU     21h
VIRTUAL_MODE            EQU     0001
A20_PORT                EQU     0d1
A20_ON                  EQU     0df
A20_OFF                 EQU     0ddh
EOI                     EQU     20
MASTER8259A             EQU     20
SLAVE8259A              EQU     0a0h
KBD_PORT_A              EQU     60h
KBD_PORT_B              EQU     61h

        gdt_off         dw       ?
        gdt_seg         dw  ?

        real_ss         dw      ?
        real_sp         dw      ?
        real_es         dw      ?

CODESEG

        PUBLIC  _real_mode, _protected_mode
        PUBLIC  _enable_interrupt
        PUBLIC   _pause

PROC _protected_mode NEAR
                push    bp
                mov     bp,sp

                mov     ax,[bp+4]
                mov     dx,[bp+6]
                mov     [gdt_seg], dx
                mov     [gdt_off], ax

                push            ds
                mov             ax,40
                mov             ds,ax
                mov             [WORD 67],OFFSET shutdown_return
                mov             [WORD 69],cs
                pop             ds

                cli
                in                      al, INT_MASK_PORT
                and             al, 0ffh
                out             INT_MASK_PORT, al

                mov             al,8f
                out             CMOS_PORT,al
                jmp             delay1
delay1:
                mov             al,5
                out             CMOS_PORT+1,al

                mov             [real_ss],ss
                mov             [real_es],es

; Загружаем регистры ES:SI адресом GDT, полученным
; как параметр функции protected_mode()

                mov             es, [gdt_seg]
                mov             si, [gdt_off]

; Подготавливаем номера прерываний IRQ0 и IRQ8
; для перепрограммирования контроллеров прерываний.

                mov             bx, 2028h

; Устанавливаем защищённый режим работы

                mov             ax, 8900h
                int             15h
                jnc             pok

; Если произошла ошибка, мы остались в реальном режиме,
; завершаем работу программы.

                mov     ah, 4ch
                int     21h

; Установлен защищённый режим работы процессора !

pok:
                pop     bp
                ret
ENDP _protected_mode

PROC _real_mode   NEAR

                mov             [real_sp], sp
                mov             al, SHUT_DOWN
                out             STATUS_PORT, al
waitr1:
                hlt
                jmp             waitr1

LABEL   shutdown_return FAR

                mov             ax, DGROUP
                mov             ds, ax

assume  ds:DGROUP

                cli
                mov     ss,[real_ss]
                mov     sp,[real_sp]

                in      al, INT_MASK_PORT
                and     al, 0
                out     INT_MASK_PORT, al

                mov     ax, DGROUP
                mov     ds, ax
                mov     ss, ax
                mov     es, ax

                mov     ax,000dh
                out     CMOS_PORT,al
                sti
                ret
ENDP _real_mode


PROC    _pause          NEAR

        push    cx
        mov     cx,10
ploop0:
        push    cx
        xor     cx,cx
ploop1:
        loop    ploop1
        pop     cx
        loop    ploop0

        pop     cx
        ret

ENDP    _pause

PROC    _enable_interrupt NEAR

                sti
                in                      al, INT_MASK_PORT
                and             al, 0fch
                out             INT_MASK_PORT, al

                ret
ENDP    _enable_interrupt

        end




6.2. Интерфейс HIMEM.SYS

Как мы уже говорили, назначение драйвера HIMEM.SYS и его возможности были подробно описаны во второй части второго тома "Библиотеки системного программиста" (глава 10). Поэтому здесь мы не будем подробно рассматривать эти функции и говорить об их использовании, а приведём только краткий перечень:

Таблица 8. Функции XMS.

00h Получить версию XMS (XMS - eXtended Memory Specification - спецификация расширенной памяти).
01h Запросить управление областью старшей памятью HMA.
02h Освободить область HMA.
03h Глобальное разрешение линии A20.
04h Глобальное запрещение линии A20.
05h Локальное разрешение линии A20.
06h Локальное запрещение линии A20.
07h Определение текущего состояния линии A20.
08h Определение размера свободной расширенной памяти.
09h Получить блок расширенной памяти EMB.
0Ah Освободить блок EMB.
0Bh Копирование блоков расширенной памяти EMB.
0Ch Блокирование блока EMB. Для заблокированного EMB можно определить его физический адрес.
0Dh Разблокирование EMB.
0Eh Получить информацию об индексе EMB.
0Fh Изменить размер блока EMB.
10h Запросить управление областью UMB.
11h Освободить область UMB.

Для проверки наличия в системе драйвера, поддерживающего спецификацию XMS, необходимо загрузить в регистр AX значение 4300h и вызвать прерывание INT 2Fh. Если в регистре AL окажется значение 80h, драйвер установлен. В этом случае можно получить адрес управляющей программы, которую надо вызывать для выполнения функций. Если загрузить в регистр AX значение 4310h и вызвать прерывание INT 2Fh, в регистрах ES:BX будет записан искомый адрес.

Как можно заметить, функции XMS позволяют управлять линией A20 и копировать блоки расширенной памяти. Но вы не найдёте среди них функции для перехода в защищённый режим!

И это не случайно. Всё дело в том, что для работы с расширенной памятью драйвер HIMEM.SYS, реализующий спецификацию XMS, не использует защищённый режим работы процесора!

Как это может быть? Ведь процессор, находясь в реальном режиме не может адресовать память за границей первого мегабайта.

Секрет заключается в том, что для процессора i80286 драйвер HIMEM.SYS использует недокументированную машинную команду LOADALL. Эта команда предназначена для тестирования процессора и не приведена в документации на процессор i80286 (см. описание команды LOADALL в приложении).

С помощью этой команды можно выполнить загрузку всех регистров процессора, в том числе и некоторых недоступных программам. Используя нестандартную загрузку регистров, драйвер HIMEM.SYS может адресовать расширенную память, находясь в реальном режиме.

Такой способ адресации расширенной памяти значительно ускоряет процесс копирования по сравнению с использованием функции 87h прерывания INT 15h. Кроме того, во время копирования функцией 0Bh драйвера HIMEM.SYS прерывания остаются разрешёнными.

Процессор i80386 тоже имеет недокументированную команду LOADALL, которая отличается от имеющейся в процессоре i80286 и кодом, и выполняемыми действиями. Однако процессор i80386 способен адресовать расширенную память, находясь в реальном режиме (при соответствующей загрузке системных регистров, которая может быть выполнена с использованием только документированных команд).

В том, что на уровне интерфейса HIMEM.SYS не используется защищённый режим, заключена ещё одна причина, по которой мы не стали подробно рассматривать этот интерфейс в книге, посвящённой защищённому режиму.

Использование интерфейса HIMEM.SYS оправдано в тех случаях, когда для работы вашей программы требуется буфер памяти большого размера, который может быть размещён только в расширенной памяти. Однако программа не сможет непосредственно адресовать этот буфер и вынуждена выполнять операцию копирования данных из стандартной памяти в расширенную и обратно, что ведёт к непроизводительной потере времени.

В приложении описана утилита MEMOSCOP. Эта утилита выводит на экран список установленных в системе драйверов дополнительной памяти и доступных интерфейсов с защищённым режимом. В файле xmmc.asm находятся функции для работы с интерфейсом XMS. Этот файл и сами функции XMS были подробно описаны во втором томе "Библиотеки системного программиста".

6.3. Интерфейс EMS/VCPI

Во второй части второго тома "Библиотеки системного программиста" (глава 11) мы рассказывали вам о дополнительной памяти и об использовании для работы с ней спецификации EMS - Expanded Memory Specification.

Драйверы дополнительной памяти предоставляют программам интерфейс прерывания INT 67h, который мы тогда подробно описали. Мы также говорили о том, что для компьютеров на базе процессоров i80386 или i80486 существуют драйверы памяти, эмулирующие дополнительную память с использованием расширенной. Самые известные драйверы такого типа - EMM386.SYS и QEMM.SYS.

Эти драйверы используют защищённый (точнее, виртуальный) режим работы процессора i80386 и страничную адресацию расширенной памяти. Для прикладных программ предоставляется интерфейс, который называется VCPI - Virtual Programm Control Interface. Этот интерфейс реализован как подфункции функции DEh прерывания INT 67h:

Таблица 9. Функции интерфейса VCPI.

Подфункция Выполняемые действия
00 Проверить наличие в системе интерфейса VCPI.
01 Получить адрес точки входа для работы с интерфейсом VCPI.
02 Определить максимальный физический адрес памяти.
03 Определить количество свободных страниц памяти размером 4 килобайта.
04 Получить страницу памяти.
05 Освободить страницу памяти.
06 Получить физический адрес страницы памяти, располагающейся в пределах первого мегабайта, т.е. в стандартной памяти.
07 Прочитать содержимое системного регистра CR0.
08 Прочитать содержимое отладочных регистров.
09 Установить отладочные регистры.
0A Получить отображение векторов прерываний, используемых контроллерами прерываний 8259.
0B Установить отображение векторов прерываний, используемых контроллерами прерываний 8259.
0C Переключить процессор из реального в защищённый режим, а также из защищённого в виртуальный режим.

Перед вызовом прерывания INT 67h регистр AH должен содержать DEh, а номер требуемой подфункции должен быть загружен в регистр AL. Кроме того, прежде чем вызывать прерывание INT 67h, в самом начале работы программы необходимо убедиться в том, что в системе установлен драйвер EMS. О том, как это сделать, мы рассказывали в главе 11 второго тома "Библиотеки системного программиста". Там же приведён соответствующий пример программы.

Функции VCPI позволяют перевести процессор в защищённый или виртуальный режим работы и предоставляют программам полноценный доступ к расширенной памяти. Поэтому использование интерфейса VCPI более предпочтительно, чем интерфейса драйвера HIMEM.SYS, особенно в тех случаях, когда требуется интенсивная работа с расширенной памятью.

Другое принципиальное новшество интерфейса VCPI - поддержка схемы преобразования адресов процессоров i80386/i80486, а именно страничной памяти. С помощью VCPI программа может легко получать и освобождать страницы памяти, не работая непосредственно с системными регистрами процессора.

Драйверы EMM386 и QEMM обеспечивают для программ DOS интерфейс VCPI и сами пользуются этим интерфейсом. Вы знаете, что в пределах первого мегабайта адресного пространства имеется 640 килобайт памяти. Остальная память используется видеоадаптерами, ПЗУ BIOS и другой аппаратурой. Вся эта память называется зарезервированной памятью. Зарезервированная память задействована не полностью, в ней есть окна. Страницы памяти, соответствующие свободным окнам, с использованием механизма трансляции страниц отображаются в адресное пространство за пределами первого мегабайта памяти, т.е. на расширенную память.

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

Рассмотрим функции интерфейса VCPI более подробно.

Проверка наличия в системе интерфейса VCPI

Регистры на входе:
AX      0DE00h

Регистры на выходе:
AH      равен 00h - если VCPI установлен,
       не равен 00h - если VCPI не установлен.
BH      Верхний (major) номер версии VCPI.
BL      Нижний (minor) номер версии VCPI.




Получить адрес интерфейса VCPI

Регистры на входе:
AX      0DE01h
ES:DI   Адрес буфера размером в 4 килобайта для таблицы страниц.
DS:SI   Адрес GDT, состоящей из трёх элементов, в первый будет записан   дескриптор сегмента кода, остальные два будут использованы драйвером VCPI.

Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.
DI      Номер первого свободного элемента в таблице страниц, которая   размещена в заказанном ранее буфере.
EBX     Смещение в сегменте кода точки входа в защищённый режим.




Определить максимальный физический адрес памяти

Регистры на входе:
AX      0DE02h
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.
EDX     Максимальный физический адрес страницы памяти размером 4 килобайта.




Определить количество свободных страниц памяти

Регистры на входе:
AX      0DE03h
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.
EDX     Количество свободных страниц памяти, доступных для всех задач в системе.




Эта функция доступна в защищённом режиме через вызов драйвера в его интерфейсной точке, адрес которой можно получить с помощью функции 01h.

Получить страницу памяти

Регистры на входе:
AX      0DE04h
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.
EDX     Физический адрес полученной страницы памяти.




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

Эта функция доступна в защищённом режиме через вызов драйвера в его интерфейсной точке.

Освободить страницу памяти

Регистры на входе:
AX      0DE05h
EDX     Физический адрес освобождаемой страницы памяти.
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.




Эта функция доступна в защищённом режиме через вызов драйвера в его интерфейсной точке.

Получить физический адрес страницы памяти, располагающейся в пределах первого мегабайта

Регистры на входе:
AX      0DE06h
CX      Номер страницы, равен линейному адресу страницы, сдвинутому вправо на 12 бит.
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - неправильный номер страницы.
EDX     Физческий адрес страницы.




Прочитать содержимое системного регистра CR0

Регистры на входе:
AX      0DE07h
Регистры на выходе:
AH      00h
EBX     Значение системного регистра CR0.




Прочитать содержимое отладочных регистров

Регистры на входе:
AX      0DE08h
ES:DI   Адрес буфера размером 8 двойных слов.
Регистры на выходе:
AH      00h
EBX     Значение системного регистра CR0.




В буфере располагается содержимое отледочных регистров в следующем порядке: DR0, DR1, DR3, DR4, DR5, DR6, DR7. Регистры DR4 и DR5 зарезервированы и в процессоре i80386 не используются.

Установить отладочные регистры

Регистры на входе:
AX      0DE09h
ES:DI   Адрес буфера размером 8 двойных слов, содержащего новые значения для отладочных регистров.
Регистры на выходе:
AH      00h
EBX     Значение системного регистра CR0.




Значения, подготовленные для зарезервированных регистров DR4 и DR5, игнорируются.

Получить отображение векторов прерываний для контроллеров прерываний 8259

Регистры на входе:
AX      0DE0Ah
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.
BX      Вектор прерывания, используемый для IRQ0.
CX      Вектор прерывания, используемый для IRQ8.




Установить отображение векторов прерываний для контроллеров прерываний 8259

Регистры на входе:
AX      0DE0Bh
BX      Вектор прерывания, используемый для IRQ0.
CX      Вектор прерывания, используемый для IRQ8.
Регистры на выходе:
AH      равен 00h - успешное выполнение функции,
не равен 00h - ошибка.




После выполнения этой функции прерывания запрещены. Перед завершением своей работы программа должна установить прежнее отображение векторов для контроллеров прерываний.

Переключить процессор в защищённый режим

Регистры на входе:
AX      0DE0Ch
ESI     Линейный адрес массива значений для системных регистров, массив должен располагаться в первом мегабайте памяти.
Регистры на выходе:
Загружаются регистры GDTR, IDTR, LDTR, TR. В стеке, на который указывают регистры SS:ESP, необходимо отвести по крайней мере 16 байт для возможности обработки прерываний.




Содержимое регистров EAX, ESI, DS, ES, FS, GS после выполнения функции будет потеряно.

Перед вызовом функции прерывания должны быть запрещены. После выполнения переключения в защищённый режим прерывания также запрещены.

Приведём формат области для загрузки системных регистров перед переходом в защищённый режим:

Таблица 10. Формат буфера для загрузки регистров и перехода в защищённый режим средствами VCPI.

Смещение Размер и назначение
00h DWORD, значение для регистра CR3.
04h DWORD, линейный адрес в пределах первого мегабайта для загрузки регистра GDTR.
08h DWORD, линейный адрес в пределах первого мегабайта для загрузки регистра IDTR.
0Ch WORD, значение для регистра LDTR.
0Eh WORD, значение для регистра TR.
10h PWORD, значение адреса CS:EIP точки входа в защищённый режим.

Переключить процессор в виртуальный режим

Это переключение можно выполнить, если находясь в защищённом режиме вызвать точку интерфейса VCPI с регистрами, загруженными следующим образом:

AX      DE0Ch
DS      Селектор, полученный от функции DE01h.
SS:ESP  Стек должен быть расположен в пределах первого мегабайта памяти




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

Приведём пример программы, определяющей присуствие в системе драйвера дополнительной памяти XMM. Если этот драйвер присутствует, программа проверяет поддержку этим драйвером интерфейса VCPI. Затем, если интерфейс VCPI поддерживается, программа выводит его версию на экран.

Листинг 19. Определение версии VCPI
Файл vcpi.c
-----------------------------------------------------------


#include <stdio.h>
#include <dos.h>

void main(void) {

        unsigned err;
        char ver, ver_hi, ver_lo;

        clrscr();
        printf("Virtual Control Program Interface Demo, © Frolov A.V., 1992\n\r"
                  "-------------------------------------------------------------\n\r\n\r");

// Проверяем наличие драйвера EMS/VCPI

        if(ems_init()) {
                printf("Драйвер EMS/VCPI не загружен.");
                exit(-1);
        }
        printf("Драйвер EMS/VCPI загружен");

// Выводим номер версии драйвера

        if((err = ems_ver(&ver)) != 0) {
                printf("\nОшибка %02.2X при определении версии EMM", err);
                exit(-1);
        }
        printf("\nВерсия EMM: %02.2X", ver);

// Определяем присутствие VCPI и его версию

        if(vcpi_ver(&ver_hi, &ver_lo) != 0) {
                printf("\nДрайвер EMM не поддерживает VCPI\n");
                exit(-1);
        }
        printf("\nВерсия VCPI: %02.2X.%02.2X", ver_hi, ver_lo);
}

/**
*.Name         ems_init
*.Title        Функция проверяет установку драйвера EMS
*
*.Descr        Эта функция проверяет наличие драйвера EMS
*
*.Proto        int ems_init(void);
*
*.Params       Не используются
*
*.Return       0 - драйвер EMS установлен;
*              1 - драйвер EMS не установлен.
*
*.Sample       ems_test.c
**/

int ems_init(void) {

        void (_interrupt _far *EMS_driver_adr)(void);
        char _far *EMS_driver_name;
        char test_name[8];
        int i;

        EMS_driver_adr = _dos_getvect(0x67);

        FP_SEG(EMS_driver_name) = FP_SEG (EMS_driver_adr);
        FP_OFF(EMS_driver_name) = 10;

        for(i=0; i<8; i++) test_name[i] = EMS_driver_name[i];

        if(strncmp(test_name, "EMMXXXX0", 8) == 0) return(0);
        else return(1);

}

/**
*.Name         ems_ver
*.Title        Определение версии драйвера EMS
*
*.Descr        Эта функция возвращает номер версии
*              драйвера EMS в двоично-десятичном формате.
*
*.Proto        int ems_ver(char *ver);
*
*.Params       char *ver - указатель на байт, в который
*                 будет записан номер версии.
*
*.Return       Номер версии драйвера EMS в формате BCD
*
*.Sample       ems_test.c
**/

int ems_ver(char *ver) {

        union REGS reg;

        reg.x.ax = 0x4600;
        int86(0x67, &reg, &reg);

        *ver = reg.h.al;
        return(reg.h.ah);
}

int vcpi_ver(char *ver_hi, char *ver_lo) {

        union REGS reg;

        reg.x.ax = 0xDE00;
        int86(0x67, &reg, &reg);

        *ver_hi = reg.h.bh;
        *ver_lo = reg.h.bl;
        return(reg.h.ah);
}




6.4. Интерфейс DPMI

Для создания программ, работающих в защищённом режиме, фирмы Microsoft, Intel, IBM, Lotus, Phar Lap, Rational Systems, Borland, Quarterdeck разработали интерфейс с защищённым режимом - DPMI (DOS Protected Mode Interface). Пользуясь этим интерфейсом, программы, стартующие как обычные DOS-программы реального режима, могут переключиться в защищённый режим и работать с расширенной или даже виртуальной памятью. Интерфейс DPMI обеспечивается специальной программой, называемой сервером DPMI. Сервером может быть отдельная резидентная программа, либо средства для поддержки DPMI могут входить в состав операционной системы (например, WINDOWS и OS/2 версии 2.0 позволяют работающим под их управлением DOS-программам использовать интерфейс DPMI).

В этом разделе мы кратко опишем функции DPMI, а в разделе, посвященном виртуальной машине WINDOWS, приведём пример программы, работающей в защищённом режиме с использованием функций DPMI спецификации 0.9. Спецификация DPMI версии 0.9 поддерживается операционной системой WINDOWS версий 3.0 и 3.1, работающей в расширенном режиме на процессорах i80386 или i80486.

Все функции DPMI реализованы в рамках прерывания INT 31h, обработчиком этого прерывания является сервер DPMI. Прежде чем использовать функции DPMI, программа с помощью описанных ниже функций прерываний INT 2Fh должна убедиться в присутствии поддержки DPMI, получить адрес процедуры переключения в защищённый режим и выполнить это переключение.

Программа должна вызывать прерывание INT 31h после переключения в защищённый режим, в реальном режиме это прерывание вызывать нельзя.

Получить адрес перехода в защищённый режим

С помощью этой функции программа может определить наличие в системе интерфейса DPMI и получить адрес процедуры перехода в защищённый режим работы.

Для вызова этой функции необходимо использовать прерывание INT 2Fh, загрузив регистр AX следующим образом:

Регистры на входе:
AX      1687h




Если функция была успешно выполнена, в регистры будут записаны следующие значения:

Регистры на выходе:
AX      00h
BX      Если установлен бит 0, данная реализация DPMI поддерживает работу с 32-разрядными программами.
CL      Тип процессора:
     02h = 80286
     03h = 80386
     04h = 80486
DH      Верхний (major) номер версии используемой спецификации DPMI.
DL      Нижний (minor) номер версии используемой спецификации DPMI.
SI      Количество параграфов памяти, требуемых для личной области данных сервера DPMI. Программа должна заказать эту память у операционной системы перед переходом в защищённый режим.
ES:DI   Адрес процедуры, которую необходимо вызвать для входа в защищённый режим.




Если выполнение функции закончилось с ошибкой, содержимое регистра AX не равно нулю.

Вход в защищённый режим

После того, как программа получила адрес процедуры для входа в защищённый режим, она может вызвать эту процедуру при помощи команды CALL. Перед вызовом необходимо загрузить регистры:

Регистры на входе:
AX      Разрядность программы. Если программа является 32-разрядной, в регистре AX необходимо установить бит 0 в единицу.
ES      В этот регистр необходимо загрузить сегментный адрес буфера, который будет использован сервером DPMI. Размер буфера должен быть определён при помощи предыдущей функции (регистр SI).




После загрузки регистров необходимо выполнить вызов процедуры с адресом, который был получен в регистрах ES:DI после вызова предыдущей функции.

Если функция выполнилась успешно, флаг CARRY сброшен и программа выполняется в защищённом режиме.

Регистры на выходе
CS      Селектор, которому соответствует базовый адрес сегмента кода программы и предел 64 килобайта.
SS      Селектор для сегмента стека, базовый адрес соответствует стеку реального режима, предел - 64 килобайта.
DS      Селектор, соответствующий сегменту данных реального режима, предел - 64 килобайта.
ES      Селектор, указывающий на PSP программы с пределом 100h байт.
FS, GS  0 (если программа работает на процессоре i80386 или i80486).
ESP     Если программа работает в 32-разрядном режиме, старшее слово регистра ESP будет равно 0.




Остальные регистры не изменяются.

Если функция выполнилась с ошибкой, флаг CARRY устанавливается в единицу и программа продолжает выполнение в реальном режиме.

После входа в защищённый режим вам становится доступен интерфейс DPMI через функции прерывания INT 31h.

Для завершения своей работы программа, использующая DPMI, должна выдать прерывание INT 21h (функция 4Ch) - это обычный способ завершения программ, работающих в среде MS-DOS.

Приведём фрагмент программы, использующей DPMI. На примере этого фрагмента мы покажем, как убедиться в том, что в системе имеется интерфейс DPMI, как войти в защищённый режим и завершить работу программы.

; Получаем адрес точки входа в защищённый режим

        mov     ax, 1687h
        int     2Fh
        test    ax, ax
        jnz     Cant_Enter_PMode ; не можем войти в защищённый режим

        mov     [PMode_Entry_Seg], es
        mov     [PMode_Entry_Off], di

; Заказываем память для сервера DPMI (если это требуется)

        test    si, si
        jz      Enter_PMode_Now
        mov     bx, si
        mov     ah, 48h
        int     21h
        jc      Cant_Enter_PMode
        mov     es, ax

; Устанавливаем защищённый режим

Enter_PMode_Now:
        xor     ax, ax
        call    DWORD PTR [PMode_Entry_Off]
        jc      Cant_Enter_PMode

; Программа работает в защиённом режиме.

;      Здесь располагаются строки
;      вашей программы.

; Завершение программы и возврат в DOS

        mov     ax, 4C00h
        int     21h




Определение текущего режима работы

Если ваша программа может работать и в реальном режиме и в защищённом, с помощью этой функции вы можете определить текущий режим работы.

Для выполнения функции необходимо вызвать прерывание INT 2Fh, предварительно загрузив регистр AX:

Регистры на входе:
AX      1686h
Регистры на выходе:
AX      0, если программа работает в защищённом режиме под управлением DPMI,
не равно 0, если программа работает в реальном или виртуальном режиме.




Создать дескриптор в таблице LDT

С помощью этой функции программа может создать один или несколько дескрипторов в LDT, принадлежащей задаче, в рамках которой работает программа, т.е. для своей задачи.

Вся работа по инициализации полученных дескрипторов должна выполняться самой программой, для чего в интерфейсе DPMI имеются соответствующие функции.

Регистры на входе:
AX      0000h
CX      Количество создаваемых дескрипторов.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
AX      Базовый селектор.




Если программа заказывала несколько селекторов, AX содержит первый селектор из созданного массива. Для получения остальных селекторов необходимо воспользоваться функцией 00003h.

Освободить дескриптор в таблице LDT

Функция используется для освобождения дескрипторов, созданных в LDT предыдущей функцией.

Регистры на входе
AX      0001h
BX      Селектор освобождаемого дескриптора.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Для освобождения массива дескрипторов функцию необходимо вызывать отдельно для каждого дескриптора.

Преобразование сегмента в дескриптор

Функция используется для преобразования сегментной компоненты адреса реального режима в дескриптор.

Регистры на входе
AX      0002h
BX      Сегментный адрес реального режима.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
AX      Селектор, соответствующий дескриптору, созданному для адресации сегмента реального режима.




Дескриптор, созданный данной функцией, не может быть освобождён, поэтому эту функцию имеет смысл использовать для адресации таких областей памяти, которые будут нужны на всём протяжении работы программы (область данных BIOS, память видеоконтроллера и т.д.).

Получить значение инкремента для вычисления следующего селектора в массиве

Если вы создали несколько дескрипторов в LDT при помощи функции 0000h, то этой функцией будет возвращён только первый селектор, соответствующий первому дескриптору в созданном массиве. Для вычисления остальных селекторов ваша программа должна использовать функцию 0003h. Эта функция возвращает значение, которое вы будете прибавлять к базовому селектору для вычисления остальных селекторов в массиве.

Регистры на входе
AX      0003h
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
AX      Значение для инкремента базового селектора.




Зарезервированные функции

Функции 0004h и 0005h в спецификации DPMI версии 0.9 зарезервированы и не должны вызываться из вашей программы.

Получить базовый адрес сегмента

С помощью этой функции ваша программа может определить 32-разрядный базовый адрес сегмента по его селектору.

Регистры на входе
AX      0006h
BX      Селектор сегмента, для которого требуется получить базовый адрес.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
CX:DX   32-разрядный базовый адрес сегмента.




Установить базовый адрес сегмента

Эта функция устанавливает базовый адрес сегмента в дескрипторе, соответствующем заданному селектору. Вы можете модифицировать только те дескрипторы, которые создала ваша программа.

Регистры на входе
AX      0007h
BX      Селектор сегмента, для которого требуется установить базовый адрес.
CX:DX   Устанавливаемый 32-разрядный линейный базовый адрес сегмента.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Установить предел сегмента

Эта функция устанавливает предел сегмента в дескрипторе, соответствующем заданному селектору.

Регистры на входе
AX      0008h
BX      Селектор сегмента, для которого требуется установить новое значение предела.
CX:DX   Устанавливаемое 32-разрядное значение предела сегмента.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Установить поле прав доступа в дескрипторе

При помощи этой функции вы можете модифицироать поле прав доступа в дескрипторе, созданного вашей программой.

Регистры на входе
AX      0009h
BX      Селектор сегмента, для которого требуется установить новые права доступа.
CL      Байт прав доступа.
CH      Расширение байта прав доступа для процессора i80386/i80486, используется только 32-разрядными реализациями DPMI.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




В расширении байта прав доступа старший бит (бит 7) - бит гранулярности, который определяет интерпретацию поля предела (0 - предел в байтах, 1 - предел в страницах). Бит 6 указывает режим работы процессора (0 - 16-разрядный, 1 - 32-разрядный). Бит 5 и 2-3 должны быть равны 0. Бит 4 может содержать любое значение.

Создание алиасного дескриптора для сегмента кода

Так как в защищённом режиме модификация сегмента кода с использованием селектора CS невозможна, для решения такой задачи необходимо создать дескриптор сегмента данных, который бы указывал на сегмент кода (т.е. имел бы такой же базовый адрес и предел). Такой дескриптор является алиасом дескриптору сегмента кода и может быть создан при помощи функции 000Ah.

Если алиас больше не нужен, он может быть уничтожен при помощи функции 0001h.

Регистры на входе
AX      000Ah
BX      Селектор сегмента кода, для которого требуется создать алиас.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Получить дескриптор

С помощью этой функции программа может скопировать в буфер размером 8 байт дескриптор, соответствующий заданному селектору.

Регистры на входе
AX      000Bh
BX      Селектор сегмента, для которого требуется получить дескриптор.
ES:(E)DI        Указатель на буфер размером 8 байт, в который будет скопирован дескриптор. Для 32-разрядных программ необходимо использовать регистр EDI.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Установить дескриптор

Функция выполняет действия, обратные предыдущей - копирует содержимое 8-байтового буфера в дескриптор, заданный селектором.

Регистры на входе
AX      000Ch
BX      Селектор сегмента, для которого требуется установить дескриптор.
ES:(E)DI        Указатель на буфер размером 8 байт, из которого будет взята информация для установки дескриптора. Для 32-разрядных программ необходимо использовать регистр EDI.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Программа может модифицировать только дескрипторы из своей локальной таблицы LDT, созданные там с помощью функции 0000h.

Получить конкретный дескриптор в таблице LDT

Функция позволяет получить дескриптор по его селектору.

Регистры на входе
AX      000Dh
BX      Селектор сегмента, для которого требуется получить дескриптор.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Для этой функции сервер DPMI резервирует первые 16 дескрипторов в таблице LDT. Программа может освободить полученные с помощью этой функции дескрипторв при помощи функции 0001h.

Получить блок памяти из пула свободной памяти DOS

С помощью этой функции программа может получить от DOS блок памяти, лежащий в пределах первого мегабайта адресного пространства. Для полученного блока памяти функция создаёт один или несколько дескрипторов (в зависимости от размера блока) и возвращает как сегмент блока для реального режима, так и базовый селектор для доступа к блоку в защищённом режиме.

Если размер запрошенного блока превышает 64 килобайта, функция создаст массив дескрипторов и возвратит базовый селектор. Для получения остальных селекторов вы сможете использовать значение инкремента, полученное после вызова функции 0003h.

Дескрипторы, созданные этой функцией, можно освобождать только с помощью функции 0101h.

Регистры на входе
AX      0100h
BX      Размер блока в параграфах (напомним, что размер параграфа составляет 16 байт).

Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
В случае ошибки регистр AX содержит код ошибки, полученный от DOS:
        07h - разрушен блок MCB;
        08h - слишком большой размер заказанного блока.
В последнем случае регистр BX содержит максимально возможный размер блока в параграфах.
AX      Сегмент реального режима полученного блока.
DX      Селектор для доступа к полученному блоку в защищённом режиме.




Освободить блок памяти, взятый из пула DOS

Функция предназначена для освобождения памяти, полученной при помощи предыдущей функции.

Регистры на входе
AX      0101h
DX      Селектор освобождаемого блока.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
В случае ошибки регистр AX содержит код ошибки, полученный от DOS:
        07h - разрушен блок MCB;
        09h - неправильное задание сегментного адреса освобождаемого блока памяти.




Изменить размер блока памяти, полученного из пула DOS

С помощью этой функции программа может увеличить или уменьшить размер блока памяти, полученного функцией 0100h.

Регистры на входе
AX      0102h
BX      Новый размер блока памяти в параграфах.
DX      Селектор модифицируемого блока.

Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
В случае ошибки регистр AX содержит код ошибки, полученный от DOS:
        07h - разрушен блок MCB;
        08h - слишком большой размер заказанного блока.
В этом случае регистр BX содержит максимально возможный размер блока в параграфах.
        09h - неправильное задание сегментного адреса модифицируемого блока памяти.




Получить вектор прерывания реального режима

Функция возвращает для заданного вектора прерывания адрес его обработчика в формате реального ржима.

Регистры на входе
AX      0200h
BL      Номер прерывания.
Регистры на выходе:
CARRY   0
CX:DX   Адрес обработчика прерывания в формате <сегмент:смещение>.




Установить вектор прерывания реального режима

Функция позволяет задать для любого вектора прерывания адрес обработчика в формате реального режима.

Регистры на входе
AX      0201h
BL      Номер прерывания.
CX:DX   Адрес обработчика прерывания в формате <сегмент:смещение>.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Получить адрес обработчика исключения с заданным номером

Функция возвращает адрес текущего обработчика исключения с заданным номером в формате защищённого режима.

Регистры на входе
AX      0202h
BL      Номер исключения (00h - 1Fh).
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
CX:(E)DX        Адрес обработчика исключения в формате <селектор:смещение>.




Установить адрес обработчика исключения с заданным номером

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

Регистры на входе
AX      0203h
BL      Номер исключения (00h - 1Fh).
CX:(E)DX        Адрес обработчика исключения в формате <селектор:смещение>.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Получить вектор прерывания защищённого режима

Функция возвращает адрес обработчика заданного прерывания в формате защищённого режима.

Регистры на входе
AX      0204h
BL      Номер прерывания.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
CX:(E)DX        Адрес обработчика прерывания в формате <селектор:смещение>.




Установить вектор прерывания защищённого режима

С помощью этой функции программа может установить собственный обработчик защищённого режима для любого прерывания.

Регистры на входе
AX      0205h
BL      Номер прерывания.
CX:(E)DX        Адрес обработчика прерывания в формате <селектор:смещение>.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Эмуляция прерывания реального режима

Эта функция предназначена для эмуляции выполнения прерывания в реальном режиме. С её помощью вы сможете находясь в защищённом режиме обращаться к функциям BIOS, DOS и к другим прерываниям реального режима.

Регистры на входе
AX      0300h
BL      Номер прерывания.
BH      Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль.
CX      Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима.
ES:(E)DI        Адрес управляющей структуры для вызова прерывания.

Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
ES:(E)DI        Адрес модифицированной (в результате выполнения обработчика прерывания) управляющей структуры в формате защищённого режима.




Управляющая структура содержит значения для инициализации регистров перед вызовом эмулируемого прерывания реального режима. Она имеет следующий формат:

Таблица 11. Формат управляющей структуры для эмуляции прерывания реального режима средствами DPMI.

Смещение Регистр
00h EDI
04h ESI
08h EBP
0Ch Зарезервировано
10h EBX
14h EDX
18h ECX
1Ch EAX
20h FLAGS
22h ES
24h DS
26h FS
28h GS
2Ah IP
2Ch CS
2Eh SP
30h SS

Ваша программа, находясь в защищённом режиме, может не только вызвать прерывание реального режима, но и передать параметры через стек, указав в управляющей структуре их количество. Наример:

     push    Parametr1
     push    Parametr2
     push    Parametr3

     mov     cx, 3           ; Копируем три параметра
     mov     ax, 0301h
     int     31h
     add     sp, 6           ; Восстанавливаем стек




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

Parametr1
Parametr2
Parametr3
CS для возврата
IP для возврата




Указатель стека реального режима SS:SP установлен на слово, содержащее IP для возврата.

Вызов процедуры реального режима, заканчивающейся командой RET FAR

Эта процедура позволяет выполнить вызов процедуры, предназначенной для работы в реальном режиме и возвращающей управление при помощи команды RET FAR.

Регистры на входе
AX      0301h
BH      Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль.
CX      Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима.
ES:(E)DI        Адрес управляющей структуры для вызова процедуры реального режима.

Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
ES:(E)DI        Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима.




Вызов процедуры реального режима, заканчивающейся командой IRET

Эта процедура позволяет выполнить вызов процедуры, предназначенной для работы в реальном режиме и возвращающей управление при помощи команды IRET.

Регистры на входе
AX      0302h
BH      Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль.
CX      Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима.
ES:(E)DI        Адрес управляющей структуры для вызова процедуры реального режима.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
ES:(E)DI        Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима.




Перехват передачи управления программе реального режима

Эта функция может быть использована для перехвата передачи управления процедуре реального режима.

Например, драйвер мыши выполняет вызов процедуры реального режима при перемещении мыши. Программа может сама задавать адрес реальной процедуры, которая будет получать управление при перемещении мыши или при нажатии на её кнопки. С помощью данной функции программы защищённого режима могут перехватить передачу управления этой процедуры и заменить её на другую процедуру, работающую в защищённом режиме.

Регистры на входе
AX      0303h
DS:(E)SI        Адрес процедуры в формате защищённого режима, которая будет вызвана вместо перехваченной.
ES:(E)DI        Адрес управляющей структуры для вызова процедуры реального режима (в формате защищённого режима).
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
ES:(E)DI        Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима.
CX:DX   Адрес вызова перехватываемой программы в формате реального режима.




Процедура защищённого режима, перехватывающая управление, вызывается с запрещёнными прерываниями и получает следующие параметры:

DS:(E)SI Адрес стека реального режима в формате <селектор:смещение>.
ES:(E)DI Адрес управляющей структуры в формате <селектор:смещение>.
SS:(E)SP Стек защищённого режима.

Остальные регистры остаются в неопределённом состоянии.

Для выполнения возврата из процедуры перехвата необходимо выполнить команду IRET, предварительно загрузив регистры следующим образом:

ES:(E)DI        Адрес управляющей структуры  в формате <селектор:смещение>.




Отмена перехвата передачи управления процедуре реального режима.

Эта функция отменяет действие предыдущей.

Регистры на входе
AX      0304h
CX:DX   Адрес вызова перехватываемой программы в формате реального режима, для которой отменяется перехват.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Получить адреса процедур сохранения/восстановления состояния.

С помощью этой функции программа может сохранять или восстанавливать состояние задачи (в том числе содержимое всех регистров процессора). это бывает необходимо перед изменением реального режима работы на защищённый и обратно.

Регистры на входе
AX      0305h
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
AX      Размер буфера для сохранения состояния.
BX:CX   Адрес программы сохранения/восстановления для реального режима в формате <сегмент:смещение>.
ES:(E)DI        Адрес программы сохранения/восстановления для защищённого режима в формате <селектор:смещение>.




Процедура сохранения/восстановления состояния вызывается командой CALL FAR и получает следующие параметры:

ES:(E)DI        Указатель на буфер сохранения состояния.
AL      0 - для сохранения состояния,
1 - для восстановления состояния.




Получить адрес процедуры переключения режима

С помощью этой функции программа может получить адреса процедур для переключения из реального режима в защищённый и из защищённого в реальный.

Регистры на входе
AX      0306h
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
BX:CX   Адрес программы переключения из реального режима в защищённый в формате <сегмент:смещение>.
SI:(E)DI        Адрес программы для переключения из защищённого режима в реальный в формате <селектор:смещение>.




Перед переключением режима, которое выполняется командой JMP FAR, необходимо подготовить регистры:

AX Новое содержимое регистра DS.
CX Новое содержимое регистра ES
DX Новое содержимое регистра SS
(E)BX Новое содержимое регистра (E)SP
SI Новое содержимое регистра CS
(E)DI Новое содержимое регистра (E)IP

В процессе переключения режима содержимое регистра (E)BP останется неизменным, поэтому этот регистр можно использовать как указатель.

Для процессоров i80386 и i80486 после переключения режима в регситры FS и GS бует записано нулевое значение.

Получить версию спецификации DPMI

Функция позволяет получить текущую версию DPMI.

Регистры на входе
AX      0400h

Регистры на выходе:
CARRY   0
AH      Верхний (major) номер версии.
AL      Нижний (minor) номер версии.
BX      Байт флагов:
        бит 0 = 1 если программа работает под управлением DPMI для процессора i80386;
        бит 1 = 1 если процессор вернулся в реальный режим для обработки прерывания;
        бит 2 = 1 если в системе поддерживается виртуальная память;
        бит 3  и все остальные зарезервированы для использования в будущем.
CL      Тип процессора:
     02 = i80286
     03 = i80386
     04 = i80486
DH      Текущее значение номера прерывания для IRQ0.
DL      Текущее значение номера прерывания для IRQ8.




Получить информацию о свободной памяти

Регистры на входе
AX      0500h
ES:(E)DI        Адрес бфера размером 30h байт в формате <селектор:смещение>.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
ES:(E)DI        Адрес бфера размером 30h байт в формате <селектор:смещение>, заполненного информацией о свободной памяти.




Формат буфера:

Таблица 12. Формат буфера для информации о свободной памяти, получаемой средствами DPMI.

Смещение Описание
00h Размер наибольшего доступного свободного блока в байтах
04h Максимальное количество доступных незаблокированных страниц памяти.
08h Максимальное количество доступных заблокированных страниц памяти.
0Ch Размер линейного адресного пространства в страницах.
10h Общее количество незаблокированных страниц.
14h Количество свободных страниц.
18h Общее количество физических страниц.
1Ch Размер свободного линейного адресного пространства в страницах.
20h Размер страничного файла или раздела в страницах.
24h-2Fh Зарезервировано.

Если текущая реализация DPMI не поддерживает виртуальную память, данная функция заполняет только первое поле структуры. Остальные поля устанавливаются в -1 (0FFFFFFFFh).

Получить блок памяти

Эта функция предназначена для получения программой участка линейной памяти. Для полученного блока памяти не выполняется автоматического создания селектора, программа должна создать селектор самостоятельно.

Регистры на входе
AX      0501h
BX:CX   Размер блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
BX:CX   Линейный адрес полученного блока.
SI:DI   Индекс полученного блока памяти, нужен для выполнения операций с блоком памяти (изменение его размера или освобождение).




Освободить блок памяти

Функция освобождает блок памяти, полученный при помощи предыдущей функции.

Регистры на входе
AX      0502h
SI:DI   Индекс освобождаемого блока памяти.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Изменить размер блока памяти

Функция изменяет размер блока памяти, полученного при помощи функции 0501h.

Регистры на входе
AX      0503h
BX:CX   Новый размер блока памяти в байтах.
SI:DI   Индекс изменяемого блока памяти.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
BX:CX   Новый линейный адрес полученного блока.
SI:DI   Новый индекс блока памяти.




Зафиксировать линейную область памяти

Эта функция фиксирует в памяти область, задаваемую линейным адресом. Для зафиксированной области памяти не выполняется свопинг страниц.

Регистры на входе
AX      0600h
BX:CX   Начальный линейный адрес фиксируемого участка памяти.
SI:DI   Размер фиксируемого блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Расфиксирование блока памяти

Эта функция должна быть использована для расфиксирования блока памяти, зафиксированного предыдущей функцией.

Регистры на входе
AX      0601h
BX:CX   Начальный линейный адрес расфиксируемого участка памяти.
SI:DI   Размер расфиксируемого блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Отмена фиксации страниц для области памяти виртуального режима

Область памяти, используемая виртуальной машиной, обычно зафиксирована и не подвергается свопингу. С помощью этой функции вы можете разрешить свопинг для заданного участка памяти, принадлежащей виртуальной машине.

Регистры на входе
AX      0602h
BX:CX   Начальный линейный адрес расфиксируемого участка памяти.
SI:DI   Размер расфиксируемого блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Фиксация страниц для области памяти виртуального режима

Функция предназначена для отмены действия предыдущей функции. Она фиксирует участок памяти виртуальной машины, отменяя для него свопинг страниц.

Регистры на входе
AX      0603h
BX:CX   Начальный линейный адрес фиксируемого участка памяти.
SI:DI   Размер фиксируемого блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Получить размер страницы памяти

Функция возвращает размер страницы памяти в байтах.

Регистры на входе
AX      0604h
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
BX:CX   Размер страницы памяти в байтах.




Зарезервированные функции

Функции 0700h и 0701h зарезервированы и не должны вызываться вашей программой.

Отметка страницы для свопинга

Функция используется для того, чтобы сообщить операционной системе о возможности выгрузки (свопинга) на диск указанной страницы или диапазона страниц.

Регистры на входе
AX      0702h
BX:CX   Начальный линейный адрес отмечаемых страниц.
SI:DI   Размер отмечаемого блока памяти.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Отвергнуть содержимое страниц

Функция отмечает страницы как не содержащие полезной информации. Операционная система может использовать данные страницы для удовлетворения запросов на память.

Регистры на входе
AX      0703h
BX:CX   Начальный линейный адрес страниц, отмечаемых как не содержащие полезной информации.
SI:DI   Размер отмечаемого блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Преобразование физического адреса в линейный

Функция может быть использована для работы с периферийными устройствами, адресное пространство ввода/вывода которых отображается в диапазон физических адресов.

Регистры на входе
AX      0800h
BX:CX   Физический адрес памяти.
SI:DI   Размер блока памяти в байтах.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
BX:CX   Линейный адрес памяти.




Запретить виртуальные прерывания

Функция сбрасывает флаг виртуального прерывания и возвращает предыдущее состояние этого флага.

Регистры на входе
AX      0900h
Регистры на выходе:
CARRY   0
AL      0, если виртуальные прерывания были запрещены,
1, если виртуальные прерывания были разрешены.




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

Функция устанавливает флаг виртуальных прерываний, разрешая виртуальные прерывания, и возвращает предыдущее сосотояние этого флага.

Регистры на входе
AX      0901h
Регистры на выходе:
CARRY   0
AL      0, если виртуальные прерывания были запрещены,
1, если виртуальные прерывания были разрешены.




Получить состояние флага виртуальных прерываний

Функция позволяет узнать текущее состояние флага виртуальных прерываний.

Регистры на входе
AX      0902h
Регистры на выходе:
CARRY   0
AL      0, если виртуальные прерывания запрещены,
1, если виртуальные прерывания разрешены.




Получить адрес для использования расширений DPMI

DOS-экстендеры могут расширять сервис DPMI своими функциями. Для получения доступа к этим функциям можно использовать функцию 0A00h. Вызывающая программа должна задать в регистрах DS:(E)SI адрес строки, закрытой нулём. Строка должна содержать название производителя DOS-экстендера или другой уникальный идентификатор расширения.

Регистры на входе
AX      0A00h
DS:(E)SI        Указатель на строку, закрытую нулём.
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
ES:(E)DI        Точка входа для вызова расширений




Содержимое регистров DS, FS, GS, EAX, EBX, ECX, EDX, ESI, и EBP не сохраняется.

Установить точку останова для отладки

Функция позволяет установить отладочную точку останова по заданному линейному адресу.

Регистры на входе
AX      0B00h
BX:CX   Линейный адрес точки останова.
DL      Размер используемого для точки останова операнда (1, 2, или 4 байта).
DH      Тип точки останова
     0 = Выполнение
     1 = Запись
     2 = Чтение/запись
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
BX      Индекс для доступа к отладочной точке останова




Отмена отладочной точки останова

Функция отменяет точку останова, установленную при помощи предыдущей функции.

Регистры на входе
AX      0B01h
BX      Индекс отладочной точки останова
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




Получить состояние отладочной точки останова

Функция возвращает состояние отладочной точки останова, определённой при помощи функции 0B00h.

Регистры на входе
AX      0B02h
BX      Индекс отладочной точки останова
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.
AX      Флаги:
   Бит 0 = 1 если произошло выполнение точки останова.




Сброс точки останова

Функция сбрасывает состояние для заданной отладочной точки останова.

Регистры на входе
AX      0B03h
BX      Индекс отладочной точки останова
Регистры на выходе:
CARRY   0, если функция выполнилась без ошибки,
1, если произошла ошибка.




6.5. DOS-экстендеры

Вы уже наверное заметили, что все программы, приведённые в этой книге, не поддаются отладке с помощью обычных отладчиков, таких как Borland Turbo Debugger или Microsoft Code View. Эти отладчики зависают уже на этапе загрузки регистра дескрипторной таблицы прерываний IDTR, до перехода в защищённый режим дело так и не доходит. И это понятно - такие отладчики рассчитаны на обычные программы реального режима.

Другая трудность связана с тем, что программе, работающей в защищённом режиме, недоступны прерывания MS-DOS и BIOS. Программа должна работать с аппаратурой на уровне регистров и аппаратных прерываний.

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

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

Кроме того, DOS-экстендер предоставляет программе возможность обращения к прерываниям MS-DOS и BIOS! В своих программах, работающих в защищённом режиме под управлением DOS-экстендера вы можете пользоваться привычными вам функциями MS-DOS и BIOS (правда, не всеми, а только документированными). Возможности этих функций возрастут. Например, с помощью функции DOS можно будет заказать для программы буфер размером в несколько мегабайт.

Модуль DOS-экстендера, прикомпонованный к программе, может использовать интерфейсы DPMI, VCPI, XMS (драйвер HIMEM.SYS), INT 15h, или может использовать собственную схему управления памятью в защищённом режиме и собственные средства переключения режима процессора или состояния адресной линии A20. В спецификации DPMI приведены рекомендации для разработчиков DOS-экстендеров по использованию перечисленных выше интерфейсов. DOS-экстендер должен проверять наличие интерфейсов и по возможности использовать более высокоуровневый интерфейс. Проверка должна выполняться в следующем порядке:

К сожалению, не все поставляющиеся DOS-экстендеры следуют этим рекомендациям. В результате могут возникнуть проблемы при попытке запустить разработанную с помощью DOS-экстендера программу на виртуальной машине WINDOWS в режиме "Enhanced 386 Mode" или в MS-DOS при установленных драйверах EMM386 или QEMM.

Необходимость обеспечения совместимости с интерфейсами DPMI, VCPI и XMS требует тщательного выбора DOS-экстендера. Так как в настоящее время операционная система WINDOWS находится в состоянии взрывообразного распространения среди пользователей персональных компьютеров, неудачный выбор DOS-экстендера может привести к тому, что огромное количество потенциальных покупателей не смогут использовать вашу программу в среде WINDOWS. То же относится и к значительному количеству пользователей дрйверов расширенной памяти EMM386 и QEMM. В документации на DOS-экстендер должно содержаться подтверждение совместимости с интерфейсами DPMI, VCPI и XMS. Такой DOS-экстендер будет совместим с современными операционными системами и драйверами расширенной памяти.

Примерами программ, созданных с использованием несовместимых с DPMI интерфейсом служат СУБД ORACLE версии 5.1 и FOXPRO версии 2.0. Эти программные продукты не будут работать на виртуальной машине WINDOWS в режиме "Enhanced 386 Mode".

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

DOS-экстендеры поставляются в комплекте с трансляторами, редакторами связей, отладчиками и библиотеками стандартных функций (например, библиотеками для транслятора языка Си). Поэтому создание и отладка программ защищённого режима, созданных с использованием DOS-экстендеров, обычно не вызывает затруднений.

Мы кратко рассмотрим возможности двух DOS-экстендеров: 386-DOS/Extender фирмы Phar Lap и виртуальную машину операционной системы WINDOWS в режиме "Enhanced 386 Mode".

Phar Lap DOS-экстендер

В состав Phar Lap DOS-экстендера входят транслятор для языка Си hc386.exe, ассемблер 386asm.exe, редактор связей 386link.exe, отладчик minibug.exe и программа загрузки run386.exe.

С помощью транслятора языка Си или ассемблера получаются объектные модули, которые компонуются редактором связей 386link.exe в загрузочный модуль. Этот загрузочный модуль имеет расширение "exp" и запускается при помощи программы загрузки run386.exe. Полученный загрузочный модуль может работать только на процессорах i80386 или i80486. Версия 2.2 Phar Lap DOS-экстендера не поддерживает интерфейс DPMI, поэтому разработанные с использованием экстендера этой версии программы не будут работать на виртуальной машине WINDOWS в режиме "Enhanced 386 Mode".

Phar Lap DOS-экстендер предоставляет программе, которая получает управление сразу в защищённом режиме, возможность использовать документированные прерывания MS-DOS и BIOS. Кроме того, в рамках прерывания INT 21h DOS-экстендером реализуются дополнительные функции, связанные с работой в защищённом режиме.

Для того, чтобы у вас было представление о возможностях Phar Lap DOS-экстендера, приведём таблицу дополнительных функций, реализованных в рамках прерывания INT 21h:

Таблица 13. Функции Phar Lap DOS-экстендера.

Регистр AX Выполняемая функция
2501h Установка в исходное состояние структур данных DOS-экстендера.
2502h Получить вектор прерывания защищённого режима.
2503h Получить вектор прерывания реального режима.
2504h Установить вектор прерывания защищённого режима.
2505h Установить вектор прерывания реального режима.
2506h Установить режим, при котором прерывание будет всегда обрабатываться в защищённом режиме.
2507h Установить вектора прерываний реального и защищённого режима.
2508h Установить линейный базовый адрес сегмента.
2509h Преобразовать линейный адрес в физический
250Ah Отобразить физическую память в конце сегмента.
250Ch Получить вектора аппаратных прерываний.
250Dh Получить информацию связи с реальным режимом.
250Eh Вызвать процедуру реального режима.
250Fh Преобразовать адрес защищённого режима в адрес реального режима.
2510h Вызвать процедуру реального режима с заданным содержимым регистров.
2511h Вызвать прерывание реального режима с заданным содержимым регистров.
2512h Загрузить программу для отладки.
2513h Создать алиасный дескриптор сегмента (т.е. для заданного дескриптора создаётся ещё один, указывающий на тот же сегмент).
2514h Изменить атрибуты сегмента.
2515h Получить атрибуты сегмента.
2516h Освободить всю память, распределённую при помощи LDT.
2517h Получить информацию о буферах данных DOS.
2518h Определить драйвер для обработки перемещения сегмента.
2519h Получить дополнительную информацию об ошибке памяти.
251Ah Зафиксировать страницы в памяти.
251Bh Расфиксировать страницы.
251Ch Освободить страницы физической памяти.
251Dh Прочитать элемент таблицы страниц.
251Eh Записать элемент таблицы страниц.
251Fh Обменять элементы таблицы страниц.
2520h Получить статистическую информацию о памяти.
2521h Максимальный размер доступной программам расширенной памяти.
2522h Определить альтернативный драйвер, обрабатывающей ситуацию отсутствия страницы в памяти.
2525h Максимальный размер доступной программам стандартной памяти.
25C0h Получить блок стандартной памяти MS-DOS.
25C1h Освободить блок стандартной памяти MS-DOS.
25C2h Изменить размер блока стандартной памяти MS-DOS.
25C3h Выполнить программу.

Сравните это с функциями интерфейса DPMI, вы увидите что между этими интерфейсами есть много общего. Есть специальная функция, предназначенная для отладчиков - "Загрузить программу для отладки".

Программе доступны селекторы, облегчающие работу с наиболее часто используемыми областями данных. Например, таблица LDT содержит следующие селекторы, готовые для использования:

Таблица 14. Таблица LDT Phar Lap DOS-экстендера.

0008h Сегмент кода программы.
0010h Сегмент данных программы.
0018h Сегмент видеопамяти для работы в текстовом режиме.
0020h PSP программы.
0028h Сегмент строк среды DOS (DOS environment).
0030h Сегмент данных для доступа к первому мегабайту памяти, доступен для записи.
0038h Сегмент для работы с сопроцессором Weitek 1167. В отличие от сопроцессора i80287/i80387 для обращения к сопроцессору Weitek 1167 используется определённая область адресов памяти.
0040h Сегмент видеопамяти для работы в графическом режиме.

В документации на Phar Lap DOS-экстендер подробно описаны форматы таблиц LDT и GDT. Программы могут пользоваться определёнными в этих таблицах селекторами для адресации системных областей памяти, таких как память видеоадаптера.

В качестве простейшего примера использования Phar Lap DOS-экстендера приведём следующую программу:

Листинг 20. Использование Phar Lap DOS-экстендера
Файл pharlap.asm
-----------------------------------------------------------


; ---------------------------------------------------
; Сегмент данных
; ---------------------------------------------------

_data   segment para public use32 'data'

hello   db      'PHAR LAP 386/DOS EXTENDER', 0dh,0ah
                db      'Вызов DOS в защищенном режиме', 0dh,0ah
                db      0dh,0ah,'© Frolov A.V., 1992',0dh, 0ah
                db      0dh,0ah,'Для возврата в DOS нажмите любую клавишу','$'

_data   ends

; ---------------------------------------------------
; Сегмент стека
; ---------------------------------------------------

_stack  segment byte stack use32 'stack'

        db      8192 dup (?)

_stack  ends

; ---------------------------------------------------
; Сегмент кода
; ---------------------------------------------------

        assume  cs:_text,ds:_data

_text   segment para public use32 'code'

        public  _start_                 
_start_ proc    near                    

; Выводим строку

        lea     edx,hello
        mov     ah,09h
        int     21h

        mov     ah,8h                   
        int     21h                     

        mov     ax,04c00h
        int     21h     

_start_ endp

_text   ends

        end _start_

Эта программа просто выводит сообщение на экран и завершает свою работу после того, как вы нажмёте любю клавишу. Особенность программы заключается в том, что она получает управление сразу в защищённом режиме. Запуск программы должен выполняться специальным загрузчиком, который входит в состав Phar Lap DOS-экстендера. Этот загрузчик находится в файле run386.exe.

Для трансляции программы и её запуска можно использовать следующий командный файл:

386asm pharlap
386link pharlap
run386 pharlap 

Обратите внимание на то, что в приведённой выше программе не выполняется загрузка сегментного регистра DS. Так как программа стартует сразу в защищённом режиме, загрузчик run386 загружает сам все сегментные регистры. В частности, он загружает в регистр DS селектор сегмента данных.

Виртуальная машина WINDOWS

Операционная система WINDOWS позволяет разделять ресурсы персонального компьютера между несколькими параллельно работающими программами. При этом в среде WINDOWS могут выполняться три типа программ:

Программы первого типа - тема для отдельной книги объёмом в несколько сотен страниц (см. список литературы). Программы второго типа - это обычные программы MS-DOS, о которых мы говорили в предыдущих томах "Библиотеки системного программиста".

Мы займёмся программами последнего типа. Для составления этих программ вы можете использовать обычную технику, применяемую для программ реального режима MS-DOS (за исключением отладки - как и все программы, приведённые в этой книге, программы третьего типа не поддаются отладке стандартными для реального режима средствами).

Наша следующая глава - о WINDOWS и о тех возможностях, которые операционная система WINDOWS предоставляет программам, составленным в старом "стиле" MS-DOS.