Использование функций DPMI
Приведённая ниже программа демонстрирует использование функций интерфейса DPMI, описанного в предыдущей главе. Эта программа может работать только под управлением WINDOWS версий 3.0, 3.1 и только в расширенном режиме на процессорах i80386 или i80486. Такое ограничение связано с тем, что только в расширенном режиме существует понятие виртуальной DOS-машины, и только в этом режиме DOS-программа может воспользоваться сервисом DPMI.
Вы можете также попробовать запустить эту программу под управлением DOS-экстендера, входящего в состав интегрированной системы разработки программ Borland C++ 3.1. Запустите программу DPMIRES.EXE, входящую в состав Borland C++ 3.1, и затем - программу, приведённую ниже. (DOS-экстендеры, входящие в состав Borland C++ 2.0 или 3.0, не вполне совместимы с DPMI, поэтому наш пример с этими системами работать не будет).
Программа начинает свою работу с проверки доступности сервера DPMI, при этом не делается никаких предположений относительно средств, обеспечивающих присутствие DPMI. Это означает, что вы можете проверить работоспособность этой программы в различных средах, предоставляющих интерфейс DPMI, например на виртуальной DOS-машине операционной системы OS/2 версии 2.0.
Программа демонстрирует возможность вызова в защищённом режиме прерываний реального режима. В первой части программы вывод на экран и ввод с клавиатуры выполняется в защищённом режиме, но с использованием привычных вам прерываний реального режима.
Во второй части программы демонстрируется непосредственная запись в видеопамять. При этом для адресации видеопамяти программа заказывает селектор в таблице LDT с помощью специально предназначенных для этого функций DPMI.
Листинг 21. Использование интерфейса DPMI Файл dpmi.c -----------------------------------------------------------
#include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <stdarg.h>
typedef struct { unsigned long edi, esi, ebp, reserved, ebx, edx, ecx, eax; unsigned flags, es, ds, fs, gs, ip, cs, sp, ss; } RM_INT_CALL;
#define MONO_MODE 0x07 #define BW_80_MODE 0x02 #define COLOR_80_MODE 0x03
// Макро для вычисления линейного адреса исходя из // логического адреса реального режима
#define ABSADDR(seg, ofs) \ ((((unsigned long) seg) << 4) + ((ofs) & 0xFFFF))
typedef struct { // байт доступа unsigned accessed : 1; unsigned read_write : 1; unsigned conf_exp : 1; unsigned code : 1; unsigned xsystem : 1; unsigned dpl : 2; unsigned present : 1; } ACCESS;
typedef struct { // дескриптор unsigned limit; unsigned addr_lo; unsigned char addr_hi; ACCESS access; unsigned reserved; } DESCRIPTOR;
// Структура для записи информации о памяти
typedef struct { unsigned long avail_block; unsigned long max_page; unsigned long max_locked; unsigned long linadr_space; unsigned long total_unlocked; unsigned long free_pages; unsigned long tot_phys_pages; unsigned long free_linspace; unsigned long size_fp; char reserved[12]; } PMI;
void dos_exit(unsigned); void dpmi_init(void); void set_pmode(void); void cdecl pm_printf(const char *, ...); void pm_puts(char *); void pm_putch(int); int rm_int(unsigned, unsigned , RM_INT_CALL far *); int mi_show(void); unsigned get_sel(void); int set_descriptor(unsigned pm_sel, DESCRIPTOR far *desc); void vi_print(unsigned int x, unsigned int y, char *s, char attr); void vi_hello_msg(void);
void main() {
clrscr(); printf("DOS Protected Mode Interface Demo, © Frolov A.V., 1992\n\r" "--------------------------------------------------------\n\r\n\r");
// Определяем текущий видеорежим и // сегментный адрес видеобуфера
video_init();
// Инициализируем защищённый режим
dpmi_init();
printf("\n\r\n\r\n\rДля входа в защищённый режим нажмите любую клавишу..."); getch();
// Входим в защищённый режим
set_pmode();
// Стираем экран и выводим сообщение, находясь в // защищённом режиме.
Пользуемся выводом через // эмулируемое прерывание реального режима DOS
textcolor(BLACK); textbackground(LIGHTGRAY); clrscr(); pm_printf(" Установлен защищённый режим работы процессора!\n\r" " ----------------------------------------------\n\r\n\r");
// Выводим текущую информацию о распределении памяти
mi_show();
pm_printf("\n\r\n\r\n\ r Для продолжения нажмите любую клавишу..."); getch();
clrscr();
// Получаем селектор для непосредственного доступа к видеопамяти
alloc_videosel();
pm_printf("\n\r\n\r\n\r Для продолжения нажмите любую клавишу..."); getch(); clrscr();
// Выводим сообщения, пользуясь непосредственным доступом // к видеопамяти
vi_hello_msg(); vi_print(0, 3, " Для возврата в реальный режим нажмите любую клавишу", 0x7f); getch();
// Освобождаем полученный селектор
free_videosel();
textcolor(LIGHTGRAY); textbackground(BLACK); clrscr();
// Завершаем работу программы выходом в DOS
dos_exit(0); }
// ------------------------------------------------- // Процедура для завершения работы программы // -------------------------------------------------
void dos_exit(unsigned err) { asm mov ax, err asm mov ah, 04ch asm int 21h }
// ------------------------------------------------- // Инициализация для работы с DPMI // -------------------------------------------------
union REGS inregs, outregs; struct SREGS segregs; void (far *pm_entry)(); unsigned hostdata_seg, hostdata_size, dpmi_flags;
void dpmi_init(void) {
// Проверяем доступность и параметры сервера DPMI
inregs.x.ax = 0x1687; int86x(0x2F, &inregs, &outregs, &segregs); if(outregs.x.ax != 0) { printf("Сервер DPMI не активен."); exit(-1); }
// Определяем версию сервера DPMI
printf("Версия сервера DPMI: \t\t\t%d.%d\n", outregs.h.dh, outregs.h.dl);
// Определяем тип процессора
printf("Тип процессора:\t\t\t\t"); if(outregs.h.cl == 2) printf("80286"); else if(outregs.h.cl == 3) printf("80386"); else if(outregs.h.cl == 4) printf("80486");
// Определяем возможность работы с 32-разрядными // программами
dpmi_flags = outregs.x.bx; printf("\nПоддержка 32-разрядных программ:\t"); if(dpmi_flags && 1) printf("ПРИСУТСТВУЕТ"); else printf("ОТСУТСТВУЕТ");
// Определяем размер области памяти для сервера DPMI
hostdata_size = outregs.x.si; printf("\nРазмер памяти для сервера DPMI:\t\t%d байт", hostdata_size * 16);
// Определяем адрес точки входа в защищённый режим
FP_SEG(pm_entry) = segregs.es; FP_OFF(pm_entry) = outregs.x.di; printf("\nАдрес точки входа в защищённый режим: \t%Fp\n", pm_entry);
// Заказываем память для сервера DPMI
if(hostdata_size) { if(_dos_allocmem(hostdata_size, &hostdata_seg) != 0) { printf("Мало стандартной памяти"); exit(-1); } }
}
// ------------------------------------------------ // Процедура для установки защищённого режима // ------------------------------------------------
void set_pmode() {
// Входим в защищённый режим
asm { mov ax, hostdata_seg mov es, ax mov ax, dpmi_flags } (*pm_entry)();
}
// ------------------------------------------- // Процедура вывода символа на экран в // защищённом режиме // -------------------------------------------
void pm_putch(int chr) {
// Структура для вызова прерывания должна // быть определена как статическая
static RM_INT_CALL regs; static RM_INT_CALL far *pregs = (void far *) 0;
// В первый раз инициализируем структуру // и указатель на неё
if (!pregs) { pregs = ®s; memset(pregs, 0, sizeof(RM_INT_CALL)); regs.eax = 0x0200; } regs.edx = chr;
// Вызываем прерывание DOS для вывода символа
rm_int(0x21, 0, pregs); }
// ------------------------------------------- // Процедура вывода строки на экран в // защищённом режиме // -------------------------------------------
void pm_puts(char *str_ptr) { while (*str_ptr) { pm_putch(*str_ptr); str_ptr++; } }
// ------------------------------------------- // Процедура вывода строки на экран в // защищённом режиме, аналог функции printf() // -------------------------------------------
void cdecl pm_printf(const char *fmt, ...) { char buffer[512], *sptr=buffer; va_list marker; va_start(marker, fmt); vsprintf(buffer, fmt, marker); va_end(marker); while (*sptr) pm_putch(*sptr++); }
// -------------------------------------------- // Процедура вызова прерывания реального режима // --------------------------------------------
int rm_int(unsigned int_number, // номер прерывания unsigned params, // количество слов параметров, // передаваемых через стек RM_INT_CALL far *rm_call) // адрес структуры // для вызова прерывания { asm { push di push bx push cx mov ax, 0300h // функция вызова прерывания mov bx, int_number mov cx, params; les di, rm_call // запись в ES:DI адреса структуры int 31h // вызов сервера DPMI jc error mov ax, 0 // нормальное завершение jmp short rm_int_end } error: asm mov ax, 0 // завершение с ошибкой rm_int_end: asm pop cx asm pop bx asm pop di }
// ----------------------------------------------------- // Процедура отображает текущее состояние памяти // -----------------------------------------------------
int mi_show(void) {
PMI minfo, far *minfoptr = &minfo; unsigned long psize, far *psizeptr=&psize; unsigned sel; void far *fp;
get_mi(minfoptr);
pm_printf(" Информация об использовании памяти\n\r" " ----------------------------------\n\r" "\r\n Размер максимального доступного блока:\t\t%ld байт" "\r\n Доступно незафиксированных страниц:\t\t%ld", minfo.avail_block, minfo.max_page);
pm_printf("\r\n Доступно зафиксированных страниц:\t\t%ld" "\r\n Размер линейного адресного пространства:\t%ld страниц" "\r\n Всего имеется незафиксированных страниц:\t%ld", minfo.max_locked, minfo.linadr_space, minfo.total_unlocked);
pm_printf("\r\n Количество свободных страниц:\t\t\t%ld" "\r\n Общее количество физических страниц:\t\t%ld", minfo.free_pages, minfo.tot_phys_pages);
pm_printf("\r\n Свободное линейное адресное пространство:\t%ld страниц" "\r\n Размер файла/раздела для страничного обмена:\t%ld страниц", minfo.free_linspace, minfo.size_fp);
get_page_size(psizeptr); pm_printf("\r\n Размер страницы:\t\t\t\t%ld байт\r\n", psize);
// Выводим текущие значения регистров CS и DS
asm mov sel,cs pm_printf("\n\r CS = %04.4X, ",sel); asm mov sel,ds pm_printf("DS = %04.4X",sel);
// Выводим значение текущего приоритетного кольца
fp = (void far *) main; sel = FP_SEG(fp) & 3; pm_printf("\n\r Номер приоритетного кольца = %d\n\r",sel);
}
// ----------------------------------------------- // Процедура для получения информации об // использовании памяти // -----------------------------------------------
int get_mi(PMI far *minfo) { asm { mov ax, 0500h les di, minfo // ES:DI = адрес структуры DMI int 31h jc error mov ax, 0 jmp short get_mi_end } error: asm mov ax, 1 get_mi_end: }
// ------------------------------------------------ // Процедура для получения размера страницы памяти // ------------------------------------------------
int get_page_size(long far *page_size) { asm { mov ax, 0604h int 31h jc error
les di, page_size // ES:DI = адрес page_size mov es:[di], cx mov es:[di+2], bx
mov ax, 0 jmp short gps_end } error: asm mov ax, 1 gps_end: }
// -------------------------------------------------- // Определение сегментного адреса видеопамяти // --------------------------------------------------
unsigned crt_mode, crt_seg;
int video_init(void) {
union REGS r;
// Определяем текущий видеорежим
r.h.ah=15; int86(0x10,&r,&r); crt_mode = r.h.al;
if(crt_mode == MONO_MODE) crt_seg = 0xb000; else if(crt_mode == BW_80_MODE || crt_mode == COLOR_80_MODE) crt_seg = 0xb800; else { printf("\nИзвините, этот видеорежим недопустим."); exit(-1); } }
// --------------------------------------------------- // Получение селектора для адресации видеопамяти // ---------------------------------------------------
char far *vid_ptr; DESCRIPTOR d; unsigned ldtsel;
int alloc_videosel(void) {
void far *fp; unsigned long addr;
FP_SEG(vid_ptr) = crt_seg; FP_OFF(vid_ptr) = 0; pm_printf(" Адрес видеопамяти реального режима:\t %Fp\r\n", vid_ptr);
// Получаем свободный LDT-селектор
if (! (ldtsel = get_sel())) { pm_printf(" Ошибка при получении селектора"); dos_exit(-1); } pm_printf(" Получен селектор:\t\t\t%04.4X\n\r", ldtsel);
// Подготавливаем дескриптор для полученного селектора
d.limit = 0x2000; addr = ABSADDR(crt_seg, 0); d.addr_lo = addr & 0xFFFF; d.addr_hi = addr >> 16; d.access.accessed = 0; // не использовался d.access.read_write = 1; // разрешены чтение/запись d.access.conf_exp = 0; // не стек d.access.code = 0; // это сегмент данных d.access.xsystem = 1; // не системный дескриптор d.access.dpl = 3; // приоритетное кольцо 3 d.access.present = 1; // сегмент присутствует в памяти d.reserved = 0;
// Устанавливаем дескриптор
if (!set_descriptor(ldtsel, &d)) { pm_printf(" Ошибка при установке дескриптора"); getch(); dos_exit(-1); }
// Выводим на экран адрес видеопамяти
FP_SEG(vid_ptr) = ldtsel; FP_OFF(vid_ptr) = 0; pm_printf(" Адрес видеопамяти защищённого режима:\t%Fp\r\n", vid_ptr); }
// -------------------------------------------------- // Освобождение селектора видеопамяти // --------------------------------------------------
int free_videosel(void) { if (!sel_free(ldtsel)) { pm_printf(" Ошибка при освобождении селектора"); dos_exit(-1); } }
// ---------------------------------------------- // Получить один селектор в LDT // ----------------------------------------------
unsigned get_sel(void) { asm { mov ax, 0 // получить селектор mov cx, 1 // нужен один селектор int 31h jc error jmp short gs_end // AX содержит новый LDT-селектор } error: asm mov ax, 0 // произошла ошибка gs_end: }
// -------------------------------------------------- // Установить дескриптор для LDT-селектора // --------------------------------------------------
int set_descriptor(unsigned pm_sel, DESCRIPTOR far *desc) {
asm { push di push bx mov ax, 000Ch mov bx, pm_sel les di, desc int 31h jc error mov ax, 1 jmp short sd_end } error: asm mov ax, 0 sd_end: asm pop bx asm pop di }
// -------------------------------------------------- // Освободить LDT-селектор // --------------------------------------------------
int sel_free(unsigned pmodesel) { asm { mov ax, 0001h mov bx, pmodesel int 31h jc error mov ax, 1 jmp short done } error: asm mov ax, 0 done: }
// ------------------------------------------------------- // Вывод символа непосредственной записью в видеопамять // -------------------------------------------------------
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(ldtsel, 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, " Демонстрация работы с интерфейсом " "DPMI ¦ © Frolov A.V., 1992 ", 0x30);
}