Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система MS-DOS

© Александр Фролов, Григорий Фролов
Том 1, книги 1-2, М.: Диалог-МИФИ, 1991.

[Назад] [Содеожание] [Дальше]

5.5. Примеры резидентных программ

Приведем несколько примеров TSR-программ.

Первая программа перехватывает прерывание 9 (аппаратное прерывание клавиатуры). Запустив эту программу из приглашения DOS, вы сможете убедиться в том, что прерывание от клавиатуры возникает не только тогда, когда вы нажимаете на клавишу, но и когда ее отпускаете.

#include <dos.h>
#include <stdio.h>
#include <stdlib.h>

// Выключаем проверку стека и указателей
#pragma check_stack( off )
#pragma check_pointer( off )

// Макро для подачи звукового сигнала
#define BEEP() _asm { \
                       _asm xor  bx, bx    \
                       _asm mov  ax, 0E07h \
                       _asm int  10h       \
                    }

// Указатель на старую функцию обработки
//   9-го прерывания
void (_interrupt _far *oldkey)( void );

// Объявление новой функции обработки
//   9-го прерывания
void _interrupt _far newkey( void );

void main(void);
void main(void) {

         unsigned size; // Размер резидентной части
                        // TSR-программы

         char _far *newstack; // Указатель на новый стек,
                              // который будет использовать
                              // TSR-программа

         char _far *tsrbottm; // Указатель на конец
                              // TSR-программы, используется
                              // для определения размера
                              // резидентной части

         // Записываем адрес стека TSR-программы
         _asm mov  WORD PTR newstack[0], sp
         _asm mov  WORD PTR newstack[2], ss

         FP_SEG(tsrbottm) = _psp; // Указатель конца
         FP_OFF(tsrbottm) = 0;    // программы устанавливаем
                                  // на начало PSP

         // Вычисляем размер программы в параграфах
         // Добавляем 1 параграф на случай
         // некратной параграфу длины
         size = ((newstack - tsrbottm) >> 4) + 1;

         // Встраиваем свой обработчик прерывания 9,
         // запоминаем старый вектор прерывания 9
         oldkey = _dos_getvect(0x9);
         _dos_setvect(0x9, newkey);

         // Завершаем программу и остаемся в памяти
         _dos_keep(0, size);
}

// Новый обработчик клавиатурного прерывания
void _interrupt _far newkey() {
        BEEP(); // Выдаем звуковой сигнал

        // Вызываем стандартный обработчик прерывания 9
        _chain_intr( oldkey );
}




Следующая программа GRAB демонстрирует использование функций DOS в TSR-программах. Она содержит все элементы, необходимые "безопасным" резидентным программам.

Программа предназначена для копирования содержимого видеобуфера в файл. Запись в файл активизируется при нажатии комбинации клавиш Ctrl+PrtSc. После каждой записи имя файла изменяется.

В самом начале своей работы программа проверяет наличие своей копии в памяти, так как повторное переназначение векторов прерываний приведет систему к краху. Некоторые тонкости, связанные с программированием клавиатуры и видеоадаптера, с получением адреса видеобуфера, используются в программе без объяснения. Вся необходимая информация будет приведена позже, в главах, посвященных программированию клавиатуры и видеоадаптеров.

Итак, текст программы:

#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include "sysp.h"

// Выключаем проверку стека и указателей
#pragma check_stack( off )
#pragma check_pointer( off )

// Макро для подачи звукового сигнала
#define BEEP() _asm { \
                       _asm xor  bx, bx    \
                       _asm mov  ax, 0E07h \
                       _asm int  10h       \
                    }
// Указатели на старые обработчики прерываний
void (_interrupt _far *old8)(void);  // Таймер
void (_interrupt _far *old9)(void);  // Клавиатура
void (_interrupt _far *old28)(void); // Занятость DOS
void (_interrupt _far *old2f)(void); // Мультиплексор

