unsigned int es, cs, ss,
Пример 5
struct SREGS { unsigned int es, cs, ss, ds; };
Наконец, структура REGPACK, обеспечивающая наиболее полный набор регистров микропроцессора:
Пример 5
void interrupt int_handler (int bp,int di, int si, int ds, int es,int dx,int cx,int bx,int ax, int ip,int cs,int flags);
Рассмотрим элементы описания подробнее. Имя функции (у нас int_handler) может быть произвольным. Тип функции void очевиден - программа обработки прерывания не может возвращать значение. Описатель interrupt заставляет Си транслятор генерировать некоторые дополнительные коды для этой функции. Как известно, по команде INT в стек заносится содержимое регистра флагов и регистров CS:IP. Дополнительные коды, гене- рируемые для interrupt-функции, обеспечивают сохранение в стеке остальных регистров. При возврате управления из interrupt-функции содержимое регистров восстанавливается, и возврат выполняется командой IRET. (При программировании на языке Ассемблера программист должен сам заботиться о сохранении и восстановлении регистров).
Поскольку для обычных (не interrupt) функций Си через стек передаются значения параметров, в функции обработки прерывания могут быть описаны параметры, как это показано в нашем примере, и программист может работать с регистрами как с параметрами, переданными его функции. Порядок сохранения регистров в стеке - всегда такой как показано в примере. Более того, в отличие от обычных функций Си параметры interrupt-функции являются также и выходными. Поскольку содержимое регистров перед возвратом восстанавливается из стека, любое изменение параметра будет произведено в стеке, и при возврате восстановится измененное содержимое регистра. Если же в программе обработки прерываний не требуется обработки содержимого регистров, она может быть описана как функция без параметров.
И еще одно требование к функции обработки прерывания. Если она обрабатывает аппаратное прерывание, то такую функцию необходимо заканчивать оператором сброса контроллера прерываний:
Пример 5
/*== ПРИМЕР 6.4 ==*/ /*============== Сигнал тревоги в AT ================*/ #include <dos.h>
#include <stdio.h>
/* Выражения преобразования BCD->int и наоборот */ #define bcd_to_int(x) (x>>4)*10+(x&0x0f) #define int_to_bcd(x) ((x/10)=60) { m-=60; rr.h.ch=int_to_bcd(bcd_to_int(rr.h.ch)+1); } rr.h.cl=int_to_bcd(m); /* Запись увеличенного времени в регистры тревоги */ rr.h.ah=6; /* функция 6 */ int86(0x1a,&rr,&rr); rr.h.ah=0x2c; intdos(&rr,&rr); printf("\nВремя запуска - %02d:%02d:%02d\n", rr.h.ch,rr.h.cl,rr.h.dh); /* Ожидание тревоги */ flag=0; /* Переменная flag установится в 1 по сигналу тревоги в обработчике прерывания 4A */ while(flag==0); rr.h.ah=0x2c; intdos(&rr,&rr); printf("Время тревоги - %02d:%02d:%02d\n", rr.h.ch,rr.h.cl,rr.h.dh); /* Отмена тревоги */ rr.h.ah=7; /* функция 7 */ int86(0x1a,&rr,&rr); } /*==== Обработчик прерывания 4A - обработчик тревоги ====*/ void interrupt new4A() { putchar(7); /* Звуковой сигнал */ flag=1; /* Установка флага */ writevect(0x4a,old4A); /* Восстановление вектора */ } /*==== Получение старого вектора ====*/ void *readvect(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*==== Запись нового вектора ====*/ void writevect(int in, void *h) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h); rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr); }
Пример 5
/*== ПРИМЕР 7.5 ==*/ /*============= Прерывание BIOS 16 ============*/ #include <dos.h>
main() { union REGS rr; int i; printf("\nПрерывание 0x16, функция 1 >"); /* Ожидание нажатия клавиши */ do { rr.h.ah=1; int86(0x16,&rr,&rr); } while((rr.x.flags&0x0040)!=0); printf("%02x %02x",rr.h.ah,rr.h.al); /* Прием клавиши */ rr.h.ah=0; int86(0x16,&rr,&rr); /* Здесь выведется то же, что и в предыдущей строке, т.к. клавиша, код которой прочитан по функции 1 из клавиа- туры не удалена. */ printf(" %02x %02x\n",rr.h.ah,rr.h.al); /* Прием новой клавиши с ожиданием */ printf("\nПрерывание 0x16, функция 0 >"); rr.h.ah=0; int86(0x16,&rr,&rr); printf("%02x %02x\n",rr.h.ah,rr.h.al); printf("\nПрерывание 0x16, функция 2 >"); /* Теперь надо нажимать клавиши состояний и смотреть, что выводится */ do { rr.h.ah=2; int86(0x16,&rr,&rr); printf("%02x ",rr.h.al); delay(300); /* Проверка нажатия любой (кроме переключателя) клавиши. Цикл продолжается до нажатия клавиши "пробел" */ rr.h.ah=1; int86(0x16,&rr,&rr); } while(rr.h.al!=13); /* Очистка буфера клавиатуры */ poke(0x40,0x1a,peek(0x40,0x1c)); }
7.3. Функции стандартного ввода DOS
В DOS нет функций, ориентированных непосредственно на клавиатуру. Функции ввода DOS работают с файлом стандартного ввода, который по умолчанию связан с клавиатурой. Но при переназначении стандартного ввода функции DOS будут успешно работать и с другим источником.
Коротко перечислим функции DOS.
- Функция 1 - ввод символа с эхо, эта функция обрабатывает комбинацию клавиш Ctrl+Break. Возвращает ASCII-код в регистре AL, для расширенных ASCII-кодов требуется два обращения к функции: первое возвращает 0, а второе - код.
- Функция 7 - ввод символа без эхо, не обрабатывает комбинацию Ctrl+Break. Возвращает то же, что и функция 1.
- Функция 8 - ввод символа без эхо, обрабатывает Ctrl+Break, возвращает то же, что и функция 1.
- Функция 6 - ввод-вывод символов.
Пример 5
/*== ПРИМЕР 8.6 ==*/ /*============ Управляющие коды режимов печати ===========*/ #include <stdio.h> #define prt(x) putc(x,stdprn); main() { /* Инициализация */__ _.prt(27); prt(64); /* Обычный режим */ fprintf(stdprn,"Default mode\n"); /* Режим двойной ширины */ prt(14); fprintf(stdprn,"Set double width mode\n"); prt(20); fprintf(stdprn,"Close double width mode\n"); /* Режим плотной печати */ prt(15); fprintf(stdprn,"Set empassed mode\n"); prt(18); fprintf(stdprn,"Close empassed mode\n"); /* Режим подчеркивания */ prt(27); prt(0x2d); prt(1); fprintf(stdprn,"Set underline mode\n"); prt(27); prt(0x2d); prt(0); fprintf(stdprn,"Close underline mode\n"); /* Режим двойной жирности */ prt(27); prt(0x45); fprintf(stdprn,"Set double strike mode\n"); prt(27); prt(0x46); fprintf(stdprn,"Close double strike mode\n"); /* Режим верхних индексов печати */ prt(27); prt(0x53); prt(0); fprintf(stdprn,"Set superscript mode\n"); prt(27); prt(0x54); fprintf(stdprn,"Close superscript mode\n"); /* Режим нижних индексов печати */ prt(27); prt(0x53); prt(1); fprintf(stdprn,"Set subscript mode\n"); prt(27); prt(0x54); fprintf(stdprn,"Close subscript mode\n"); }
Отдельный программный пример иллюстрирует интересную возможность формирования пользователем собственных печатных символов. Образы выводимых символов хранятся в ПЗУ принтера, но в принтере есть еще и ОЗУ, в которое могут быть загружены образы, созданные пользователем. Образ формируется на сетке высотой 8 и шириной 11 точек. В памяти образ представляется в двоичном виде, причем один байт описывает один столбец образа. Сформировав двоичный образ символа, следует записать его в ОЗУ, для чего используется Esc-последовательность вида:- 0x1B, 0x26, 0 - задание действия "загрузка шрифта в ОЗУ";
- <начальный код>, <конечный код> - одной последовательностью можно загрузить в ОЗУ несколько образов, каждому из которых присваивается свой код в ОЗУ;
- <байт описатель> - младший полубайт содержит ширину символа, в старшем 0 в старшем разряде означает, что при печати весь образ сдвигается на 1 точку вниз;
- 11 байт описания образа.
Пример 5
/*== ПРИМЕР 9.3 ==*/ /*==== Цветовые атрибуты. Прямая запись в видеопамять ====*/ #include <dos.h>
/* Сегментный адрес видеопамяти */ #define VSEG 0xb800 /* Вычисление смещения в видеопамяти */ #define VADDR(x,y) y*160+x*2-162 #define byte unsigned char # define word unsigned int char st[]=" x "; /* Обpазец */ word voff; /* Смещение в видеопамяти */ byte color; /* Цветовой атpибут */ int x,y; /* Экpанные кооpдинаты */ byte key; /* Код клавиши */ byte mode; /* Режим мерцания/код палитры */ byte pl[]= /* Исходные палитры 16 цветов */ { 0,1,2,3,4,5,6,56,7,8,16,24,32,40,48,56 }; union REGS rr; char *s; int i; main() { /* очистка экрана */ for (voff=0; voff
Образ экрана размером 80 x 25 символов в видеопамяти занимает 4000 байт. Но видеопамять часто имеет значительно больший объем. Так, для EGA объем ее может достигать 256 Кбайт. Половина этого объема используется только в графических режимах, вторая половина составляет адресное пространство в 32 Кбайта, что позволяет разместить в ней 8 образов экрана. Это пространство разбито на 4-Кбайтные участки, называемые страницами. Таким образом, в EGA может быть 8 текстовых страниц (а при 40 символах в строке - 16 страниц по 2 Кбайта). По умолчанию построением изображения на экране управляет образ, записанный в нулевой странице, но имеется возможность переключить адаптер на отображение 1-й, 2-й и т.д. страниц. Это обеспечивает функция 5 (номер страницы задается в AL). Получить номер активной в данный момент страницы можно из области памяти BIOS. Программа примера 9.4 заполняет видеопамять текстом (разным на разных страницах), а затем обеспечивает переключение активной страницы.
Пример 5
/*== ПРИМЕР 10.3 ==*/ /*==== Корневой (загрузочный) сектор логического диска ===*/ #include <dos.h> #define byte unsigned char #define word unsigned int #define dword unsigned long /* Структура корневой записи DOS 4.x */ struct RootRec { byte jmp[3]; /* Переход на загрузку */ char ident[8]; /* Идентификатор системы */ /* Расширенный Блок Параметров BIOS */ /* стандартная часть */ word SectSize; /* Размер сектора (байт) */ byte ClustSize; /* Размер кластера (сект) */ word ResSect; /* Резервных секторов */ byte FatCnt; /* Число копий FAT */ word RootSize; /* Размер корневого оглавления ( число элементов оглавления по 32 байта) */ word TotSecs; /* Общее число секторов */ byte Media; /* Тип диска (то же, что 1-й байт FAT */ word FatSize; /* Размер FAT (секторов) */ /* расширение; следующие 3 поля не входят в BPB для DOS 3.x, но входят в загрузочную запись */ word TrkSecs; /* Секторов на дорожке */ word HeadCnt; /* Число поверхностей */ word HidnSecL; /* Число спрятанных секторов (младшая часть) */ /* эта часть имеется только для DOS 4.x и больше */ word HidnSecH; /* (старшая часть) */ /* для диска >32 Мбайт используется вместо TotSecs */ dword LongTotSecs; /* Число секторов */ /* конец расширенного BPB */ byte Drive; /* Физический номер дисковода */ byte reserved1; byte DOS4_flag; /* Код 41 в этом поле - признак расширенного формата загр.записи */ dword VolNum; /* Серийный номер тома */ char VolLabel[11]; /* Метка тома */ char FatForm[8]; /* FAT12 или FAT16 */ /* Далее следуют программа и данные загрузки */ } *rt; byte buffer[512]; /* Структура параметров для INT 25 при работе с большим диском (>32 Мбайт) */ struct{ dword first_sect; /* # логического сектора */ word count; /* число секторов */ byte *ptr; /* адрес в памяти */ } parm; union REGS rr; struct SREGS sr;
main() { char drive; /* идентификатор дисковода */ byte sys; /* признак объем > 32 Мбайт */ ASK1: printf("\nУкажите имя диска >"); drive=getche(); if (drive>'b') { ASK2:printf("\nОбьем лог.
Пример 5
/*== ПРИМЕР 11.5 == Файл 11_5.C ==*/ /*======== Программа, вызывающая другую программу ========*/ #include <dos.h> #include <string.h> #include <stdlib.h> #define byte unsigned char # define word unsigned int struct EPB { /* блок параметров EXEC */ word env; /* адрес окружения */ char far *parm_str; /* адрес строки параметров */ byte far *fcb1; /* адреса FCB */ byte far *fcb2; } epb; char call_string[128]; /* строка вызова */ char parm_string[128] = "*"; /* строка параметров, 1-ым байтом будет длина строки */ char env_str[]= "Специальное окружение\0 для иллюстрации\0\0$"; char father_file[] = "11_5.EXE"; char sun_file[] = "11_5_A.EXE"; word env; /* адрес подготовленного окружения */ word pid; /* свой PID */ union REGS rr; struct SREGS sr; char far *s, *s1;
main() { /* определение своего PID */ rr.h.ah=0x62; intdos(&rr,&rr); pid=rr.x.bx; /* формирование строки вызова */ for(s=(char *)MK_FP(peek(pid,0x2C),0); *s|*(s+1); s++); strcpy(call_string,s+4); if ((s=strstr(call_string,father_file))==NULL) exit(0); *s='\0'; strcat(call_string,sun_file); printf("\nПрограмма-родитель: PID = %04X\n",pid); printf("Вызов: >%s %s\n",call_string,parm_string+1); /* выделение памяти для нового окружения */ rr.h.ah=0x48; rr.x.bx=4; intdos(&rr,&rr); if (rr.x.cflag) printf("Ошибка 48h\n"); else { /* формирование нового окружения и EPB */ epb.env=env=rr.x.ax; s=(char *)MK_FP(env,0); for(s1=env_str; *s1!='$'; *(s++)=*(s1++)); /* формирование строки параметров */ printf("Введите строку параметров >"); gets(parm_string+1); epb.parm_str=parm_string; parm_string[0]=strlen(parm_string)-1; epb.fcb1=epb.fcb2=NULL; /* загрузить и выполнить */ rr.h.ah=0x4b; /* функция 4B */ rr.h.al=0; /* подфункция 0 */ sr.ds=FP_SEG(call_string); /* адр.строки вызова */ rr.x.dx=FP_OFF(call_string); sr.es=FP_SEG(&epb); /* адр.EPB */ rr.x.bx=FP_OFF(&epb); intdosx(&rr,&rr,&sr); if (rr.x.cflag) printf("Ошибка EXEC - %d\n",rr.x.ax); else { /* получение кода возврата */ rr.h.ah=0x4d; /* функция 4D */ intdos(&rr,&rr); printf("Возврат из вызова - %d, с кодом - %02xh\n", rr.h.ah,rr.h.al); } /* освобождение памяти */ rr.h.ah=0x49; sr.es=env; intdosx(&rr,&rr,&sr); if (rr.x.cflag) printf("Ошибка 49h\n"); } } /*== Файл 11_5_A.C ==*/ /*======== Программа, вызываемая из другой программы ========*/
#include <dos.h> #include <stddef.h> main(int argn, char *argv[]) { union REGS rr; int i; char *eee; printf("Hello, I`m 11_5_A!\n"); printf("ПАРАМЕТРЫ:\n"); for (i=0; i<argn; i++) printf(">>%s<<\n",argv[i]); printf("ОКРУЖЕНИЕ:\n"); for (i=0; environ[i]!=NULL; i++) printf("%s\n",environ[i]); rr.h.ah=0x4c; rr.h.al=0x47; intdos(&rr,&rr); }
Пример 5
/*== ПРИМЕР 13.1 ==*/ /*================= Просмотр списка драйверов ============*/ #include <dos.h> #define byte unsigned char #define word unsigned int #define ATR(x,z) if(drv->attr&x){printf(" %s\n",z);y++;} #define DA(x,y) (struct DR_HEAD *)MK_FP(x,y); struct DR_HEAD { /* заголовок драйвера */ struct DR_HEAD *next; word attr, strat_addr, intr_addr; char name[8]; } *drv; /* адрес текущего драйвера */ struct DR_HEAD *clock, *con; /* Адреса CLOCK$ и CON */ union REGS rr; struct SREGS sr; int i, y, y1; main() { /* получение адреса CVT */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); /* адрес драйвера часов */ clock=DA(peek(sr.es,rr.x.bx+10),peek(sr.es,rr.x.bx+8)); /* адрес драйвера консоли */ con=DA(peek(sr.es,rr.x.bx+14),peek(sr.es,rr.x.bx+12)); /* адрес NUL-драйвера */ drv=DA(sr.es,rr.x.bx+34); printf("\nСписок драйверов устройств\n"); while(FP_OFF(drv)!=0xffff) { printf("Адрес - %Fp атрибуты - %04X ", drv,drv->attr); if (drv->attr&0x8000) for (i=0;i<8; printf("%c",drv->name[i++])); else printf("блочный - %d",drv->name[0]); printf("\n"); y=0; if (drv==clock) { printf(" активный CLOCK$\n"); y++; } if (drv==con) { printf(" активный CON\n"); y++; } if (drv->attr&0x8000) { ATR(1,"консоль ввода") ATR(2,"консоль вывода") ATR(4,"нулевое устройство") ATR(8,"CLOCK$ (часы)") ATR(0x2000,"поддерживает OUB") } else { ATR(2,"32-байтный адрес сектора") ATR(0x2000,"читает media FAT") } ATR(0x40,"поддерживает функции DOS 3.2") ATR(0x800,"поддерживает Open/Close") ATR(0x4000,"поддерживает IOCTL") y=(y1=wherey())-y; getch(); for(i=y;i<y1;gotoxy(1,i++),clreol()); gotoxy(1,y); drv=drv->next; /* адрес след.драйвера */ } printf("Конец списка. Нажмите любую клавишу...\n"); getch(); }
Проанализировав результаты выполнения этой программы, читатель может прийти к следующим выводам. Пустой (NUL) драйвер, который состоит из одного заголовка выполняет единственную функцию - быть "головой" списка драйверов. Системные драйверы (драйверы, включенные в файл IO.SYS) образуют исходный список драйверов. Драйверы, устанавливаемые после них (по команде DEVICE в CONFIG.SYS), включаются в начало этого списка - сразу после NUL-драйвера. Таким образом, если устанавливаемый драйвер имеет то же имя, что и системный, то выбираться при обращении по этому имени будет устанавливаемый драйвер, так как он находится ближе к началу списка. При обращениях к драйверам CON и CLOCK$ поиск в списке не выполняется, для ускорения обращения их адреса выбираются из CVT.
Содержание раздела