Этот адрес также используется при дальнейшем переключении
контекстов. Адрес идентификатора процесса (PID) восстанавливается
и сохраняется. Эта техника объяснялась в главе 11. Векторы
прерывания для таймера, клавиатуры, диска и DOSOK читаются и
запоминаются для связывания прерываний в цепочки, и собственные
обработчики присоединяются к этим прерываниям. Указатель стека
TSR-программы вычисляется так, чтобы быть на 300 байт меньше, чем
размер программы, и вектор обработки деления на ноль
восстанавливается на то значение, которое он имел до того , как
стартовый код присоединил его. Затем TSR-функция ДОС используется
для завершения программы, оставляя ее резидентной.
Обработчик обращения к диску.
-----------------------------------------------------------------
В то время, как компьютер выполняет программу, и программа
вызывает ДОС, ДОС использует дисковое прерывание BIOS (0х13) для
чтения и записи секторов данных. Напомним, что в главе 11 вас уже
предупреждали о том, что дисковые операции прерывать нельзя.
Обработчику диска в resident.c дано имя newdisk, и он защищает
дисковые операции от прерывания вашей TSR-программой. При
возникновении прерывания 0х13 устанавливается флаг, а после этого
управление передается на обработку дисковых операций. После
окончания обработки флаг очищается. Если этот флаг установлен,
когда программа вызывается по нажатию "горячего ключа", то
производится задержка выполнения до очистки этого флага.
Обратите внимание на трюк в newdisk. Функция, описанная как
interrupt, сохраняет все регистры в стеке, устанавливает регистр
ds на сегмент данных программы, содержащей эту функцию. Затем
начинается выполнение кода функции. При завершении функции
регистры восстанавливаются, и выполняется машинная команда
IRET. Эта команда восстанавливает регистры программного счетчика,
сегмента кода и флажков. Таким образом, выполняется полное
восстановление регистров прерванной программы.
Все работает до тех пор, пока вы не имеете прерывания,
которое возвращает условие в флаге переноса. Команда IRET
восстанавливает все флаги в состояние, которое было при
возникновении прерывания. Некоторые программные прерывания ДОС и
BIOS используют этот флаг для индикации результата. В их числе и
прерывание 0х13 BIOS; однако программа обработчика обращений к
диску сохраняет значение флажка переноса. Когда заканчивает свои
действия присоединенный обработчик olddisk, необходимо вернуть
вызывавшему процессу два значения: регистр ax и флажок переноса.
Значение ax берется из псевдопеременной _AX и помещается в целую
переменную ax, являющуюся одним из параметров функции прерывания.
Турбо Си использует это средство, чтобы изменять значения
регистров, которые будут восстановлены из стека при возврате из
функции. Для регистра флагов псевдопеременной нет, и, чтобы
избежать программирования на ассемблере, делается следующий
хэккерский трюк: за функцией newdisk может быть сразу же вызвана
функция newcrit, и она запишет флаговый регистр, сохраненный в
стеке при ее вызове, во внешнюю переменную cflag. При возврате из
newcrit cflag записывается в стек, где было сохранено прошлое
значение регистра флагов.При возврате из функции результирующие
флаги из olddisk будут восстановлены в регистр флагов.
Эти действия базируются на понимании того, как Турбо Си
использует регистры и производится вызов и возврат из функций
interrupt. Таким образом, эти программы не переносимы на другой
компилятор, и могут быть несовместимы даже с будущими версиями
Турбо Си, если Borland изменит свои соглашения. Программисты на
ассемблере, возможно, скажут, что подобные дествия можно было бы
легко проделать на их любимом языке. Эта критика верна, но этот
программный трюк является примером, как достигнуть пределов
возможностей Турбо Си. Автор благодарен Турбо Си за тот сервис,
который предоставляется для разрешения всех проблем, возникающих
при создании TSR-программ.
Обработчик критических ситуаций.
-----------------------------------------------------------------
Функция interrupt с именем newcrit является присоединенным
обработчиком критических ситуаций. Она не присоединяется к
прерыванию, когда программа объявляет себя резидентной, а делает
это лишь временно, при "всплытии" TSR-программы. Его задача -
обезопасить TSR-программу от возникновения критических ошибок в
то время, как она переключила контекст на себя. Обработчик
возвращает ноль в регистре ax, что означает для ДОС игнорировать
ошибку.
Обработчик клавиатуры.
-----------------------------------------------------------------
Функция interrupt с именем newkb является обработчиком
клавиатуры для TSR-программы. Она читает порт данных клавиатуры и
проверяет на соответствие определенному скан-коду, означающему
нажатие клавиши "горячего ключа". При равенстве и если
TSR-программа не приостановлена, "горячий ключ" активизируется.
Функция сбрасывает клавиатуру, чтобы не было будущих прерываний,
и затем проверяет, а не вызвана ли уже TSR-программа. Если нет,
то устанавливается флаг, означающий, что нажат "горячий ключ", и
выполнение функции заканчивается.
Если скан-код и маска статуса не равны "горячему ключу", то
управление передается старой программе обработки прерываний от
клавиатуры.
Обработчик таймера.
-----------------------------------------------------------------
Каждый импульс таймера вызывает выполнение функции newtimer
в resident.c. Прежде всего она вызывает старый обработчик
таймера. Затем она проверяет, не нажат ли "горячий ключ". Если
да, то проверяется флажок занятости ДОС. Если ДОС не занята, то
newtimer проверяет, не производится ли в этот момент дисковая
операция. Если нет, то сбрасывается прерывание от таймера,
очищается флажок "горячего ключа", и вызывается функция dores,
начинающая выполнение TSR-программы.
Обработчик DOSOK.
-----------------------------------------------------------------
Прерывание DOSOK обслуживается обработчиком с именем new28 в
resident.c. Он присоединяется к старому обработчику этого
прерывания, и проверяет флажок "горячего ключа". Если он
установлен, то проверяется, занята ли ДОС, и затем очищается
флажок "горячего ключа" и вызывается dores.
Выполнение TSR-программы.
-----------------------------------------------------------------
Функция dores вызывается лишь после того, как программа
убедилась в безопасности своего выполнения. Dores прежде всего
устанавливает флажок, означающий, что она выполняется. Эта
установка предохраняет от повторного вызова путем вторичного
нажатия "горячего ключа". Затем сохраняется регистр стека
прерванной программы, и указатель стека устанавливается на
собственный стек TSR-программы.
Сохраняется вектор прерывания по критической ошибке, затем
соответствующий обработчик присоединяется к этому вектору.
Текущий статус Ctrl-Break сохраняется, и прерывания по Ctrl-Break
запрещаются.
Адрес дискового буфера прерванной программы сохраняется, и
устанавливается на соответствующий текущему контексту. То же
производится и с идентификатором процесса. Затем вызывается
утилита popup из popup.c. Функция popup сохраняет текущее
положение курсора, вызывает вашу программу, после ее выполнения
восстанавливает курсор и заканчивается. В листинге popup.c
вызывается функция exec, вы можете подставить туда имя вашей
программы.
При завершении popup адреса идентификатора процесса,
дискового буфера, вектор прерывания по критической ошибке, статус
Ctrl-Break и указатель стека восстанавливаются в те значения,
которые они имели до вызова TSR-программы, и выполняется возврат
в прерванную программу.
Удаление TSR-программы.
-----------------------------------------------------------------
При удалении пользователем TSR-программы путем запуска ее
копии с соответствующим параметром в командной строке,
нерезидентная копия вызывает резидентную через коммуникационное
прерывание. Функция terminate в resident.c проверяет, может ли
быть снята программа путем просмотра, не изменились ли значения
векторов прерываний. Если изменились, выполнение TSR-программы
приостанавливается. Если нет, она может быть снята.
Для удаления TSR-программы необходимо проделать три
процедуры. Сначала все файлы должны быть закрыты. Когда ДОС
завершает программу, она проверяет все 20 элементов в массиве
указателей файлов в PSP. Эта процедура закрывает все файлы на
уровне указателей и не затрагивает потоковых файлов. Так как эти
файлы должны быть закрыты, то terminate вызывает функцию
closefiles в popup.c, закрывающую все открытые файлы.
Вторая процедура - восстановление всех векторов прерываний
в значение, которое они имели до присоединения их к себе
TSR-программой.
Завершающим шагом является освобождение всех блоков памяти,
распределенных под TSR-программу. Память распределяется двумя
способами - из ДОС и из программы через вызов функций ДОС. Эти
два типа блоков памяти должны освобождаться тем же путем, которым
и выделялись.
Блоки памяти и управляющие блоки памяти.
-----------------------------------------------------------------
Выделяемый ДОС блок памяти содержит 16-байтный блок
управления памятью (БУП), следующий сразу за соответствующим
распределяемым блоком памяти. Он содержит следующие поля:
- однобайтный маркер, идентифицирующий БУП. Все, кроме
последнего БУП в списке, имеют значение маркера 0x4d. Последний
БУП имеет маркер 0x5a.
- двубайтный идентификатор процесса, которому принадлежит
блок памяти. Если блок свободен, это поле содержит 0.
- двубайтный размер блока памяти в параграфах. Размер БУП не
учитывается в этом значении. БУП следует в памяти непосредственно
за блоком памяти, который он представляет. Связки БУП-блок памяти
располагаются в памяти смежно. Сегментный адрес следующего БУП
равен адресу предыдущего БУП + размер блока памяти + 1. Если у
вас есть адрес первого БУП в памяти, то можете проследить всю
цепочку.
В ДОС имеется функция (естественно, недокументированная),
которая может быть использована для определения адреса первого
БУП в цепочке. Эта функция 0х52 возвращает сегментный адрес
первого БУП в регистре es и смещение в регистре bx. Эффективный
адрес этой пары сегмент: смещение, уменьшенный на 2, дает адрес
слова, содержащего сегментный адрес первого БУП в цепочке
распределенных ДОС блоков памяти.
Для освобождения памяти, занимаемой TSR-программой, должен
быть просмотрен весь список БУП. Каждый блок, содержащий
идентификатор процесса(PID) TSR-программы, освобождается путем
обращения к функции ДОС 0х49. При завершении этого процесса
TSR-программа завершается и удаляется из памяти.
ИСХОДНЫЕ ТЕКСТЫ: popup.c, resident.c
-----------------------------------------------------------------
Листинги 12.3 и 12.4 содержат текст TSR-драйвера. Эти файлы
после компиляции и связывания с вашей программой на Турбо Си
сделают ее резидентной.
Листинг 12.3.
-------------
/*---- popup.c ----*/
#include
#include
#include
#include
static union REGS rg;
unsigned sizeprogram = 48000/16;
unsigned scancode = 52;
unsigned keymask = 8;
char *signature = "POPUP";