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

MS-DOS для программиста

© Александр Фролов, Григорий Фролов
Том 18, М.: Диалог-МИФИ, 1995, 254 стр.

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

5.3. Установка обработчиков прерываний

Последнее, что делает функция tsrinit перед возвращением управления - встраивание обработчиков некоторых прерываний. При этом для обеспечения возможности выгрузки резидентной программы из памяти сохраняются старые значения изменяемых векторов прерываний.

Перед тем как приступить к изменению векторов прерываний, функция tsrinit запрещает аппаратные прерывания, вызывая функцию _disable . После того как изменение векторов прерываний завершено, вызывается функция _enable , разрешающая обработку аппаратных прерываний:

_disable();
old_int1c = _dos_getvect (0x1C);
old_int2f = _dos_getvect (0x2F);
old_int8  = _dos_getvect (8);
old_int9  = _dos_getvect (9);
old_int13 = _dos_getvect (0x13);
old_int28 = _dos_getvect (0x28);
get_int_13();

_dos_setvect (0x1C, (fptr)new_int1c);
_dos_setvect (0x2F, (fptr)new_int2f);
_dos_setvect (8,    (fptr)new_int8);
_dos_setvect (9,    (fptr)new_int9);
_dos_setvect (0x13, (fptr)new_int13);
_dos_setvect (0x28, (fptr)new_int28);
_enable();

Обработчики всех прерываний, за исключением прерывания INT 13h, составлены на языке С. Так как прерывание INT 13h возвращает результат выполнения операций в регистре флагов, соответствующий обработчик удобнее составить на языке ассемблера.

Расскажем подробно о том, какие вектора прерываний мы переопределяем и зачем.

Прерывание INT 1Ch

Прерывание INT 1Ch является программным и вызывается обработчиком аппаратного прерывания от таймера INT 08h приблизительно 18,2 раза в секунду.

Наша программа использует это прерывание, для того чтобы выводить в правом верхнем углу экрана мигающие символы "*" и "+". Такое мигание нужно только для индикации активности резидентной программы. Если индикация не требуется, вы можете не изменять соответствующий вектор прерывания и не предусматривать обработчик для этого прерывания.

Наш обработчик прерывания INT 1Ch называется new_int1c и имеет следующий вид:

void interrupt new_int1c()
{
  s_arrayptr screen[80]; // видеопамять
  static int count;      // счетчик

  screen[0] = (s_arrayptr) MK_FP (0xB800,0);
  count++; count %= 6;
  screen[0][79] =
    ((count > 2) ? '*' : '+') + ATTR;

  _chain_intr (old_int1c);
}

Массив screen содержит дальние указатели на массив целых чисел, определенные следующим образом:

typedef unsigned int (far *s_arrayptr);

В первый элемент этого массива мы записываем указатель на область видеопамяти , соответствующую самой верхней строке экрана. Для цветного видеоконтроллера этот адрес равен B800h:0000h.

Для того чтобы символы не мигали слишком часто, обработчик прерывания INT 1Ch подсчитывает прерывания в статическом счетчике count. Если значение этого счетчика больше 2, выводится символ '*", если меньше - символ "+".

Перед тем как возвратить управление, функция new_int1c вызывает старый обработчик прерывания INT 1Ch , адрес которого хранится в переменной old_int1c. Для этого используется функция _chain_intr .

Прерывание INT 2Fh

Обработчик прерывания INT 2Fh , который встраивает наша программа, выполняет две функции.

Во-первых, этот обработчик используется для предотвращения повторной загрузки резидентной программы в память.

Во-вторых, он нужен для выгрузки резидентной программы из памяти.

Исходный текст обработчика прерывания INT 2Fh представлен ниже:

void interrupt far new_int2f(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  if(ax == 0xff00) ax = 0x00ff;
  else if(ax == 0xff01)
  {
    ExitAddress = ((long)bx << 16) + dx;
    if(!tsr_already_active)
    {
      _enable();
      tsr_exit();

      ax = 0xFFFF;
      tsr_already_active = -ax;
    }
  }
  else _chain_intr (old_int2f);
}

Вызов прерывания INT 2Fh происходит при запуске нашей программы, а также и при запуске других резидентных программ. Регистр AH при этом должен содержать идентификатор резидентной программы. Для программы TSRDEMO мы выбрали идентификатор FFh, хотя никто не сможет гарантировать, что такой идентификатор уже не используется другой программой. Для надежности вы можете указать дополнительный идентификатор, например, в виде текстовой строки, адрес которой передается через другие регистры. Мы не стали этого делать для сокращения объема листинга программы.

Итак, если при вызове прерывания INT 2Fh регистр AH содержит значение FFh, наш обработчик прерывания считает, что вызов выполняет программа TSRDEMO.

В этом случае он дополнительно проверяет содержимое регистра AL, в котором находится код выполняемой функции.