// Новые обработчики прерываний
void _interrupt _far new8(void);
void _interrupt _far new9(void);
void _interrupt _far new28(void);
void _interrupt _far new2f(unsigned _es, unsigned _ds,
                           unsigned _di, unsigned _si,
                           unsigned _bp, unsigned _sp,
                           unsigned _bx, unsigned _dx,
                           unsigned _cx, unsigned _ax,
                           unsigned _ip, unsigned _cs,
                           unsigned flags);

int iniflag; // Флаг запроса на вывод экрана в файл
int outflag; // Флаг начала вывода в файл
int name_counter; // Номер текущего выводимого файла
char _far *crit;  // Адрес флага критической секции DOS

// =======================================
void main(void);
void main(void) {
         union REGS inregs, outregs;
         struct SREGS segregs;

         unsigned size; // Размер резидентной части
                        // TSR-программы

        // Вызываем прерывание мультиплексора с AX = FF00
        // Если программа GRAB уже запускалась, то новый
        // обработчик прерывания мультиплексора вернет
        // в регистре AX значение 00FF.
        // Таким способом мы избегаем повторного изменения
        // содержимого векторной таблицы прерываний.
        inregs.x.ax = 0xff00;
        int86(0x2f, &inregs, &outregs);

        if(outregs.x.ax == 0x00ff) {
                printf("\nПрограмма GRAB уже загружена\n");
                hello();
                exit(-1);
        }

        // Выдаем инструкцию по работе с программой GRAB
        hello();

        // Вычисляем размер программы в параграфах
        // Добавляем 1 параграф на случай
        // некратной параграфу длины
        size = (12000 >> 4) + 1;

        // Устанавливаем начальные значения флагов
        outflag=iniflag=0;

        // Сбрасываем счетчик файлов. Первый файл будет
        // иметь имя GRAB0.DOC. В дальнейшем этот счетчик
        // будет увеличивать свое значение на 1.
        name_counter=0;

        // Получаем указатель на флаг критической секции DOS.
        // Когда этот флаг равен 0, TSR-программа может
        // пользоваться функциями DOS
        inregs.h.ah = 0x34;
        intdosx( &inregs, &outregs, &segregs );
        crit=(char _far *)FP_MAKE(segregs.es,outregs.x.bx);

        // Устанавливаем собственные обработчики прерываний.
        old9 = _dos_getvect(0x9);
        _dos_setvect(0x9, new9);

        old8 = _dos_getvect(0x8);
        _dos_setvect(0x8, new8);

        old28 = _dos_getvect(0x28);
        _dos_setvect(0x28, new28);

        old2f = _dos_getvect(0x2f);
        _dos_setvect(0x2f, new2f);

        // Завершаем программу и остаемся в памяти
        _dos_keep(0, size);
}

// =======================================
// Новый обработчик прерывания мультиплексора.
// Используется для предохранения программы
// от повторного встраивания в систему как резидентной.

void _interrupt _far new2f(unsigned _es, unsigned _ds,
                           unsigned _di, unsigned _si,
                           unsigned _bp, unsigned _sp,
                           unsigned _bx, unsigned _dx,
                           unsigned _cx, unsigned _ax,
                           unsigned _ip, unsigned _cs,
                           unsigned flags)
{
                // Если прерывание вызвано с содержимым
                // регистра AX, равным FF00, возвращаем
                // в регистре AX значение 00FF,
                // в противном случае передаем управление
                // старому обработчику прерывания

                if(_ax != 0xff00) _chain_intr(old2f);
                else _ax = 0x00ff;
}

// =======================================
// Новый обработчик аппаратного прерывания таймера

void _interrupt _far new8(void) {

        // Вызываем старый обработчик
        (*old8)();

        // Если была нажата комбинация клавиш Ctrl+PrtSc
        // (iniflag при этом устанавливается в 1
        // новым обработчиком прерывания 9) и
        // если запись в файл уже не началась,
        // то при значении флага критической секции
        // DOS, равном 0, выводим содержимое экрана
        // в файл

        if((iniflag != 0) && (outflag == 0) && *crit == 0) {

                outflag=1;   // Устанавливаем флаг начала вывода
                _enable();   // Разрешаем прерывания

                write_buf(); // Записываем содержимое
                             // буфера экрана в файл

                outflag=0;   // Сбрасываем флаги в исходное
                iniflag=0;   //    состояние
        }
}

