Системное программное обеспечение персональных ЭВМ

struct WORDREGS x; struct BYTEREGS



Пример 1

union REGS { struct WORDREGS x; struct BYTEREGS h; }; struct WORDREGS { unsigned int ax, bx, cx, dx; unsigned int si, di, cflag, flags; }; struct BYTEREGS { unsigned char al, ah, bl, bh; unsigned char cl, ch, cl, dh; };
Поле flags структуры WORDREGS отражает состояние флагов микропроцессора, а поле cflag - состояние системного флага переноса CY, в котором обычно при обращениях к DOS и к BIOS индицируется ошибка. Использование объединения REGS позволяет программисту обращаться к регистру общего назначения как к целому двухбайтному слову или к каждому байту этого слова, выбирая описатель второго уровня x или h соответственно. Так, если в программе имеется определение:



Пример 1

int int86(int int_num, union REGS *inregs, union REGS *outregs);
Функция выполняет прерывание с номером int_num, причем, перед выдачей команды INT содержимое полей объединения inregs копируется в регистры микропроцессора, а после возврата из прерывания - содержимое регистров - в поля объединения outregs.
Функция обращения к DOS:



Пример 1

/*= ПРИМЕР 4.2 =*/ /*=============== Поиск расширений ПЗУ ===============*/ #include <dos.h>
main() { unsigned int segm,off; /* Части адреса */ unsigned int byte=0xAA55; /* Маркер ПЗУ */ clrscr(); for (segm=0xc000;segm
Во всех проверенных нами машинах по адресу C000:0000 размещается ПЗУ дисплейного адаптера, а в машинах типа XT также по адресу C800:0000 - ПЗУ жесткого диска.
Адресное пространство с F600:0000 по FD00:0FFF предназначено для ПЗУ интерпретатора Бэйсика, имеющегося только на ПЭВМ производства фирмы IBM.



Наконец, от FE00:0000 и до конца адресного пространства расположено ПЗУ BIOS. BIOS в ПЗУ содержит программы, выполняющие следующие функции:

  • тест самопроверки;
  • начальный загрузчик;
  • обслуживание клавиатуры;
  • обслуживание дисплеев (CGA и MDA);
  • обслуживание последовательных портов;
  • служба времени;
  • печать экрана.




Пример 1

/*== ПРИМЕР 5.1 ==*/ /*======== Чтение типа ПЭВМ и даты издания BIOS =========*/ #include <dos.h>
main() { unsigned char pc; /* Код типа PC */ char *PT[]= { "AT", "PCjr", "XT", "IBM PC", "???" }; unsigned int t; /* Текущее смещение */ printf("\nТип ПЭВМ = %x = ",pc=peekb(0xf000,0xfffe)); if ((pc-=0xfc)>4) pc=4; printf("%s\n",PT[pc]); printf("Дата издания BIOS = "); for (t=0xfff5;t



Пример 1

x x 1 1 0 1 1 0
где xx - номер канала таймера, а затем послать в порт данных выбранного канала сначала младший, а затем старший байт счетчика.



Пример 1

/*== ПРИМЕР 7.1 ==*/ /*======== Прерывание от клавиатуры и scan-коды =========*/ #include <dos.h>
# define byte unsigned char void interrupt (*old9)(); /* Для сохр. старого вектора */ void interrupt new9(); /* Описание нового обработчика */ byte SC[100]; /* Массив - накопитель скан-кодов */ byte Nsc=0; /* Счетчик скан-кодов */ byte eoj_flag; /* Флаг окончания */ void *readvect(int in); void writevect(int in, void *h); union REGS rr; struct SREGS sr; main() { int i; /* Перехват вектора 9 */ old9=readvect(9); writevect(9,new9); printf("\n\nНажимайте на калавиши \n"); printf("Esc - конец работы\n"); /* В этом цикле происходит обработка нажатых клавиш */ for (eoj_flag=0; eoj_flag==0;); /* Восстановление вектора 9 */ writevect(9,old9); /* Вывод на экран введенных скан-кодов */ for (i=0; i<Nsc; printf("%x ",SC[i++]) ); } /*==== Обработчик прерывания 9 ====*/ void interrupt new9() { byte scan; /* Скан-код */ byte c; /* Состояние порта 61 */ /* Чтение scan-кода. */ scan=inportb(0x60); /* По Esc (скан - 1) устанавливается признак окончания */ if (scan==1) eoj_flag=1; /* Запоминается скан-код */ SC[Nsc++]=scan; /* Устанавл.признак окончания при заполнении массива */ if (Nsc>99) eoj_flag=1; /* Подтверждение приема. В порт 61 на короткое время выставляется "1" по шине старшего разряда. */ c=inportb(0x61); outportb(0x61,c|0x80); outportb(0x61,c); /* Сброс контроллера прерываний. */ outportb(0x20,0x20); } /*==== Получение старого вектора ====*/ 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); }
Программный пример 7.2 иллюстрирует технику обработки "горячей клавиши", часто применяемую в резидентных программах. Такие программы перехватывают прерывание 9 и распознают код некоторой закрепленной за ними клавиши. При распознавании "горячей клавиши" программа выполняет какие-то свои действия, обработку всех остальных клавиш программа "отдает" системе. Для нашей программы "горячей" является клавиша "a", действия нашей программы по клавише "a" - пустые, что приведет к исчезновению буквы "a" из вводимого потока. Эта программа, как и предыдущая, также перехватывает прерывание 9. Обработчик прерывания читает скан-код, но не спешит посылать в клавиатуру подтверждение, а анализирует код. Если это скан-код клавиши "a", то обработчик удаляет его из клавиатуры и сбрасывает контроллер прерываний. В противном случае вызывается системный обработчик, который повторно прочитает тот же код из порта 0x60 и распорядится им по-своему. Вы можете убедиться в том, что в символьную строку, вводимую функцией scanf основной программы, не будут включаться буквы "a".



Пример 1

/*== ПРИМЕР 8.1 ==*/ /*============== Получение статуса принтера ==============*/ #include <dos.h> main() { union REGS rr; int dataport,statusport,ctrlport; /* Номера портов */ unsigned char stat; /* Байт статуса */ int i; /* Определение адресов портов принтера */ dataport=peek(0x40,8); statusport=dataport+1; ctrlport=statusport+1; printf("Порты LPT1 = %03X, %03X, %03X\n", dataport,statusport,ctrlport); /* Проверка состояний */ printf("\nУстановите: принтер выключен. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 11110111\n"); printf("\nУстановите: принтер offline. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 01010111\n"); printf("\nУстановите: нет бумаги. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 01110111\n"); printf("\nУстановите: принтер готов. "); printf("Нажмите любую клавишу\n"); getch(); stat=inportb(statusport); printf("Состояние принтера - "); for (i=7; i>=0; i--) if ((stat>>i)&1) printf("1"); else printf("0"); printf("\nEpson состояние - 11011111\n"); }
Разряды байта, передаваемого в порт управления, интерпретируются следующим образом:
0- устанавливается в 1 при передаче байта данных (строб вывода);
1- установка его в 1 вызывает автоматический перевод строки после возврата каретки;
2- нормально установлен в 0, устанавливается в 1 при инициализации порта принтера;
3- устанавливается в 1 при выборе принтера;
4- разрешает (1) прерывание принтера, используется только в программе спуллинга печати;
5 - 6- не используются устанавливаются в 0.
<



Пример 1

/*== ПРИМЕР 9.1 ==*/ /*============ Определение активного адаптера ============*/ #include <dos.h>
unsigned char types1A[] = { 0,1,2,4,5,6,7,8,10,11,12,0xff }; char *stypes1A[] = { "нет дисплея","MDA,моно","CGA,цв.", "EGA,цв.","EGA,моно","PGA,цв.","VGA,моно,анал.", "VGA,цв.,анал.","MCGA,цв.,цифр.","MCGA,моно,анал.", "MCGA,цв.,анал.","неизвестный тип", "непредусмотренный код" }; unsigned char b[64]; /* буфер данных VGA */ struct SREGS sr; union REGS rr; int i; main() { /* Предположим, VGA */ rr.h.ah=0x1a; rr.h.al=0; int86(0x10,&rr,&rr); if (rr.h.al==0x1a) { printf("Поддерживается ф-ция 1Ah прерывания 10h\n"); for (i=0; i0 && i
Функция 0x1A доступна только при наличии расширения BIOS, ориентированного на обслуживание VGA. В этом случае функция возвращает в регистре AL код 0x1A - свою "визитную карточку", а в BL - код активного видеоадаптера. Мы в нашем примере в случае, когда функция 0x1A поддерживается, обращаемся еще и к функции 0x1B - эта последняя заполняет 70-байтный блок информации о состоянии, из которого мы выбираем объем видеопамяти.

Если 0x1A не поддерживается, значит, VGA у нас нет, в этом случае можно обратиться к функции 0x12 - получение информации о EGA. При наличии расширения, ориентированного на EGA, эта функция изменяет содержимое BL (перед обращением оно должно быть 0x10) на 0 (цветной режим) или 1 (монохромный режим) а в BH возвращает объем видеопамяти.

Если же ни 0x1A, ни 0x12 не поддерживаются, то список оборудования BIOS содержит достаточную информацию о видеоадаптере.



Пример 1

ttttttttTTssssss, где

  • t...t - младшие 8 бит номера дорожки;
  • TT - старшие 2 бита номера дорожки;
  • s...s - номер сектора на дорожке (6 бит).

В DOS для чтения/записи секторов служат прерывания (прерывания, а не функции DOS!) 0x25 (чтение) и 0x26 (запись).

Обычный формат обращения к этим прерываниям следующий:
AL- номер логического диска (0 - A, 1 - B, 2 - C, etc.);
CX- количество секторов, которое нужно прочитать /записать; DX - логический номер сектора;
DS:BX- адрес области оперативной памяти, с которой происходит обмен.

На выходе, как и для прерывания 0x13, устанавливается флаг переноса, а регистр AH содержит код ошибки при наличии таковой.

Заметим, что дисковый адрес задается здесь не физический, а логический - номер сектора относительно начала логического диска (о логических дисках - см. ниже). Нумерация секторов внутри логического диска начинается с 0, и номер логического сектора может быть вычислен как:
logs = ( (t * ns) + h) * nh + s-1; (10.1)
где

  • t, h, s - физический адрес (дорожка, головка, сектор);
  • ns - количество секторов на дорожке,
  • nh - количество головок чтения/записи.

Для получения абсолютного номера сектора надо к вычисленному значению прибавить еще некоторую величину s0 - абсолютный номер сектора начала логического диска.

Как мы увидим ниже, величины ns, nh, s0 могут быть получены из системной информации, находящейся на самом носителе.
В программе примера 10.1, иллюстрирующей применение средств чтения секторов средствами BIOS и DOS наибольший интерес (кроме вызывающих последовательностей для прерываний) представляют функции Daddr_to_Sect и Sect_to_Daddr, осуществляющие перевод физического адреса в логический и наоборот соответственно. Программа запрашивает способ задания адреса, формирует по заданному физическому адресу логический или наоборот, вводит один и тот же сектор дважды (сначала используя прерывание BIOS, а затем - DOS) и выводит на экран содержимое начального участка сектора, при двух вариантах ввода - результаты должны быть одинаковыми. Физический адрес в программе описывается структурой daddr, поля t, h, s которой содержат компоненты физического адреса, а ts - заготовку для регистра CX при обращении к прерыванию 0x13.

Предупредим читателя, что приведенная программа будет правильно работать только с дискетой, отформатированной на 360 Кбайт, так как величины nh, ns, s0 (параметры формулы 10.1) и другие (nt, nls) имеют константные значения, свойственные именно этому формату.



Пример 1

/*== ПРИМЕР 11.1 ==*/ /*=============== Распечатка собственного PSP ============*/ #include <dos.h> #define byte unsigned char # define word unsigned int struct psp { byte ret_op[2]; /* команда INT 20h */ word end_of_mem; /* вершина доступной памяти */ byte reserved1; byte old_call_dos[5]; /* старый вызов DOS */ void *term_ptr; /* адрес завершения */ void *ctrlbrk_ptr; /* адрес обработчика Ctrl+Break */ void *criterr_ptr; /* адрес обработчика крит.ошибок */ word father_psp; /* PID родителя */ byte JFT[20]; /* таблица файлов программы */ word env_seg; /* адрес окружения */ void *stack_ptr; /* адрес стека */ word JFT_size; /* размер таблицы файлов */ byte *JFT_ptr; /* адрес таблицы файлов */ byte reserved2[24]; byte new_call_dos[3]; /* новый вызов DOS */ } *mypsp; word mypid; /* сегм.адрес PSP */ int dos, i, l; char *s; union REGS rr; main() { clrscr(); /* определение версии DOS */ rr.h.ah=0x30; intdos(&rr,&rr); dos=rr.h.al; /* получение адреса своего PSP */ rr.h.ah=0x62; intdos(&rr,&rr); mypid=rr.x.bx; /* распечатка PSP */ printf("***** PID=%04X *****\n",mypid); mypsp=(struct psp *)MK_FP(mypid,0); printf ("Команды: завершение - int 20h ---> %02X %02X\n", mypsp->ret_op[0],mypsp->ret_op[1]); printf (" старый вызов DOS -------> "); for (i=0;i<5;printf("%02X ",mypsp->old_call_dos[i++])); printf ("\n новый вызов DOS --------> "); for(i=0;i<3;printf("%02X ",mypsp->new_call_dos[i++])); printf ("\n\nАдреса: конец памяти -------------> %04X:0000\n", mypsp->end_of_mem); printf(" обработчик завершения ----> %Fp\n", mypsp->term_ptr); printf(" обработчик Ctrl+Break ----> %Fp\n", mypsp->ctrlbrk_ptr); printf(" обработчик критич.ошибки -> %Fp\n", mypsp->criterr_ptr); printf(" стек ---------------------> %Fp\n", mypsp->stack_ptr); printf("\nРодитель: ------------------------> %04X ", mypsp->father_psp); pr_file_tab(); /* таблица файлов */ printf("\nОкружение DOS --------------------> %04X\n", mypsp->env_seg); s=(char *)MK_FP(mypsp->env_seg,0); while(l=strlen(s)) { printf(" %s\n",s); s+=l+1; } if (dos>2) { /* для DOS 3.0 и дальше можно получить строку вызова */ s++; l=*((int *)s); printf("Строки вызова ----------------------> %d\n",l); s+=2; for(i=0; i<l; i++) { printf("%s\n",s); s+=strlen(s)+1; } } getch(); /* увеличение размера таблицы файлов */ rr.h.ah=0x67; /* функция 67 */ rr.x.bx=30; /* новый размер - 30 */ intdos(&rr,&rr); if (rr.x.cflag) printf("Ошибка функции 67h\n"); else pr_file_tab(); getch(); } /*==== распечатка таблицы файлов ====*/ pr_file_tab() { s=mypsp->JFT_ptr; printf ("\n\nТаблица файлов: -------------------> %Fp (%d) ", s,mypsp->JFT_size); if (s==(byte *)mypsp+0x18) printf(" - в этом же PSP"); printf("\n"); for (i=0; ++i<=mypsp->JFT_size; printf("%d ",*(s++))); printf("\n"); }



Пример 1

struct MCB { byte type; /* тип */ word owner; /* владелец */ word size; /* размер */ byte reserved[3]; /* не используется */ char pgmname[8]; /* имя (только DOS 4.0 и выше) */ };
Поле type MCB содеpжит код, показывающий, является ли этот MCB последним (код буквы Z - 0x5A.) или непоследним (код буквы M - 0x4D). Поле owner содеpжит PID (сегментный адpес пpефикса пpогpаммного сегмента) пpогpаммы, котоpой данный блок памяти пpинадлежит. Если значение этого поля нулевое, то блок свободен. Поле size содеpжит pазмеp блока памяти в паpагpафах (в это число не включен 1 паpагpаф, занимаемый самим MCB). Следующие 3 байта (поле reserved) заpезеpвиpованы во всех веpсиях. Поле pgmname заpезеpвиpовано (не используется) в веpсиях DOS ниже 4.0. Начиная с веpсии 4.0, в MCB, пpедваpяющем пpогpаммный сегмент, здесь записано имя (без pасшиpения) пpогpаммы, находящейся в этом сегменте (если длина имени меньше 8 символов, оно заканчивается нулевым байтом).

Все MCB увязаны в цепочку. Зная адpес пеpвого MCB в цепочке, можно, пpибавив к нему длину (size) и еще 1 (паpагpаф, занимаемый самим MCB), получить адpес следующего MCB и так далее. Пpизнаком конца цепочки является значение 'Z' поля type.

Адpес начала цепочки блоков памяти можно получить пpи помощи недокументиpованной функции DOS 0x52. Подpобности пpименения этой функции pассмотpены в следующей главе. Здесь же только сообщим читателю, что эта функция возвpащает в pегистpах ES:BX некотоpый адpес. Вычтя из этого адpеса 2, получим адpес того слова памяти, в котоpом DOS хpанит сегментный адpес пеpвого MCB в цепочке.
Пpогpамма следующего пpимеpа позволяет пpосмотpеть "каpту pаспpеделяемой памяти" ПЭВМ - пpоиндициpовать, какие блоки свободны, а какие заняты и кем (какой пpогpаммой) заняты.



Пример 1

/*===== Таблица Векторов Связи для DOS 3.0 и выше =====*/ struct CVT { word MCB_segment; /* Сегментный адрес 1-го Блока Управления Памятью */ dword DPB_ptr; /* Адрес 1-го Блока Параметров Диска */ dword SFT_ptr; /* Адрес 1-й Системной Таблицы Файлов */ dword clock_ptr; /* Адрес заголовка драйвера CLOCK$ */ dword con_ptr; /* Адрес заголовка драйвера CON */ word maxBlock; /* Максимальный размер блока на блочном устройстве */ dword BCB_ptr; /* Адрес 1-го буфера дисковых операций */ dword ACD_ptr; /* Адрес Массива Текущих Каталогов */ dword FCBtab_ptr; /* Адрес Таблицы Блоков FCB, открываемых в режиме разделения */ word FCBtab_size; /* Размер Таблицы Блоков FCB (параметр FCBS в CONFIG.SYS) */ byte driveCnt; /* Число блочных устройств в системе */ byte lastdrive; /* Число идентификаторов дисков (параметр LASTDRIVE в CONFIG.SYS) */ byte NUL_drive[18]; /* Заголовок драйвера NUL */ };
Недокументированная функция DOS 0x52, именуемая в нефирменных описаниях GetSysVars или GetCvt, возвращает в регистрах ES:BX адрес поля DPB_ptr таблицы векторов связи. Но перед этим полем в памяти находится 2-байтный сегментный адрес первого управляющего блока памяти, который логически также относится к векторам связи (мы уже использовали это свойство функции 0x52 в программах предыдущей главы). Поэтому мы включаем это поле в CVT и считаем, что функция 0x52 возвращает адрес, на 2 больший начального адреса таблицы.

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



Пример 1

u 0 i r c l l o
    где
  • u - диск (1) или другое устройство (0);
  • i - разрешено игнорировать (1);
  • r - разрешено повторить (1);
  • c - разрешено снять (1);
  • ll - локализация места ошибки на диске (00 - системная область, 01 - таблица размещения файлов, 10 - каталог, 11 - область данных), имеет смысл только при u=1;
  • o - операция чтения (0) или записи (1).

  • Если в регистре AH бит u=1, то регистр AL содержит логический номер диска.
  • Регистры BP:SI указывают не заголовок драйвера устройства, на котором произошла ошибка.
  • Регистр DI (младший его байт) содержит код ошибки. Возможны следующие коды:
    0-запись на защищенный диск;9-нет бумаги в принтере;
    1-неизвестное устройство;    10-ошибка записи;
    2-дисковод не готов;    11-ошибка чтения;
    3-неизвестная команда;    12-общий сбой;
    4-ошибка CRC (циклического кода проверки);    13-нарушение режима разделения;
    5-неправильная структура запроса    14-нарушение блокировки;
    6-ошибка поиска;    15-ошибка замены диска;
    7-неизвестный тип диска;    16-недоступен FCB;
    8-не найден сектор;    17-буфер режима разделения переполнен.

    Обработчик критической ошибки должен вернуть в регистре AX код решения:
    0- игнорировать ошибку;
    1- повторить операцию;
    2- завершить программу;
    3- снять системный вызов, в котором произошла ошибка.

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

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



    Пример 1

    /*== ПРИМЕР 15.1 ==*/ /*============== Резидентная программа ===================*/ /* Модель памяти - small. /* При компиляции установить: Options -> Compiler -> Code generation -> Test stack overflow -> Off !!! */ #include <dos.h> #include <stdio.h> #include <string.h> #define byte unsigned char #define word unsigned int /* адрес в виде - сегмент:смещение */ typedef struct { word segm,offs; } addr; /* Описания функций */ byte check_tsr(void); /* проверка наличия TSR-программы в памяти */ void get_context(void); /* запоминание своего контекста */ void set_vectors(void); /* установка своих векторов */ void restore_vectors(void); /* восстановление векторов */ void far *get_vector(int n); /* чтение вектора */ void set_vector(int n, void far *v); /* установка вект. */ void act_tsr(void); /* активизация TSR-программы */ void self_kill(void); /* самовыгрузка TSR-программы */ void tsr_exec(void); /* прикладная часть TSR-программы */ /* описания новых обработчиков прерываний */ void interrupt new_8(); void interrupt new_9(); void interrupt new_13(); void interrupt new_28(); void interrupt new_2F(); void interrupt new_24(); /* номера обрабатываемых прерываний */ int int_nums[] = { 8, 9, 0x13, 0x28, 0x2f }; void interrupt (* new_v[])() = { /* адреса новых обработчиков */ new_8, new_9, new_13, new_28, new_2F }; /* области сохранения старых векторов */ void interrupt (* old_v[5])(); addr a_2F; /* адрес старого обработчика INT 2F */ /* Флаги */ byte far *dos_flag; /* адрес флажка занятости DOS */ byte tsr_run = 0; /* "TSR выполняется" (1) */ byte hot_key = 0; /* "горячая клавиша нажата"(1) */ byte disk_op = 0; /* "дисковая операция" (1) */ byte key_byte; /* байт символа/состояния клавиатуры */ word cflag; /* регистр флагов процессора */ /* набор команд перехода на обработчики прерываний */ struct far_jmp { byte jmp; /* код команды */ void interrupt (* int_h)(); /* адрес */ } far *jmpar; word jmp_segm; /* сегм.адрес блока команд перехода */ /* переменные контекста */ byte old_ctrl_break; /* состояние обработки Ctrl-Break прерванной программы */ addr old_stack; /* сегмент и указатель стека прерванной программы */ addr tsr_stack; /* то же - TSR-программы */ word old_pid; /* PID прерванной программы */ word tsr_pid; /* то же - TSR-программы */ addr old_dta; /* адрес DTA прерванной программы */ addr tsr_dta; /* то же - TSR-программы */ void interrupt (* old_24)(); /* адрес обработчика критической ошибки прерванной программы */ /* общие переменные */ word sizeprogram = 17000/16; /* размер программы, определенный опытным путем */ word hot_scan = 2; /* scan-код горячей клавиши - "1" */ word hot_status =8; /* маска спец.клавиши - Alt */ char tsr_mark[] = "TSR-программа 15_1 загружена"; /* опознавательная строка символов */ char far *mark; byte com_func; /* номер коммуникационной функции прерывания 2F */ word far *ast; /* адрес в стеке */ word mcb_seg; /* адрес блока MCB */ byte screen[160]; /* для сохранения части экрана */ union REGS rr; struct SREGS sr; int i, n; /*-------------------------------------------------------*/ /* ==== Пусковая программа ====*/ main(int argn, char *argc[]) { /* проверка - не является ли программа уже резидентной */ /* check_tsr возвращает 0, если уже резидентна */ /* check_tsr присваивает значение переменной com_func */ mark=(char far *)tsr_mark; if (!check_tsr()) { /*--- программа уже резидентна --*/ /* с каким параметром вызвана программа ? */ if (argn>1) { if (!strcmp(argc[1],"/Q")) { /* /Q - обращение к коммуникации для выгрузки */ rr.h.ah=com_func; rr.h.al=0x30; /* коммуникационное прерывание */ int86x(0x2f,&rr,&rr,&sr); printf("TSR-программа 15_1 выгружена\n"); } else /* неправильный вызов */ printf("15_1 - инсталляция\n15_1 /Q - выгрузка\n"); } else { /* параметров нет - попытка повторной инсталляции */ printf("!!! УЖЕ- %s -УЖЕ !!!\n",tsr_mark); printf("Функция прерывания 2Fh - %02Xh\n",com_func); } } else { /*--- программа не резидентна --*/ get_context(); /* запоминание своего контекста */ /* освобождение своего блока окружения */ sr.es=peek(tsr_pid,0x2c); rr.h.ah=0x49; intdosx(&rr,&rr,&sr); set_vectors(); /* установка своих векторов */ /* получение адреса флажка занятости DOS */ rr.h.ah=0x34; intdosx(&rr,&rr,&sr); dos_flag=(byte far *)MK_FP(sr.es,rr.x.bx); /* завершение программы с оставлением ее резидентной */ printf("%s\n",tsr_mark); rr.h.ah=0x31; rr.h.al=0; rr.x.dx=sizeprogram; intdos(&rr,&rr); } } /*---------------------------------------------*/ /*==== Обработчик прерывания от клавиатуры ====*/ void interrupt new_9() { /* чтение scan-кода и проверка его на совпадение с кодом горячей клавиши; проверка состояния переключателей */ if (inportb(0x60)==hot_scan) { key_byte=peekb(0x40,0x17); if ((key_byte&hot_status) == hot_status) { /* посылка подтверждения в клавиатуру */ key_byte=inportb(0x61); outportb(0x61,key_byte|0x80); outportb(0x61,key_byte); /* сброс контроллера прерываний */ outportb(0x20,0x20); if (!tsr_run) /* блок.реентерабельного вызова */ hot_key=1; /* установка флага горячей клавиши */ } else (*old_v[1])(); /* системный обработчик */ } else (*old_v[1])(); /* системный обработчик */ } /*------------------------------------------*/ /*==== Обработчик прерывания от таймера ====*/ void interrupt new_8() { (*old_v[0])(); /* cистемный обработчик */ if (hot_key && !(*dos_flag)) /* если нажата горячая клавиша и не установлен флажок занятости DOS... */ if (!disk_op) { /* ...и не выплоняется дисковая операция */ hot_key=0; /* сброс флага горячей клавиши */ act_tsr(); /* активизация */ } } /*--------------------------------------------------*/ /*==== Обработчик прерывания обращения к дискам ====*/ void interrupt new_13(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl; { disk_op++; /* флаг дисковой операции */ (*old_v[2])(); /* системный обработчик int 13 */ /* возврат регистров, установленных сист.обработчиком */ ax=_AX; cx=_CX; dx=_DX; /* обращение к int 2F, которая записывает регистр флагов в static-переменную cflags */ _AX=0x10; new_2F(); fl=cflag; --disk_op; /* сброс флага дисковых операций */ } /*-------------------------------------*/ /*==== Обработчик прерывания DOSOK ====*/ void interrupt new_28() { (*old_v[3])(); /* Системный обработчик */ if (hot_key && *dos_flag) { /* если нажата горячая клавиша и установлен флажок занятости */ hot_key=0; /* сброс флага горячей клавиши */ act_tsr(); /* активизация */ } } /*-------------------------------------------------------*/ /*==== Обработчик прерывания 24 - критические ошибки ====*/ void interrupt new_24 (bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl; { ax=0; } /*------------------------------------------------*/ /*==== Чтение вектора ====*/ void far *get_vector(int in) { rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr); return(MK_FP(sr.es,rr.x.bx)); } /*------------------------------------------------*/ /*==== Запись вектора ====*/ void set_vector(int in, void far *v) { rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(v); rr.x.dx=FP_OFF(v); intdosx(&rr,&rr,&sr); } /*------------------------------------------------*/ /*==== Перехват векторов прерываний ====*/ void set_vectors(void) { /* выделение памяти для команд перехода */ rr.h.ah=0x48; rr.x.bx=2; intdos(&rr,&rr); jmp_segm=rr.x.ax; jmpar=(struct far_jmp far *)MK_FP(jmp_segm,0); for (i=0;i<5;i++) { /* получение старых векторов */ old_v[i]=get_vector(int_nums[i]); /* запись кодов команд FAR JMP */ jmpar[i].jmp=0xea; /* запись адресов наших обработчиков */ jmpar[i].int_h=new_v[i]; /* установка вектора на соответствующий jmp */ set_vector(int_nums[i],(void far *)(jmpar+i)); } /* адрес мультиплексного прерывания запоминается */ a_2F.segm=FP_SEG(old_v[4]); a_2F.offs=FP_OFF(old_v[4]); } /*-----------------------------------------------*/ /*==== Восстановление векторов прерываний ====*/ void restore_vectors(void) { for (i=n=0;i<5;i++) { /* если вектор наш - восстановить его */ if ( get_vector(int_nums[i])== (void far *)(jmpar+i)) set_vector(int_nums[i],old_v[i]); else { /* если нет - запись адреса старого обработчика в команду перехода */ jmpar[i].int_h=old_v[i]; n++; } } /* если не все векторы восстановлены - блок команд перехода помечается принадлежащим DOS */ if (n) poke(jmp_segm-1,1,8); } /*-------------------------------------------------------*/ /*==== Запоминание своего контекста ====*/ void get_context(void) { /* сохранение своего сегмента стека */ tsr_stack.segm=_SS; tsr_stack.offs=_SP-100; /* сохранение адреса своей DTA */ rr.h.ah=0x2f; intdosx(&rr,&rr,&sr); tsr_dta.segm=sr.es; tsr_dta.offs=rr.x.bx; /* сохранение своего PID */ rr.h.ah=0x62; intdos(&rr,&rr); tsr_pid=rr.x.bx; } /*-------------------------------------------------------*/ /*==== Переключение контекстов и активизация TSR-программы ====*/ void act_tsr(void) { tsr_run=1; /* установка флага "TSR работает" */ /*= изменение контекста при активизации TSR-программы =*/ /* переключение на стек TSR-программы */ disable(); old_stack.offs=_SP; old_stack.segm=_SS; _SP=tsr_stack.offs; _SS=tsr_stack.segm; enable(); /* подключение к вектору критических ситуаций */ old_24=get_vector(0x24); set_vector(0x24,new_24); /* переключение статуса обработки Ctrl-Break */ rr.h.ah=0x33; rr.h.al=0; intdos(&rr,&rr); old_ctrl_break=rr.h.dl; rr.h.ah=0x33; rr.h.al=1; rr.h.dl=0; intdos(&rr,&rr); /* переключение на DTA TSR-программы */ rr.h.ah=0x2f; intdosx(&rr,&rr,&sr); old_dta.segm=sr.es; old_dta.offs=rr.x.bx; rr.h.ah=0x1e; sr.ds=tsr_dta.segm; rr.x.dx=tsr_dta.offs; intdosx(&rr,&rr,&sr); /* переключение на PID TSR-программы */ rr.h.ah=0x62; intdos(&rr,&rr); old_pid=rr.x.bx; rr.h.ah=0x50; rr.x.bx=tsr_pid; intdos(&rr,&rr); /*= выполнение "прикладной части" =*/ tsr_exec(); /*= восстановление контекста прерванной программы =*/ /* восстановление PID */ rr.h.ah=0x50; rr.x.bx=old_pid; intdos(&rr,&rr); /* восстановление DTA */ rr.h.ah=0x1e; sr.ds=old_dta.segm; rr.x.dx=old_dta.offs; intdosx(&rr,&rr,&sr); /* восстановление Ctrl-Break */ rr.h.ah=0x33; rr.h.al=1; rr.h.dl=old_ctrl_break; intdos(&rr,&rr); /* восстановление обработчика критических ситуаций */ set_vector(0x24,old_24); /* восстановление стека */ disable(); _SP=old_stack.offs; _SS=old_stack.segm; enable(); tsr_run=0; } /*------------------------------------------*/ /*==== "Прикладная" часть TSR-программы ====*/ void tsr_exec(void) { char *s; int i; /* сохранение экрана и вывод сообщения */ for (i=0, s=tsr_mark; *s; ) { screen[i]=peekb(0xb800,i); pokeb(0xb800,i++,*s++); screen[i]=peekb(0xb800,i); pokeb(0xb800,i++,0x40); } screen[i]=0; /* ожидание нажатия клавиши */ do { rr.h.ah=1; int86(0x16,&rr,&rr); } while (rr.x.flags&0x0040); rr.h.ah=0; int86(0x16,&rr,&rr); /* восстановление экрана */ for(i=0;screen[i];i++) pokeb(0xb800,i,screen[i]); } /*---------------------------------------------------*/ /*==== Обработчик прерывания 0x2F (коммуникации) ====*/ void interrupt new_2F(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl) word bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl; { if ((ax>>8)==com_func) { /* подфункции */ switch (ax&0xff) { case 0: /* проверка возможности установки */ ax|=0xff; /* запрет установки */ es=FP_SEG(mark); bx=FP_OFF(mark); break; case 0x10: /* программный вызов TSR */ act_tsr(); ax=0; break; case 0x20: /* получение регистра флагов */ cflag=fl; break; case 0x30: /* выгрузка */ self_kill(); ax=0; break; default: ax=0xffff; } } else { /* возврат из нашего обработчика в старый обработчик */ for (ast=(word far *)&bp; ast<=(word far *)&flags; ast++) *(ast-3)=*ast; cx=a_2F.offs; bx=a_2F.segm; _SP-=6; } } /*-------------------------------------------------------*/ /*==== Поиск свободных функций 2F или функции, занятой 15_1 ===*/ byte check_tsr(void) { byte a, b; char far *s1; char *s2; com_func=0; for (a=0xff; a>=0xc0; a--) { rr.h.ah=a; rr.h.al=0; int86x(0x2f,&rr,&rr,&sr); b=rr.h.al; /* запоминание первого свободного номера функции */ if (!(b+com_func)) com_func=a; if (b==0xff) /* функция занята */ { s1=(MK_FP(sr.es,rr.x.bx)); for (s2=tsr_mark; *s2; s1++,s2++) if (*s1!=*s2) break; if (*s2) continue; /* занята нами */ com_func=a; return (0); } } return (1); } /*-----------------------------------------------*/ /*==== Операции по уничтожению TSR-программы ====*/ void self_kill(void) { /* восстановление системных векторов прерываний */ restore_vectors(); /* определение начала цепочки MCB */ rr.h.ah=0x52; intdosx(&rr,&rr,&sr); mcb_seg=peek(sr.es,rr.x.bx-2); while (peekb(mcb_seg,0)==0x4d) { /* выборка из MCB признака последний/нет */ if (peek(mcb_seg,1)==tsr_pid) { /* выборка из MCB PID программы-хозяина, если он совпадает с нашим, блок памяти освобождается rr.h.ah=0x49; sr.es=mcb_seg+1; intdosx(&rr,&rr,&sr);*/ poke(mcb_seg,1,0); } /* переход к следующему MCB в цепочке */ mcb_seg+=peek(mcb_seg,3)+1; } }

    Содержание раздела