Операционная система 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-программы способ организовать обслуживание нестандартной аппаратуры. |