// =======================================
// Новый обработчик прерывания 28h, которое вызывает
// DOS, если она ожидает ввода от клавиатуры.
// В этот момент TSR-программа может пользоваться
// функциями DOS.

void _interrupt _far new28(void) {

        // Если была нажата комбинация клавиш Ctrl+PrtSc
        // (iniflag при этом устанавливается в 1
        // новым обработчиком прерывания 9) и
        // если уже не началась запись в файл,
        // то выводим содержимое экрана в файл

        if((iniflag != 0) && (outflag == 0)) {

                outflag=1; // Устанавливаем флаг начала вывода
                _enable(); // Разрешаем прерывания

                write_buf(); // Записываем содержимое видеобуфера
                                   // в файл

                outflag=0;   // Сбрасываем флаги в исходное
                iniflag=0;   //   состояние
        }

        // Передаем управление старому обработчику
        // прерывания 28
        _chain_intr(old28);
}

// =======================================
// Новый обработчик клавиатурного прерывания.
// Он фиксирует нажатие комбинации клавиш Ctrl+PrtSc
// и устанавливает флаг iniflag, который сигнализирует
// о необходимости выбрать подходящий момент и
// записать содержимое видеобуфера в файл

void _interrupt _far new9(void) {

 // Если SCAN-код равен 0x37 (клавиша PrtSc),
 // нажата клавиша Ctrl (бит 4 байта состояния
 // клавиатуры, находящийся в области данных
 // BIOS по адресу 0040:0017 установлен в 1)
 // и если не установлен флаг iniflag,
 // то устанавливаем флаг iniflag в 1.

 if((inp(0x60) == 0x37) && (iniflag == 0) &&
        (*(char _far *)FP_MAKE(0x40,0x17) & 4) != 0) {

                // Выдаем звуковой сигнал
                BEEP();
                BEEP();
                BEEP();

                _disable(); // Запрещаем прерывания

                // Разблокируем клавиатуру
                // и разрешим прерывания
                _asm {
                        in  al,61h
                        mov ah,al
                        or  al,80h
                        out 61h,al
                        xchg ah,al
                        out 61h,al

                        mov al,20h
                        out 20h,al
                }

                // Устанавливаем флаг запроса
                // на запись содержимого видеобуфера
                // в файл
                iniflag = 1;
                _enable();  // Разрешаем прерывания
 }

 // Если нажали не Ctrl+PrtSc, то
 // передаем управление старому
 // обработчику прерывания 9
 else _chain_intr(old9);

}

// =======================================
// Функция возвращает номер
// текущего видеорежима

int get_vmode(void) {
        char _far *ptr;
        ptr = FP_MAKE(0x40,0x49); // Указатель на байт
                                  // текущего видеорежима
        return(*ptr);
}

// =======================================
// Функция возвращает сегментный адрес
// видеобуфера. Учитывается содержимое
// регистров смещения адреса видеобуфера.

int get_vbuf(int vmode) {
        unsigned vbase;
        unsigned adr_6845;
        unsigned high;
        unsigned low;
        unsigned offs;

        // В зависимости от видеорежима базовый адрес
        // видеобуфера может быть 0xb000 или 0xb800
        vbase = (vmode == 7) ? 0xb000 : 0xb800;

        // получаем адрес порта видеоконтроллера 6845
        adr_6845 = *(unsigned _far *)(FP_MAKE(0x40,0x63));

        // Считываем содержимое регистров 12 и 13
        // видеоконтроллера
        outp(adr_6845,0xc);
        high = inp(adr_6845+1);

        outp(adr_6845,0xd);
        low = inp(adr_6845+1);

        offs = ((high << 8) + low) >> 4;

        // Добавляем к базовому адресу видеобуфера
        // смещение, взятое из регистров видеоконтроллера
        vbase += offs;

        return(vbase);
}

