Вы уже, наверное, обратили внимание на то, что программирование для защищённого режима значительно сложнее, чем для реального, и требует учёта большого количества важных деталей. Даже для того, чтобы просто перевести процессор 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.
Этот интерфейс реализуется в рамках прерывания 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 рассчитаны на работу в реальном режиме и после перехода в защищённый режим становятся недоступны.
Наш пример демонстрирует использование функции 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
Как мы уже говорили, назначение драйвера 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 были подробно описаны во втором томе "Библиотеки системного программиста".
Во второй части второго тома "Библиотеки системного программиста" (глава 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 более подробно.
Регистры на входе: AX 0DE00h Регистры на выходе: AH равен 00h - если VCPI установлен, не равен 00h - если VCPI не установлен. BH Верхний (major) номер версии VCPI. BL Нижний (minor) номер версии 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 Физческий адрес страницы.
Регистры на входе: 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, игнорируются.
Регистры на входе: AX 0DE0Ah Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка. BX Вектор прерывания, используемый для IRQ0. CX Вектор прерывания, используемый для IRQ8.
Регистры на входе: 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, ®, ®); *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, ®, ®); *ver_hi = reg.h.bh; *ver_lo = reg.h.bl; return(reg.h.ah); }
Для создания программ, работающих в защищённом режиме, фирмы 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, принадлежащей задаче, в рамках которой работает программа, т.е. для своей задачи.
Вся работа по инициализации полученных дескрипторов должна выполняться самой программой, для чего в интерфейсе DPMI имеются соответствующие функции.
Регистры на входе: AX 0000h CX Количество создаваемых дескрипторов. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. AX Базовый селектор.
Если программа заказывала несколько селекторов, AX содержит первый селектор из созданного массива. Для получения остальных селекторов необходимо воспользоваться функцией 00003h.
Функция используется для освобождения дескрипторов, созданных в 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.
Функция позволяет получить дескриптор по его селектору.
Регистры на входе AX 000Dh BX Селектор сегмента, для которого требуется получить дескриптор. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.
Для этой функции сервер DPMI резервирует первые 16 дескрипторов в таблице LDT. Программа может освободить полученные с помощью этой функции дескрипторв при помощи функции 0001h.
С помощью этой функции программа может получить от DOS блок памяти, лежащий в пределах первого мегабайта адресного пространства. Для полученного блока памяти функция создаёт один или несколько дескрипторов (в зависимости от размера блока) и возвращает как сегмент блока для реального режима, так и базовый селектор для доступа к блоку в защищённом режиме.
Если размер запрошенного блока превышает 64 килобайта, функция создаст массив дескрипторов и возвратит базовый селектор. Для получения остальных селекторов вы сможете использовать значение инкремента, полученное после вызова функции 0003h.
Дескрипторы, созданные этой функцией, можно освобождать только с помощью функции 0101h.
Регистры на входе AX 0100h BX Размер блока в параграфах (напомним, что размер параграфа составляет 16 байт). Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. В случае ошибки регистр AX содержит код ошибки, полученный от DOS: 07h - разрушен блок MCB; 08h - слишком большой размер заказанного блока. В последнем случае регистр BX содержит максимально возможный размер блока в параграфах. AX Сегмент реального режима полученного блока. DX Селектор для доступа к полученному блоку в защищённом режиме.
Функция предназначена для освобождения памяти, полученной при помощи предыдущей функции.
Регистры на входе AX 0101h DX Селектор освобождаемого блока. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. В случае ошибки регистр AX содержит код ошибки, полученный от DOS: 07h - разрушен блок MCB; 09h - неправильное задание сегментного адреса освобождаемого блока памяти.
С помощью этой функции программа может увеличить или уменьшить размер блока памяти, полученного функцией 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.
Регистры на входе AX 0301h BH Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль. CX Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима. ES:(E)DI Адрес управляющей структуры для вызова процедуры реального режима. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима.
Эта процедура позволяет выполнить вызов процедуры, предназначенной для работы в реальном режиме и возвращающей управление при помощи команды 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.
Регистры на входе 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, если виртуальные прерывания разрешены.
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, если произошла ошибка.
Вы уже наверное заметили, что все программы, приведённые в этой книге, не поддаются отладке с помощью обычных отладчиков, таких как 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-экстендера входят транслятор для языка Си 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 могут выполняться три типа программ:
Программы первого типа - тема для отдельной книги объёмом в несколько сотен страниц (см. список литературы). Программы второго типа - это обычные программы MS-DOS, о которых мы говорили в предыдущих томах "Библиотеки системного программиста".
Мы займёмся программами последнего типа. Для составления этих программ вы можете использовать обычную технику, применяемую для программ реального режима MS-DOS (за исключением отладки - как и все программы, приведённые в этой книге, программы третьего типа не поддаются отладке стандартными для реального режима средствами).
Наша следующая глава - о WINDOWS и о тех
возможностях, которые операционная система WINDOWS
предоставляет программам, составленным в старом
"стиле" MS-DOS.