Использование функций 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); }