// =======================================
// Функция возвращает количество символов в строке
// для текущего видеорежима

int get_column(void) {
        return(*(int _far *)(FP_MAKE(0x40,0x4a)));
}

// =======================================
// Функция возвращает количество строк
// для текущего видеорежима

int get_row(void) {
        unsigned char ega_info;
        ega_info = *(unsigned char _far *)(FP_MAKE(0x40,0x87));

        // Если нет EGA, то используется 25 строк,
        // если EGA присутствует, считываем число
        // строк. Это число находится в области данных
        // BIOS по адресу 0040:0084.

        if(ega_info == 0 || ( (ega_info & 8) != 0) ) {
                return(25);
        }
        else {
                return(*(unsigned char _far *)
                         (FP_MAKE(0x40,0x84)) + 1);
        }
}

// =======================================
// Функция записи содержимого видеобуфера в
// файл

int write_buf(void) {

// Видеопамять состоит из байтов символов и байтов
// атрибутов. Нам нужны байты символов chr.
typedef struct _VIDEOBUF_ {
         unsigned char chr;
         unsigned char attr;
} VIDEOBUF;

        VIDEOBUF _far *vbuf;
        int i, j, k, max_col, max_row;
        FILE *out_file;
        char fname[20],ext[8];

        i=get_vmode(); // Получаем номер текущего
                                // видеорежима

        // Для графического режима ничего не записываем
        if(i > 3 && i != 7) return(-1);

        // Устанавливаем указатель vbuf на видеобуфер
        vbuf=(VIDEOBUF _far *)FP_MAKE(get_vbuf(i),0);

        // Определяем размеры экрана
        max_col = get_column();
        max_row = get_row();

        // Формируем имя файла для записи образа экрана
        itoa(name_counter++,ext,10);
        strcpy(fname,"!grab");
        strcat(fname,ext);
        strcat(fname,".doc");

        out_file=fopen(fname,"wb+");

        // Записываем содержимое видеобуфера в файл
        for(i=0; i<max_row; i++) {
                for(j=0; j<max_col; j++) {

                        fputc(vbuf->chr,out_file);
                        vbuf++;

                }

                // В конце каждой строки добавляем
                // символы перевода строки и
                // возврата каретки
                fputc(0xd,out_file);
                fputc(0xa,out_file);
        }
        fclose(out_file);
        return(0);
}

// =======================================
// Функция выводит на экран инструкцию по
// использованию программы GRAB

int hello(void) {
        printf("\nУтилита копирования содержимого"
               "\nэкрана в файл GRAB<n>.DOC"
               "\nCopyright (C)Frolov A.,1990"
               "\n"
               "\nДля копирования нажмите Ctrl+PrtSc"
               "\n");
}


Приведем пример TSR-программы, написанной на языке ассемблера. Эта программа переназначает прерывание 13h, которое используется для работы с дисками. Она позволяет организовать защиту диска от записи. При первом запуске программа включает защиту, при втором выключает, потом опять включает и так далее. В качестве флага - признака включения или выключения защиты, используется компонента смещения вектора прерывания F0h, зарезервированного для интерпретатора BASIC.

                .MODEL tiny
                .CODE
                .STARTUP
 
                jmp begin

old_int13h_off  dw 0  ; Адрес старого обработчика
old_int13h_seg  dw 0  ; прерывания 13h

old_int2Fh_off  dw 0  ; Адрес старого обработчика
old_int2Fh_seg  dw 0  ; прерывания 2Fh

; Новый обработчик прерывания 2Fh нужен
; для проверки наличия программы в памяти
; при ее запуске для предохранения
; от повторного запуска

new_int2Fh  proc  far
                cmp   ax,0FF00h
                jz    installed

                jmp   dword ptr cs:old_int2Fh_off

; Если код функции 0FF00h, то возвращаем
; в регистре AX значение 00FFh. Это признак
; того, что программа уже загружена в память