Если этот код равен 0, прерывание INT 2Fh вызвано для проверки наличия программы TSRDEMO в памяти. В ответ обработчик возвращает в регистре AX значение 00FFh. Это и есть признак того, что программа TSRDEMO уже загружена в память и ее повторная загрузка невозможна.

Если же код равен 1, программа TSRDEMO была запущена с параметром u для выгрузки своей копии из памяти.

В этом случае обработчик прерывания INT 2Fh сохраняет в глобальной переменной ExitAddress адрес завершения, переданный обработчику в регистрах BX:DX. Затем проверяется флаг tsr_already_active, который установлен, если резидентная программа активна и ее в данный момент нельзя выгружать из памяти.

Далее обработчик разрешает аппаратные прерывания, которые были запрещены перед вызовом прерывания INT 2Fh для выгрузки, и предпринимает попытку выгрузить резидентную программу, вызывая функцию tsr_exit.

Заметим, что после выгрузки резидентной программы функция tsr_exit передает управление MS-DOS, а не возвращает его обратно в программу.

Попытка выгрузки программы может окончиться неудачно, если пользователь загрузил после программы TSRDEMO другую резидентную программу, изменившую вектора прерываний, для которых программа TSRDEMO установила свои обработчики. В этом случае наша программа "не знает", как восстановить вектора прерываний и не выгружается из памяти. Тем не менее, она блокирует свою работу, записывая в переменную tsr_already_active значение -FFFFh.

Подробнее процесс выгрузки резидентной программы будет рассмотрен позже.

Заметим, что если содержимое регистра AX при вызове прерывания INT 2Fh не равно FF00h или FF01h, наш обработчик прерывания передает управление по цепочке, вызывая для этого функцию _chain_intr .

Прерывание INT 09h

Аппаратное прерывание от клавиатуры INT 09h обрабатывается для того чтобы обнаружить комбинацию клавиш, предназначенную для активизации резидентной программы:

void interrupt far new_int9(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  if(tsr_already_active) _chain_intr (old_int9);

  keycode = inp(KEYBOARD_PORT);
  if((_bios_keybrd(_KEYBRD_SHIFTSTATUS) & ShiftKey) != ShiftKey)
    _chain_intr (old_int9);

  if(!(keycode == HotKeyRecording))
    _chain_intr (old_int9);

  popup_while_dos_busy = 1;

  asm cli
  asm in al, 61h
  asm mov ah, al
  asm or al, 80h
  asm out 61h, al
  asm xchg ah, al
  asm out 61h, al
  asm mov al, 20h
  asm out 20h, al
  asm sti
}

Когда программа уже активна, установлен флаг tsr_already_active. В этом случае наш обработчик прерывания передает управление по цепочке, вызывая старый обработчик.

Если программа TSRDEMO не активна, наш обработчик прерывания вводит код нажатой клавиши из порта клавиатуры и проверяет, была ли нажата клавиша <Ctrl>. Затем проверяется код нажатой клавиши. В том случае, если пользователь нажал комбинацию клавиш <Ctrl+R>, нужно активизировать резидентную программу.

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

Активизация резидентной программы будет выполнена позднее, при обработке аппаратного прерывания INT 08h или прерывания INT 28h .

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

Прерывание INT 08h

Активизация резидентной программы TSRDEMO не выполняется сразу после того, как пользователь нажал комбинацию клавиш <Ctrl+R>. Для активизации необходимо выбрать подходящий момент, когда прерываемая программа не вызывает функцию MS-DOS или прерывание BIOS , предназначенное для работы с диском.

Наша программа использует несколько возможностей для своей активизации. В частности, обработчик аппаратного прерывания таймера INT 08h периодически проверяет возможность активизации:

void interrupt far new_int8(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  if(!tsr_already_active   &&
      popup_while_dos_busy &&
     !DosBusy() && !unsafe_flag)
  {
    popup_while_dos_busy = 0;
    tsr_already_active = 1;
   (*old_int8)();
    _enable(); // разрешаем прерывания

    activate_tsr(); // активизируем TSR
    tsr_already_active = 0;
  }
  else (*old_int8)();
}

Прежде всего, проверяется флаг tsr_already_active. Этот флаг устанавливается, когда резидентная программа уже активизирована. Проверка флага активизации позволяет избежать повторной активизации, которая может привести к катастрофе.

Активизация не выполняется также и в том случае, если установлен флаг InDos или unsafe_flag (последний устанавливается, если вызван обработчик прерывания INT 13h ).

Флаг popup_while_dos_busy установлен в том случае, когда был запрос на активизацию. Он устанавливается обработчиком аппаратного прерывания от клавиатуры INT 08h . Если запроса на активизацию нет, этот флаг не установлен и, следовательно, активизацию выполнять не нужно.