installed:
                mov   ax,00FFh
                iret

new_int2Fh  endp

; Новый обработчик прерывания 13h. Для команд записи
; на жесткий диск выполняет проверку содержимого
; компоненты смещения вектора прерывания FFh.
; Эта ячейка служит для триггерного переключения
; режима работы прерывания 13h - включения/выключения
; защиты записи.

new_int13h  proc  far
                cmp   ah,3 ; запись сектора
                je protect
                cmp   ah,5 ; форматирование трека
                je protect
                jmp   dword ptr cs:old_int13h_off

old_int13h:
                pop   es
                pop   bx
                pop   ax

                jmp   dword ptr cs:old_int13h_off

protect:
                push  ax
                push  bx
                push  es

; Проверяем значение триггерного флага защиты
                xor   ax,ax
                mov   es,ax
                mov   bx,0F0h*4
                mov   ax,WORD PTR es:[bx]
                cmp   ax,0FFFFh

                jne   old_int13h

; Для флоппи-дисков защиту не включаем

                cmp   dl,0
                je old_int13h
                cmp     dl,1
                je old_int13h

                pop   es
                pop   bx
                pop   ax

; Имитируем ошибку записи при попытке
; записать данные на защищенный от записи диск

                mov   ah,3
                stc
                ret 2

new_int13h  endp
  
;==============================
; Точка входа в программу

begin proc  far

; Проверяем, не загружена ли уже программа
; в память

                mov   ax,0FF00h
                int   2Fh

                cmp   ax,00FFh
                jne   first_start

                jmp   invert_protect_flag

; Первоначальный запуск программы

first_start:

; Устанавливаем триггерный флаг защиты записи
; в состояние, соответствующее включенной защите

                xor   ax,ax
                mov   es,ax
                mov   bx,0F0h*4
                mov   WORD PTR es:[bx],0FFFFh

; Запоминаем адрес старого обработчика прерывания 13h

                mov   ax,3513h
                int   21h
                mov   cs:old_int13h_off,bx
                mov   cs:old_int13h_seg,es

; Запоминаем адрес старого обработчика прерывания 2Fh

                mov   ax,352Fh
                int   21h
                mov   cs:old_int2Fh_off,bx
                mov   cs:old_int2Fh_seg,es

                push  cs
                pop      ds

; Выводим сообщение о включении защиты

                mov   dx,offset msg_on
                mov      ah,9
                int   21h

; Устанавливаем новые обработчики прерываний 13h и 2Fh

                mov   dx,OFFSET new_int13h
                mov      ax,2513h
                int   21h

                mov   dx,OFFSET new_int2Fh
                mov   ax,252Fh
                int   21h

; Завершаем программу и оставляем резидентно
; в памяти часть программы, содержащую новые
; обработчики прерываний

                mov   dx,OFFSET begin
                int   27h

; Если это не первый запуск программы,
; инвертируем содержимое триггерного флага защиты

invert_protect_flag:

                xor   ax,ax
                mov   es,ax
                mov   bx,0F0h*4

                mov   ax,WORD PTR es:[bx]
                not   ax
                mov   WORD PTR es:[bx],ax
                mov   cx,ax

                cmp   cx,0FFFFh
                je prot_on

; Выводим сообщение о выключении защиты

                mov   dx,OFFSET msg_off
                jmp   short send_msg
prot_on:
; Выводим сообщение о включении защиты
                mov   dx,OFFSET msg_on
send_msg:
                mov     ah,9
                push    cs
                pop     ds
                int   21h

                .EXIT
begin endp

msg_on      db 'Защита диска включена$'
msg_off     db 'Защита диска ВЫКЛЮЧЕНА$'
                end


Этим примером мы завершим обзор TSR-программ. В следующей главе будет описан другой вид резидентных программ - драйверы. Использование драйвера - более предпочтительный, чем TSR-программы способ организовать обслуживание нестандартной аппаратуры.

[Назад] [Содеожание] [Дальше]