Если активизация возможна, сбрасываются флаги popup_while_dos_busy и устанавливается флаг tsr_already_active. Затем вызывается старый обработчик прерывания INT 08h и разрешаются прерывания.

Далее вызывается функция activate_tsr, которая выполняет все действия, необходимые для активизации. Она будет описана отдельно. После возвращения управления из этой функции флаг tsr_already_active сбрасывается.

В том случае, когда активизация резидентной программы невозможна, наш обработчик прерывания INT 08h вызывает старый обработчик и возвращает управление прерванной программе.

Прерывание INT 28h

Прерывание INT 28h вызывается MS-DOS, когда она ожидает ввод данных от клавиатуры или, иными словами, ничем особенным не занята. Поэтому обработчик прерывания INT 28h может активизировать резидентную программу, если на это есть запрос от пользователя.

Приведем исходный текст обработчика прерывания INT 28h :

void interrupt far new_int28(
  unsigned bp, unsigned di, unsigned si,
  unsigned ds, unsigned es, unsigned dx,
  unsigned cx, unsigned bx, unsigned ax)
{
  int_28_in_progress++;

  if(popup_while_dos_busy && (!Int28DosBusy())
     && !tsr_already_active && !unsafe_flag)
  {
    tsr_already_active = 1;
    activate_tsr(); // активизируем TSR
    tsr_already_active = 0;
  }
  int_28_in_progress--;
  _chain_intr (old_int28);
}

Обработчик прерывания INT 28h содержит счетчик рекурсивных вызовов int_28_in_progress, который анализируется при активизации резидентной программы функцией activate_tsr.

Если есть запрос на активизацию, проверяются флаги tsr_already_active и unsafe_flag. Дополнительно проверяется, не выполняется ли попытка активизировать резидентную программу во время обработки прерывания MS-DOS, отличного от INT 28h . Для этого вызывается функция Int28DosBusy.

Обратите внимание на различие в способе проверки возможности активизации при обработке прерывания INT 08h и INT 28h .

В первом случае вызывается функция DosBusy:

int DosBusy(void)
{
  if(indos_ptr && crit_err_ptr)
    return(*crit_err_ptr || *indos_ptr);
  else
    return 0xFFFF;
}

Эта функция проверяет содержимое флагов InDos и флага обработки критической ошибки. Если MS-DOS не выполняет обработку критической ошибки и если прерываемая программа не вызывает функцию MS-DOS, можно выполнять активизацию. В этом случае функция DosBusy возвращает значение 0.

Однако при вызове прерывания INT 28h флаг InDos установлен всегда, так как указанное прерывание - это тоже прерывание MS-DOS. В данном случае для проверки возможности активизации используется другая функция, которая называется Int28DosBusy.

Эта функция допускает однократный (не рекурсивный) вызов функции INT 28h , проверяя значение, которое записывается в байт памяти, отведенный для флага InDos:

int Int28DosBusy(void)
{
  if(indos_ptr && crit_err_ptr)
    return (*crit_err_ptr || (*indos_ptr > 1));
  else
    return 0xFFFF;
}

При рекурсивных вызовах функций MS-DOS значение, хранящееся в этом байте, увеличивается на 1. Активизация резидентной программы допускается только в том случае, когда в этом байте находится значение 1, т. е. когда нет рекурсии.

Обработчик прерывания INT 28h активизирует резидентную программу точно таким же способом, что и обработчик прерывания INT 08h , а именно, вызывая специально предназначенную для активизации функцию activate_tsr, определенную в нашей программе.

Прерывание INT 13h

Обработчик прерывания INT 13h составлен на языке ассемблера. Его единственное назначение - увеличение значения флага unsafe_flag при каждом вызове прерывания INT 13h и уменьшение при возврате из этого прерывания. Когда программа TSRDEMO будет предпринимать попытку активизации во время обработки прерывания INT 13h, она проверит значение флага unsafe_flag. Если оно не будет равно 0, активизация невозможна, так как в данный момент времени выполняется обработка прерывания INT 13h.

Исходный текст обработчика прерывания INT 13h представлен ниже:

_new_int13 proc far
  push    ax
  push    ds

  mov ax, DGROUP
  mov ds, ax
  inc word ptr _unsafe_flag
  pop ds
  pop ax

  pushf
  call    cs:old_int13

  push    ax
  push    ds
  mov ax, DGROUP
  mov ds, ax
  dec word ptr _unsafe_flag
  pop ds
  pop ax

  ret 2
_new_int13 endp

Для обеспечения возможности адресации глобальной переменной unsafe_flag регистр DS устанавливается на сегмент данных программы TSRDEMO.

Адрес старого обработчика прерывания INT 13h находится в переменной old_int13, куда он записывается функцией get_int_13. Эта функция вызывается из функции tsrinit. Ее исходный текст вы найдете в листинге 5.3 (см. ниже).

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