Прерывание от клавиатуры (9). ----------------------------------------------------------------- При нажатии пользователем "горячего ключа" резидентная программа должна прервать все, что бы ни делала система, и запустить себя. Чтобы сделать это, программа должна сканировать клавиатуру в поисках "горячего ключа". Понимание этого процесса требует понимания, как работает прерывание от клавиатуры и сканирование кодов. Нажатие клавиши генерирует прерывание 9, и обработка передается программе по адресу вектора прерывания 9. Она должна прочитать порт данных клавиатуры и обработать это значение. Клавиатура PC генерирует код сканирования, который может быть прочитан из входного порта клавиатуры, а не значение ASCII, поступающее в вашу программу при использовании функции get_char. Каждая клавиша на клавиатуре имеет свое собственное значение. Программа должна определить по нему, какая клавиша или их комбинация нажата, и что делать с ними. Программа обработки прерывания от клавиатуры, находящаяся в ROM-BIOS (базовой системе ввода-вывода в ПЗУ), читает коды сканирования, переводит их в коды ASCII, и пересылает их в буфер клавиатуры. Программы (включая ДОС), читающие с клавиатуры, читают коды из буфера. ROM-BIOS поддерживает байт состояния, показывающий, были ли нажаты клавиши Alt, правый Shift, левый Shift, и Ctrl. Программа может прочитать эти значения, восстановив у себя байт состояния. В главе 12 иллюстрируются коды сканирования и значения байта состояния клавиатуры. TSR-программа может присоединить себя к прерыванию 9. Подпрограмма обработки прерывания в резидентной программе может поддерживать выполнение программы из ROM-BIOS, читая коды сканирования и байт состояния, и проверяя, не нажат ли "горячий ключ". Если нет, то обработка дальше предоставляется программе из ROM-BIOS. Если в TSR-программе нет вызовов функций ДОС, подпрограмма - 2 - обработки прерывания 9 может непосредственно выполнить свою функцию. Если же необходимо использовать функции ДОС, подпрограмма должна выставить флаг присутствия "горячего ключа" и закончить работу. Другие подпрограммы обработки прерывания должны заметить этот флаг и выполниться, если это безопасно. Одна из подпрограмм, ждущая подходящего момента для выполнения функции, - это подпрограмма обработки прерывания от таймера. Прерывание от таймера. ----------------------------------------------------------------- Работа PC прерывается 18.2 раза в секунду системным таймером, который использует вектор прерывания 0х1c для запуска подпрограммы обработки. В главу 12 включен пример резидентной программы "часы", использующей прерывание от таймера. TSR-программы, вызываемые по "горячему ключу", также должны использовать это прерывание, поэтому надо прикрепиться и к вектору 0х1с. Так как программа не может выполняться, пока не убедится, что использование функций ДОС безопасно, то надо проверить, установлен ли флаг "горячего ключа", как описано выше, и затем проверить безопасность. Если эти два условия выполнены, то подпрограмма обработки прерывания от таймера может запускать основную резидентную программу. Как и в случае с прерыванием от клавиатуры, должна быть реализована возможность работы с прерыванием от таймера программе, которая до этого была обработчиком такого прерывания. Проблема реентерабельности ДОС. ----------------------------------------------------------------- Вы уже читали о проблемах, возникающих при использовании функций ДОС из резидентных программ.В случае, когда программа прерывает ДОС и хочет использовать ее функции, ДОС не является реентерабельной. Может случиться так, что при нажатии "горячего - 3 - ключа" ДОС будет находиться в состоянии ввода с клавиатуры, и пользователю не удастся быстро вызвать TSR-программу, так как до окончания ввода она не может начать выполнение. Два стека ДОС. ----------------------------------------------------------------- ДОС поддерживает два стека.Когда ДОС выполняет одну из функций с номером от 0 до 12, используется первый стек; при выполнении других функций используется второй. ДОС сохраняет значение сегментного регистра стека и регистра смещения стека для каждой группы функций отдельно; но реентерабельных функций, однако, нет. Сохраненные значение регистров при первом вызове функции могут быть затерты при следующем вызове. ДОС лучше определить как полуреентерабельную. При прерывании выполнения функций 0-12 безопасно вызывать остальные функции, и наоборот. Можно легко избежать использования функций 0-12. Они предназначены для организации консольного ввода и вывода. Но есть много других способов, чтобы управлять клавиатурой и экраном, и частенько они работают лучше, чем функции ДОС. Использования остальных функций не так-то легко избежать. С их помощью можно делать многое из того, что нужно в резидентной программе, включая управление файлами. Если вы можете избежать использование функций 0-12, но нуждаетель в остальных функциях, то ситуация с двумя стеками работает на вас. При уверенности, что прерывания по вызову вашей программы будут происходить не во время работы функций второй группы, проблемы реентерабельности не существует. Задачу можно решить установкой флажка ввода "горячего ключа" и ожиданием окончания работы небезопасных функций ДОС, если таковая имеет место. Эти функции работают быстро, так что большой задержки не получится. - 4 - Теперь остальсь узнать, каким способом получить информацию о возможности использовать ДОС. Такую информацию содержит системный флажок занятости. Системный флажок занятости (0х34). ----------------------------------------------------------------- Функция ДОС 0х34 возвращает сегментный адрес и смещение специального флага в памяти, поддерживаемого ДОС. Он называется системным флажком занятости. Этот флаг устанавливается, когда ДОС выполняет одну из своих "небезопасных" функций. После выхода из функции ДОС очищает флаг. При первом запуске TSR-программы она использует функцию ДОС 0х34 для нахождения и запоминания адреса системного флажка занятости. При каждом прерывании от системного таймера обработчик, находящийся в TSR-программе, проверяет наличие флажка "горячего ключа", устанавливаемого обработчиком прерывания от клавиатуры, и отсутствие системного флажка занятости. При наличии этих двух условий обработчик таймера сбрасывает флажок "горячего ключа" и запускает основную часть TSR-программы. Такая процедура временами срабатывает. Но временами ДОС остается занятой, пока пользователь занят чем-то другим, например, набирает символы в командной строке. Если при этом он нажмет "горячий ключ", то обработчик клавиатуры установит флаг "горячего ключа", но обработчик таймера никогда не запустит TSR-программу. Чтобы бороться в таких условиях, надо использовать вектор прерывания DOSOK. Прерывание DOSOK. ----------------------------------------------------------------- - 5 - Часто наступают периоды, когда ДОС занята, но можно использовать вторую группу функций; например, когда ДОС ожидает ввода строки символов. В этом случае ДОС вызывает прерывание 0х28 - DOSOK. Цель этого - известить TSR-программу (в частности - системный спулер PRINT.COM ), о том, что можно использовать функции второй группы. Если никакая программа не прикреплена к этому прерыванию, оно просто указывает на команду IRET. Прерывание 0х28 - программное прерывание; обработчик, прикрепленный к нему, будет вызываться из другой программы по команде INT 0х28. TSR-программы могут прикрепляться к прерыванию DOSOK и использовать его для обнаружения занятости ДОС. Обработчик этого прерывания из TSR-программы проверяет установку флажка "горячего ключа" и в зависимости от его значения либо запускает основную часть TSR-программы; либо возвращает управление программе, вызвавшей это прерывание, или программе, ранее прикрепленной к этому прерыванию. Прерывание DOSOK вызывается в случае, когда ДОС занята; однако TSR-программа, вызванная в это время, не может быть прервана другой такой же программой до тех пор, пока выполняющаяся TSR-программа не сделает вызов прерывания 0х28. Причина этого ограничения в том, что когда ДОС вызывает прерывание DOSOK, флажок занятости ДОС остается установленным. При выполнении TSR-программы он не меняет значения, и, если пользователь нажмет "горячий ключ" для другой TSR-программы, ее обработчик таймера будет ожидать, пока флажок занятости не сбросится. А это не случится до окончания текущей TSR-программы. Такая ситуация всегда случается при вызове TSR-программы в момент, когда ДОС ожидает ввода команды пользователя. Резидентная программа, чтобы разрешить прервать свое выполнение другой такой же программе, должна вызвать прерывание DOSOK в подходящее время. Лучше всего это сделать, когда текущая программа ожидает ввода строки от пользователя. Если вы обратили - 6 - внимание, функция get_char из главы 4 вызывает прерывание 0х28 во время ожидания ввода строки. Это делается для поддержки концепции резидентных программ, описанной в этой главе и используемой в главе 12. Дисковое прерывание ROM-BIOS.(0х13) ----------------------------------------------------------------- Дисковые операции нельзя прерывать из-за возникновения ошибок в прерванной программе. Чтобы избежать этого, TSR-программа должна присоединить себя к дисковому прерыванию ROM -BIOS 0х13. Если какой-либо процесс вызывает это прерывание, TSR- программа устанавливает флаг и затем передает управление по старому адресу прерывания. Когда обработка прерывания 0х13 завершается, TSR-программа очищает флаг. Этот флаг проверяется ISR таймера и DOSOK. Если производится дисковая операция, то TSR не разрешает прерывание. Прерывание тяжелой ошибки в ДОС.(0х24) При возникновении тяжелой ошибки ДОС вызывает прерывание 0х24. Например, при обращении к дискете в открытом дисководе, ДОС обнаруживает состояние неготовности и вызывает прерывание тяжелой ошибки. Если программа не присоедиила себя к вектору 0х24, все критические ошибки будут обрабатываться ISR в командном процессоре ДОС. Он выдает сообщение "Abort,Retry, or Ignore" на экран. ISR возвращает в ДОС значение, определяющее порядок дальнейшей обработки. Предположим, что ни одна программа не присоединена к вектору 0х24. Ваша TSR прерывает нерезидентную программу, и пытается прочитать дискету из открытого дисковода. Командный процессор ДОС перехватывает управленеие и запрашивает пользователя о дальнейших действиях. Пользователь отвечает "A" (Abort-прервать), и ДОС пытается прервать выполнение программы. Если ваша TSR переключила адрес PSP, то ДОС будет пытаться прервать TSR. ДОС не знает, что - 7 - в памяти находится нерезидентная программа в прерванном состоянии, и не возвращает ей управление. Система ломается. Если TSR не переключала PSP, то ДОС завершает нерезидентную программу и не возвращает управление TSR. Предположим, что нерезидентная программа присоединилась к вектору 0х24. Дисковая ошибка в вашей программе вызовет исполнение обработчика тяжелой ошибки из прерванной нерезидентной программы. TSR должна присоединяться к вектору 0х24 в любом случае, будет ли или не будет в ней какая-либо обработка ошибок. Вектор присоединяется при "всплытии" TSR и восстанавливается на тот, что был, при возвращении управления в прерванную программу. Никакие обстоятельства не должны заставить TSR просить ДОС прервать обработку. Большинство TSR-программ просто игнорируют ошибки и говорят ДОС также их игнорировать. Прерывание тяжелой ошибки не связывается в цепочки - это опасно. Если другая TSR-программа (например, спулер) присоединила к себе вектор 0х24 и вызвана после вашей TSR, она получит ваши ошибки. Если же эта программа была вызвана до вашей TSR, то вы будете получать ее ошибки и говорить ДОС игнорировать их, что является проблемой, которую нельзя разрешить без написания системно-ориентированного обработчика ошибок. Прерывание Ctrl-Break в ДОС.(0х23) ----------------------------------------------------------------- При нажатии пользователем клавиш Ctrl-Break ДОС отображает на экране в текущей позиции курсора символы ^C и вызывает прерывание 0х23. Обработчик этого прерывания, имеющийся в ДОС, вызывает немедленное завершение текущей программы. TSR-программу завершать таким способом нельзя: слишком много векторов прерываний прикреплено к ней, и нерезидентная программа может находиться в памяти за ней. Если вы просто проигнорируете это прерывание, имеется риск, что другая программа, загруженная за - 8 - вашей, присоединит себя к этому прерыванию и сделает что-то неподходящее при нажатии клавиш Ctrl-Break во время выполнения вашей программы. В ДОС имеется функция (0х33), которая позволяет программе читать текущий статус (разрешено/запрещено) обработки Ctrl-Break и устанавливать его. При вызове TSR-программы она должна прочитать текущее значение статуса, и затем запретить обработку Ctrl-Break. При завершении своей работы TSR должна восстановить значение статуса обработки Ctrl-Break. Присоединяться к этому прерыванию нет необходимости. После таких действий вашей TSR в случае нажатии Ctrl-Break во время ее выполнения, после возврата в прерванную программу она будет закончена ДОС. Если все TSR будут использовать такой способ, то завершаться по Ctrl-Break будут только нерезидентные программы. Выполнение TSR-программы. ----------------------------------------------------------------- Выполнение TSR-программы проходит в два этапа. Первый этап - когда пользователь запускает ее из командной строки. Программа выполняет инициализационный код, сохраняет свой контекст, присоединяет себя к прерываниям, если это нужно, и завершается с использованием функции ДОС TSR, таким образом объявляя себя резидентной. Второй этап - когда TSR-программа запускается в результате одного из прерываний, к которому она прикрепилась. В большинстве случаев, векторы прерываний связываются в цепочки, как это было описано ранее. Программа определяет, может ли она выполняться - это зависит от состояния некоторых важных системных индикаторов, описанных выше. Если она может выполняться, она сохраняет контекст прерванной программы, восстанавливает свой собственный контекст, выполняет свою задачу, восстанавливает контекст - 9 - прерванной программы, и передает ей управление. Завершение TSR-программы. ----------------------------------------------------------------- Могут быть причины, по которым вы можете захотеть завершить TSR-программу, что отнюдь не является легкой задачей. Вспомните, ведь ДОС не знает ничего о программе, оставшейся резидентной, и вам самим надо сделать все то, что обычно делает ДОС по отношению к обычным нерезидентным программам. Завершение TSR-программы включает в себя следующие шаги: 1. Сообщение программе о том, что ей надо завершиться. Для этого можно использовать другой "горячий ключ", или коммуникационный вектор прерывания, указывающий на сигнатуру в программе, означающую, что программа уже в памяти. Второй метод используется в драйвере TSR-программ, описанном в главе 12. При этом пользователь, знающий, что программа уже резидентна, запускает ее второй раз, давая в командной строке параметр, означающий, что программа должна завершиться. Программа ищет свою сигнатуру способом, описанным выше. При нахождении сигнатуры она также находит свой вектор прерывания. Через этот вектор она посылает команду завершения свой резидентной копии, после чего та совершает следующие шаги завершения. 2. Восстановление векторов прерывания в первоначальное состояние. У вас может быть возможность сделать это, а может и не быть. Если другая TSR-программа была загружена после вашей, и она связала векторы в цепочки, то вы успешно дезактивируете ее при восстановлении векторов. Далее, если эта TSR-программа будет затем завершена, то она переустановит вектора на адреса в вашей - 10 - программе, по которым уже может находиться неизвестно что. Так что до того, как восстанавливать вектора, надо убедиться, что ваша программа до сих пор владеет ими. Чтобы определить это, надо сравнить адреса, находящиеся в соответствующих векторах, с адресами соответствующих обработчиков в вашей программе. Если какой-либо вектор изменен, то ваша программа уже не владеет им, поэтому завершать ее нельза и надо просто приостановить ее. Еще одним осложнением, возникающем при завершении вашей TSR с другой программой такого же типа, загруженной после нее, является фрагментация памяти в нерезидентной области. ДОС будет использовать область, освобожденную вашей программой, при запросах на память из выполняющихся программ, но для загрузки других программ эта область не будет использоваться. Помните, что ДОС - однозадачная ОС - не понимает концепции фрагментированных программ, потому что она не понимает мультипрограммирование. 3.Закрытие всех файлов, открытых в программе. При завершении нерезидентной программы ДОС автоматически закрывает все файлы, сканируя массив указателей файлов программы в PSP и закрывая все вхождения в него, которые еще используются. Помните, что эти вхождения - это номера элементов массива, который поддерживает ДОС. При завершении и объявлении резидентной ДОС не закрывает файлы, открытые этой программой, и при завершении работы TSR-программы необходимо это сделать. Если файлы не будут закрыты, то элементы массива не будут освобождены, и будут недоступны для дальнейшего использования другими программами. Загрузка и завершение без закрытия файлов TSR-программ может привести к исчерпанию таблицы файлов ДОС и "зависанию" системы. Элементы в таблице указателей файлов представляют файлы на уровне указателей. Это эквивалентно функциям небуферизованного ввода-вывода низкого уровня в Си. Функции Си, выполняющие буферизованный потоковый ввод-вывод, поддерживают собственные буферы и указатели и могут потребовать особого порядка - 11 - закрытия, называемого потоковым. Такое закрытие - это библиотечная функция Си, отличающаяся от соответствующей функции ДОС. Такие файлы надо закрывать стандартной функцией fclose. Файлы, открытые функциями open и creat, могут быть закрыты функцией close. 4.Возврат памяти, занимаемой программой, в ДОС. Для этого можно использовать функцию ДОС 0х49. TSR-программой, как минимум, занимается два блока памяти. Первый - это блок системных параметров, адрес которого хранится в PSP со смещением 0х2с. Второй блок - это сам PSP. Если программе выделены оба этих блока, то они оба должны быть возвращены ДОС. В главе 12 демонстрируется техника сканирования списка управляющих блоков памяти ДОС для нахождения и возвращения в ДОС блоков памяти, выделенных программе. Приостановка и возобновление выполнения TSR-программы. ----------------------------------------------------------------- При приостановке TSR-программы ее не удаляют из памяти, а только дают ей команду не реагировать на прерывания путем установки флага. Команда возобновления сбрасывает этот флаг. С этой целью можно использовать коммуникационный вектор прерывания. TSR-драйвер в главе 12 использует эту технику для завершения, приостановки, и возобновления выполнения TSR-программ. Выводы. ----------------------------------------------------------------- В этой главе описывается операционное окружение резидентных программ. В главе 12 на примере двух таких программ описывается - 12 - весь процесс шаг за шагом. Первая программа - это оперативная программа-часы, которая поддерживает на экране постоянное отображения текущих времени и даты. Вторая программа более интересна - это TSR-драйвер. Вы связываете свою программу на Турбо Си c этим драйвером, используя некоторые соглашения для инициализации, и ваша программа на Турбо Си становится резидентной. Это значит, что программа будет вызываться по нажатию клавиши, что она может открывать, читать, пиать в и закрывать дисковые файлы, что она использует ROM-BIOS функции для чтения с клавиатуры и прямой доступ к видеопамяти для вывода на экран, и что она никогда не выходит в ДОС. ГЛАВА 12 Построение резидентных программ ----------------------------------------------------------------- В этой главе демонстрируется, как концепции TSR-программ, описанные в главе 11, воплощаются на практике в программы на Турбо Си. Функции из этой главы представляют собой программу-драйвер, связав которую с вашей программой на Турбо Си, вы получите резидентную программу. Имеются некоторые ограничения на эту программу. Первое - это то, что в ней не должны применяться функции Турбо Си, вызывающие прерывание ДОС 0х21 с номерами функций от 0 до 12. Это означает, что весь обмен с клавиатурой и экраном должен выполняться с помощью вызовов BIOS или прямого доступа к экранной памяти. В примерах используются оконные функции из предыдущих глав, удовлетворяющие этим соглашениям. Второе - TSR-программа должна быть скомпилирована в крохотной (tiny) или малой (small) модели памяти. В главе 10 демонстрируется интеграция всех предыдуших примеров в одну программу, выполняемую под управлением оконных меню. В этой главе та же программа превращается в TSR. Интегрированный пример использует файловые функции ДОС и - 13 - выполняет в соответствии с этим все правила, описанные в главе 11. По причине сложности этой задачи и для того, чтобы облегчить ваше знакомство с программированием TSR, первый пример не требует соблюдения этих правил. Пример TSR-программы - "часы". ----------------------------------------------------------------- На листинге 12.1 приведена программа clock.c, простая TSR-утилита, обеспечивающая постоянное отображение даты и времени в верхнем левом углу экрана. В программе после активизации не делаются вызовы ДОС, поэтому нет нужды в защите от нереентерабельности ДОС. Превращение программы в резидентную. ----------------------------------------------------------------- Функция main производит все подготовительные действия и объявляет себя резидентной. Сначала она сохраняет свой указатель стека, что позволит затем восстановить свой собственный стек при вызове. Затем в программе используется getvect для чтения текущего вектора прерывания таймера, после чего с помощью setvect в качестве обработчика таймерного прерывания устанавливается функция newtimer. Указатель стека TSR-программы устанавливается как функция от объявленного размера программы и адреса видеопамяти, определяемого на основе значения, возвращаемого функцией vmode. Для получение даты и времени используются функции ДОС. Прерывание по делению на ноль. ----------------------------------------------------------------- При старте программ, написанных на Турбо Си, выполнение начинается со стартового кода. При этом устанавливается начальные - 14 - величины стека и "кучи" и вызывается функция main. Стартовый код находится в файлах c0t.obj (для крохотной модели) и c0s.obj (для малой модели). Эти файлы поставляются вместе с Турбо Си. В стартовом коде находится обработчик прерывания по делению на ноль, который присоединяется к соответствующему вектору перед вызовом функции main. При выполнении return из функции main этот вектор устанавливается в предыдущее свое значение. Возврат из функции main нормальной нерезидентной программы означает, что программа закончила свои действия и готова к завершению. Но TSR-программы не завершаются выдачей return из функции main. Они используют одну из TSR-функций ДОС, и таким образом должны сами восстанавливать предыдущее значение вектора прерывания по делению на ноль до завершения и превращения в резидентную. Если этого не будет сделано, то ошибка деления на ноль в другой программе будет обрабатываться стартовым кодом вашей TSR-программы. Фирма Borland поставляет исходные тексты стартового кода Турбо Си. Они находятся в файлах c0.asm и rules.asi. Вам понадобится модифицировать c0.asm и ассемблировать его дважды - для крохотной и малой моделей памяти. Адрес, по которому в c0.asm сохраняется вектор прерывания по делению на ноль, назван ZeroDivVector. Эта переменная локальна в c0.asm. Чтобы ваша программа могла восстановить значение этого вектора, к нему надо получить доступ путем преобразования ZeroDivVector в public-переменную. Турбо Си добавляет символ _ в начале имен внешних переменных, и каждое вхождение переменной ZeroDivVector в c0.asm вы должны заменить на _ZeroDivVector. Затем надо заменить оператор, объявляющий ZeroDivVector в программе, на следующий: PubSym@ ZeroDivVector
,_CDECL_ Ассемблируйте файл дважды с помощью следующих команд: C>masm c0,c0t /ML /D_TINY_, C>masm c0,c0s /ML /D_SMALL_, - 15 - после чего будут созданы файлы c0t.obj и c0s.obj. Замените исходные файлы Турбо Си на эти. Обратите внимание на объявление в clock.c указателя функции прерывания ZeroDivVector. Внешний указатель - это как раз то, что вы только что объявили public в стартовом коде. В функции main, до объявления себя резидентной, clock.c использует функцию setvect для восстановления вектора прерывания по делению на ноль. Затем clock.c завершается с объявлением себя резидентной. Если у вас нет исходных текстов стартового кода, можно найти следующий выход: позволить TSR-программе указывать на ошибку деления на ноль, как только она случилась. При возникновении такой ошибки в другой программе стартовый код вашей программы будет выдавать сообщение об ошибке и завершать программу, в точности как и соответствующий обработчик ДОС. При завершении программы ДОС устанавливает вектор прерывания на свой обработчик деления на ноль. Ваша программа больше не будет работать. Конечно, вы должны удалить ссылки на ZeroDivVector в clock.c и resident.c. Выполнение обработчика прерываний от таймера. ----------------------------------------------------------------- С каждым "тиканьем часов", происходящим 18.2 раза в секунду, вызывается функция newtimer, объявленная как interrupt в Турбо Си. Это объявление означает, что при вызове функции регистры сохраняются в стеке и регистр сегмента данных указывает на сегмент данных программы, с которой функция связана с помощью link. Такое объявление также гарантирует восстановление регистров из стека и выполнение машинной команды IRET при завершении работы функции. Команда IRET используется обычно для выхода из обработчика прерывания. Она восстанавливает регистры программного счетчика, флагов и сегмента кодов, сохраненные при возникновении прерывания. - 16 - Связывание старого вектора прерывания по таймеру. ----------------------------------------------------------------- При выполнении newtimer прежде всего вызывается обработчик прерывания, на который указывает oldtimer. Это действие позволяет другим программам, уже присоединенным к вектору, произвести необходимые действия. Функция newtimer проверяет флаг, который устанавливает сама же эта функция, и означающий, что она еще работает. Сохранение и переключение контекста стека. ----------------------------------------------------------------- Функция newtimer сохраняет сегмент стека и регистры указателей - эти величины принадлежат прерванному процессу. Значения стековых регистров, сохраненные при выполнении clock.c, восстанавливаются в регистрах процессора, поэтому clock. c может использовать свой собственный стек и не портить стек прерванной программы. Вычисление времени. ----------------------------------------------------------------- Функция newtimer подсчитывает сигналы таймера. При прохождении 18 сигналов (19 каждый пятый раз, так как сигналы приходят 18.2 раза в секунду), новая величина времени вычисляется для отображения на экран. Затем дата и время отображаются в верхнем левом углу экрана, восстанавливаются значения регистров стека прерванной программы, - 17 - и newtimer возвращает ей управление. Заметим, что newtimer не переводит дату в полночь и не изменяет значения на экране после ввода новых даты и времени командой ДОС. Эта программа лишь иллюстрирует работу простейшей TSR-программы. Если вы не работаете после полуночи, вы можете использовать ее для отображения даты и времени на экране. Она обновляет значения каждую секунду, поэтому вывод на экран другими программами ничего не испортит. В качестве эксперимента вы можете добавить будильник в clock.c. Включите время, когда надо "звонить", как параметр, передаваемый в командной строке при первом запуске clock.exe. Затем, при каждом изменении значения часов сравнивайте его с этим временем. При равенстве времен, выдайте звуковой сигнал, избегая, естественно, использования функций ДОС. Позднее, когда вы узнаете, как использовать коммуникационный вектор прерывания, вы сможете модифицировать clock.c для установки и изменения времени звонка путем запуска clock.exe из командной строки с параметром в то время, как TSR уже резидентна. Вы можете добавить комментарии к звонку путем использования оконных функций и оконного редактора. Путем именно таких последовательных улучшений были созданы программы мировой известности. Программа clock.c использует прерывание от таймера. Если вы загрузите ее после Sidekick, часы перестанут идти при вызове Sidekick. Так как newtimer просто считает секунды, а не читает время ДОС, такая смесь программы с Sidekick'ом сделает ее результаты неверными. Sidekick отбирает вектор прерывания от таймера у любой TSR-программы, загружаемой после него, чем и вызыает такой результат. Остерегайтесь Sidekick'а при загрузке ваших резидентных программ. Чтобы запустить "часы", введите следующую команду: C>clock ( Листинг 12.1 ). - 18 - /*--------- clock.c -----------*/ #include void interrupt (*oldtimer)(); void interrupt newtimer(); extern void interrupt (*ZeroDivVector)(); #define sizeprogram 375 unsigned intsp,intss; unsigned myss,stack; static union REGS rg; struct date dat; struct time tim; unsigned vseg; int running = 0; char bf[20]; unsigned v; char tmsk []= " %2d-%02d-%02d %02d:%02d:%02d "; int ticker = 0; static struct SREGS seg; main() { segread(&seg); myss = _SS; oldtimer = getvect(0x1c); setvect(0x1c,newtimer); stack = (sizeprogram - (seg.ds - seg.cs))*16-300; vseg = vmode() == 7 ? 0xb000 : 0xb800; gettime(&tim); getdate(&dat); - 19 - setvect(0,ZeroDivVector); rg.x.ax = 0x3100; rg.x.dx = sizeprogram; intdos(&rg,&rg); } void interrupt newtimer() { (*oldtimer)(); if (running ==0) { running = 1; disable(); intsp = _SP; intss = _SS; _SP = stack; _SS = myss; enable(); if (ticker ==0) { ticker = (((tim.ti_sec % 5) ==0)? 19 :18 ); tim.ti_sec++; if (tim.ti_sec == 60) { tim.ti_sec =0; tim.ti_min++; if (tim.ti_min == 60) { tim.ti_min=0; tim.ti_hour++; if (tim.ti_hour == 24) tim.ti_hour = 0; } } sprintf(bf,tmsk,dat.da_day,dat.da_man,dat.da_year % 100, tim.ti_hour,tim.ti_min,tim.ti_sec); - 20 - } for (v=0;v<19;v++) vpoke (vseg,(60+v)*2,0x7000+bf[]); disable(); _SS = intsp; _SS = intss; enable(); running = 0; } } Файл проекта для построения clock.exe с именем clock.prj имеет следующий вид: Листинг 12.2: clock.prj clock ibmpc.obj ПРОГРАММЫ TSR-ДРАЙВЕРА. ----------------------------------------------------------------- Чтобы расширить возможности TSR-программ по использованию функций ДОС при ее вызове, в этой главе приводится два исходных текста на Си. После их адаптации и связи с любой стандартной программой на Си, та становится резидентной программой. Первый текст содержит функцию main, и туда помещаются параметры, зависящие от вашей программы. Второй файл - это основной TSR-драйвер, управляющий присоединением векторов прерываний, самих прерываний, арбитраж столкновений ДОС и BIOS, определение, резидентна ли уже программа, приостановка и возобновление работы TSR-программы, и удаление TSR-программы из памяти. - 21 - Третий модуль в этом наборе - ваша прогррамма на Си, которая должна придерживаться следующих правил, чтобы правильно выполняться в этом окружении: - программа должна быть построена в крохотной или малой моделях памяти; - программа не должна использовать функций ДОС от 0 до 12; - при изменении текущего дискового каталога программа должна восстанавливать его при возврате в прерванную программу; - программа не должна использовать операции с плавающей запятой; - программа не должна завершаться или выходить в ДОС. Вас может заинтересовать, почему надо избегать использования операций с плавающей запятой. Дело в том, что подпрограммы с плавающей запятой Турбо Си используют ряд векторов прерываний, которые присоединяются во время выполнения стартового кода. Эти вектора не восстанавливаются до завершения программы и передачи управления при этом в стартовый код. В стартовом коде не поддерживается область сохранения для этих векторов (среди которых имеется и вектор немаскируемого прерывания 2); таким образом, при завершении работы TSR-программы и удалении ее из памяти эти векторы не будут восстанавливаться. В этолй главе используются программа exec.c из главы 10 и все примеры оконных программ практически готовых стать резидентными. Действия трех программных модулей. ----------------------------------------------------------------- Три программных модуля для TSR-программ - это popup.c, resident.c и ваша утилита на Турбо Си. Popup.c (листинг 12.3) и resident.c (листинг 12.4) содержат сам TSR-драйвер. Popup.c - это - 22 - модуль, который надо изменить соответственно требованиям вашей утилиты, а resident.c - модуль, остающийся неизменным для всех TSR-программ. В дальнейшем обсуждении мы будем переключаться между двумя этими модулями, так как они оба созданы для поддержки создания вами TSR-программ. Popup.c содержит кроме любого установочного кода, необходимого вам, еще и несколько переменных, которые надо инициализировать значениями, описывающими вашу программу. Размер TSR-программы. ----------------------------------------------------------------- Беззнаковая переменная sizeprogram специфицирует размер программы в параграфах по 16 байт. Вы уже прочитали в главе 11, как определять это значение. До тех пор, пока ваша программа не начнет действовать, увеличивайте это значение. Программа в крохотной модели не может быть больше, чем 64К (4096 параграфов), а в малой модели - больше 128К (8192 параграфов). Присвоение "горячего ключа". ----------------------------------------------------------------- Значение клавиши "горячего ключа" TSR-программы определяется значениями беззнаковых переменных scancode и keymask. При нажатии клавиши обработчик прерывания 09 читает входной порт клавиатуры, в котором находится скан-код клавиши (ее порядковый номер). Каждая клавиша имеет свой скан-код, которые изображены на рисунке 12.1. Следовало бы присваивать такие клавиши, которые не совпадали бы с клавишами других программ, в том числе и нерезидентных. Стоит избегать функциональных клавиш, комбинаций с Alt- и Ctrl-, так как многие программы их используют. Наилучшим выбором является редкая и оригинальная комбинация клавиш, такая, - 23 - как Alt-точка. +-----------------------------------------------------------------+ |+--+--++--+-+--+--+--+--+--+--+--+--+--+--+--+--+--++--+--+--+--+| ||59|60||41|2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|43|14||1 |69|70|84|| |+--+--++--+++-++-++-++-++-++-++-++-++-++-++-++-++--++--+--+--+--+| ||61|62||15 |16|17|18|19|20|21|22|23|24|25|26|27| ||71|72|73|55|| |+--+--++---++-++-++-++-++-++-++-++-++-++-++-++-+ 28|+--+--+--+--+| ||63|64|| 29 |30|31|32|33|34|35|36|37|38|39|40| ||75|76|77|74|| |+--+--++----++-++-++-++-++-++-++-++-++-++-++-+-----++--+--+--+--+| ||65|66|| 42 |44|45|46|47|48|49|50|51|52|53| 54 ||79|80|81| || |+--+--++----++-++--+--+--+--+--+--+--+--+--+-+-----++--+--+--+78|| ||67|68|| 56 | | 57 | | 58 || 82 |83| || |+--+--++----+ +---------------------------+ +-----++-----+--+--+| | | | Клавиатура IBM AT | | | | | |+--+--++--+-+--+--+--+--+--+--+--+--+--+--+--+-----++-----+-----+| ||59|60|| 1|2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13| 14 || 69 | 70 || |+--+--++--+++-++-++-++-++-++-++-++-++-++-++-++-+---++--+--+--+--+| ||61|62||15 |16|17|18|19|20|21|22|23|24|25|26|27| ||71|72|73|74|| |+--+--++---++-++-++-++-++-++-++-++-++-++-++-++-++28|+--+--+--+--+| ||63|64|| 29 |30|31|32|33|34|35|36|37|38|39|40|41| ||75|76|77| || |+--+--++----++-++-++-++-++-++-++-++-++-++-++-+--+--++--+--+--+ || ||65|66|| 42 |44|45|46|47|48|49|50|51|52|53| 54 |55||79|80|81|78|| |+--+--++----++-++--+--+--+--+--+--+--+--++++---++--+++-+--+--+ || ||67|68|| 56 | | 57 || 58 || 82 | 83 | || |+--+--++----+ +-------------------------++----++----+-------+--+| | | | Клавиатура IBM PC | +-----------------------------------------------------------------+ Рис.12.1. Скан-коды клавиатуры. Обнаружение комбинации клавиш облегчается при использовании - 24 - маски статуса по адресу 0:417, содержащей биты для клавиш Ctrl, Alt, Shift, Ins, Caps Lock и Scroll Lock. Эта маска показывает, какая из этих клавиш в текущий момент нажата. На рис.12.2 показано положение битов в маске. +------+-------+-------+--------+-------+-------+-------+-------+ | INS | CAPS | NUM | SCROLL | ALT | CTRL | LEFT | RIGHT | | | LOCK | LOCK | LOCK | | | SHIFT | SHIFT | +------+-------+-------+--------+-------+-------+-------+-------+ Рис.12.2. Если соответствующий бит в маске установлен в 1, то клавиша нажата. По скан-коду нажатой клавиши и значению бита в маске обработчик прерываний от клавиатуры может определить, нажата ли клавиша "горячего ключа". Чтобы специфицировать эти значения, вам надо присвоить значения переменным scancode и keymask в popup.c. Как показано в листинге, scancode равен 52, и keymask равен 8, что соответствует Alt-точка. Сигнатура TSR-программы. ----------------------------------------------------------------- Программа popup.c также обеспечивает сигнатуру, которая используется для определения, не резидентна ли уже TSR-программа. Символьный массив с именем signature - это строка, заканчивающаяся \0. При первом старте popup.c вызывает функцию resident. Она находится в resident.c, ее задачей является определение, резидентна ли уже программа, и если нет, определить и назначить коммуникационный вектор. Это делается сканированием пользовательских векторов прерываний от 0х60 до 0х67. Если какой- либо вектор содержит значение, то сегментная половина этого значения комбинируется со смещением сигнатуры в программе. Это - 25 - смещение является указателем на сигнатуру, то есть смещением относительно регистра сегмента данных. Сегментная часть вектора суммируется с разницей между значениями регистров сегмента кода и сегмента данных. Помните, что значение сегментного регистра, взятое из вектора, должно быть значением сегмента кода первоначально стартовавшей TSR-программы. Сигнатура имеется как в области данных TSR-программы, так и в области данных копии TSR-программы, которая пытается стать резидентной. Нам нужен сегментный адрес данных TSR-программы, который не сохраняется в коммуникационном векторе. Это значение должно быть вычислено. Программа, просматривающая векторы, является второй копией TSR-программы (в предположении, что TSR-программа уже загружена), поэтому разность значений регистров кода и данных у нее должна быть такой же, как и у уже резидентной программы. Пользуясь этим алгоритмом, вы сравниваете значение по соответствующим смещением, и если они одинаковы, то программа уже загружена, и функция resident вернет найденный вектор в функцию main в popup.с. Если сигнатуры не равны, сканирование продолжается, пока не будут проверены все векторы. Если определяется, что TSR-программа еще не резидентна, то первый подходящий вектор становится коммуникационным. В функцию main возвращается 0, что означает, что программа стала резидентной. Коммуникационные прерывания. ---------------------------------------------------------------- Если функция main обнаруживает, что копия программы уже резидентна, она проверяет параметры командной строки. Используемая техника позволяет передавать при запуске программы параметры для ее резидентной копии. Напомним, что в памяти имеется в этот момент две копии TSR-программы - резидентная и только что загруженная в нерезидентную область. Нерезидентная - 26 - версия может связываться с резидентной копией через коммуникационный вектор. В типичной TSR-программе используются три параметра, но вы можете добавить еще, если необходимо. Стандартные три позволяют пользователю удалять программу из памяти, приостанавливать и возобновлять ее выполнение. Если один из этих параметров присутствует в командной строке (это определяется при помощи args,argv), то генерируется программное прерывание, с установлением регистра ax в соответствующее опции значение. Прерывание генерируется функцией main нерезидентной копии TSR-программы. Посмотрим далее на листинг popup.c. Функция прерывания с именем ifunc является обработчиком коммуникационного прерывания. Когда функция main нерезидентной копии TSR-программы инициирует прерывание, вызывается обработчик из резидентной копии. Он проверяет значение регистра ax и производит соответствующие действия. При этом вызывается одна из трех функций: terminate, restart или wait в resident.c. Вы можете вставить сюда и другую логику, чтобы управлять состоянием резидентной программы. Функции wait и restart в resident.c просто устанавливают и очищают флаг, означающий для обработчика клавиатуры необходимость реакции на нажатие клавиши "горячего ключа". Функция terminate в resident.c должна определять, может ли быть завершена TSR-программа, и затем, если да, производить все действия, аналогичные действиям ДОС при завершении нерезидентной программы. Этот процесс будет обсуждаться после того, как вы поймете, как программа становится резидентной. Подготовка к резидентности. ----------------------------------------------------------------- Если функция resident обнаруживает, что программа еще не в памяти, то popup.c подготавливает себя к переходу в резидентные. - 27 - ваша программа захочет открыть файлы или сделать еще что-нибудь "по хозяйству" при ее вызове. Popup.c, взятая для примера, вызывает load_help для установки функций помощи из главы 7, устанавливает путь ДОС для использования программой notepad из главы 9, и выдает сообщение. Потом она вызывает resinit, функцию из resident.c, которая делает все остальное, чтобы программа стала резидентной. Первоначально, resinit сохраняет значение регистра сегмента стека для переключения контекста стеков при вызове TSR-программы. Затем читается флаг занятости ДОС с помощью функции ДОС 0х34. Функция getdta получает адрес дискового буфера для TSR-программы. Этот адрес также используется при дальнейшем переключении контекстов. Адрес идентификатора процесса (PID) восстанавливается и сохраняется. Эта техника объяснялась в главе 11. Векторы прерывания для таймера, клавиатуры, диска и DOSOK читаются и запоминаются для связывания прерываний в цепочки, и собственные обработчики присоединяются к этим прерываниям. Указатель стека TSR-программы вычисляется так, чтобы быть на 300 байт меньше, чем размер программы, и вектор обработки деления на ноль восстанавливается на то значение, которое он имел до того , как стартовый код присоединил его. Затем TSR-функция ДОС используется для завершения программы, оставляя ее резидентной. Обработчик обращения к диску. ----------------------------------------------------------------- В то время, как компьютер выполняет программу, и программа вызывает ДОС, ДОС использует дисковое прерывание BIOS (0х13) для чтения и записи секторов данных. Напомним, что в главе 11 вас уже предупреждали о том, что дисковые операции прерывать нельзя. Обработчику диска в resident.c дано имя newdisk, и он защищает дисковые операции от прерывания вашей TSR-программой. При возникновении прерывания 0х13 устанавливается флаг, а после этого управление передается на обработку дисковых операций. После - 28 - окончания обработки флаг очищается. Если этот флаг установлен, когда программа вызывается по нажатию "горячего ключа", то производится задержка выполнения до очистки этого флага. Обратите внимание на трюк в newdisk. Функция, описанная как interrupt, сохраняет все регистры в стеке, устанавливает регистр ds на сегмент данных программы, содержащей эту функцию. Затем начинается выполнение кода функции. При завершении функции регистры восстанавливаются, и выполняется машинная команда IRET. Эта команда восстанавливает регистры программного счетчика, сегмента кода и флажков. Таким образом, выполняется полное восстановление регистров прерванной программы. Все работает до тех пор, пока вы не имеете прерывания, которое возвращает условие в флаге переноса. Команда IRET восстанавливает все флаги в состояние, которое было при возникновении прерывания. Некоторые программные прерывания ДОС и BIOS используют этот флаг для индикации результата. В их числе и прерывание 0х13 BIOS; однако программа обработчика обращений к диску сохраняет значение флажка переноса. Когда заканчивает свои действия присоединенный обработчик olddisk, необходимо вернуть вызывавшему процессу два значения: регистр ax и флажок переноса. Значение ax берется из псевдопеременной _AX и помещается в целую переменную ax, являющуюся одним из параметров функции прерывания. Турбо Си использует это средство, чтобы изменять значения регистров, которые будут восстановлены из стека при возврате из функции. Для регистра флагов псевдопеременной нет, и, чтобы избежать программирования на ассемблере, делается следующий хэккерский трюк: за функцией newdisk может быть сразу же вызвана функция newcrit, и она запишет флаговый регистр, сохраненный в стеке при ее вызове, во внешнюю переменную cflag. При возврате из newcrit cflag записывается в стек, где было сохранено прошлое значение регистра флагов.При возврате из функции результирующие флаги из olddisk будут восстановлены в регистр флагов. Эти действия базируются на понимании того, как Турбо Си использует регистры и производится вызов и возврат из функций - 29 - interrupt. Таким образом, эти программы не переносимы на другой компилятор, и могут быть несовместимы даже с будущими версиями Турбо Си, если Borland изменит свои соглашения. Программисты на ассемблере, возможно, скажут, что подобные дествия можно было бы легко проделать на их любимом языке. Эта критика верна, но этот программный трюк является примером, как достигнуть пределов возможностей Турбо Си. Автор благодарен Турбо Си за тот сервис, который предоставляется для разрешения всех проблем, возникающих при создании TSR-программ. Обработчик критических ситуаций. ----------------------------------------------------------------- Функция interrupt с именем newcrit является присоединенным обработчиком критических ситуаций. Она не присоединяется к прерыванию, когда программа объявляет себя резидентной, а делает это лишь временно, при "всплытии" TSR-программы. Его задача - обезопасить TSR-программу от возникновения критических ошибок в то время, как она переключила контекст на себя. Обработчик возвращает ноль в регистре ax, что означает для ДОС игнорировать ошибку. Обработчик клавиатуры. ----------------------------------------------------------------- Функция interrupt с именем newkb является обработчиком клавиатуры для TSR-программы. Она читает порт данных клавиатуры и проверяет на соответствие определенному скан-коду, означающему нажатие клавиши "горячего ключа". При равенстве и если TSR-программа не приостановлена, "горячий ключ" активизируется. Функция сбрасывает клавиатуру, чтобы не было будущих прерываний, и затем проверяет, а не вызвана ли уже TSR-программа. Если нет, то устанавливается флаг, означающий, что нажат "горячий ключ", и - 30 - выполнение функции заканчивается. Если скан-код и маска статуса не равны "горячему ключу", то управление передается старой программе обработки прерываний от клавиатуры. Обработчик таймера. ----------------------------------------------------------------- Каждый импульс таймера вызывает выполнение функции newtimer в resident.c. Прежде всего она вызывает старый обработчик таймера. Затем она проверяет, не нажат ли "горячий ключ". Если да, то проверяется флажок занятости ДОС. Если ДОС не занята, то newtimer проверяет, не производится ли в этот момент дисковая операция. Если нет, то сбрасывается прерывание от таймера, очищается флажок "горячего ключа", и вызывается функция dores, начинающая выполнение TSR-программы. Обработчик DOSOK. ----------------------------------------------------------------- Прерывание DOSOK обслуживается обработчиком с именем new28 в resident.c. Он присоединяется к старому обработчику этого прерывания, и проверяет флажок "горячего ключа". Если он установлен, то проверяется, занята ли ДОС, и затем очищается флажок "горячего ключа" и вызывается dores. Выполнение TSR-программы. ----------------------------------------------------------------- Функция dores вызывается лишь после того, как программа - 31 - убедилась в безопасности своего выполнения. Dores прежде всего устанавливает флажок, означающий, что она выполняется. Эта установка предохраняет от повторного вызова путем вторичного нажатия "горячего ключа". Затем сохраняется регистр стека прерванной программы, и указатель стека устанавливается на собственный стек TSR-программы. Сохраняется вектор прерывания по критической ошибке, затем соответствующий обработчик присоединяется к этому вектору. Текущий статус Ctrl-Break сохраняется, и прерывания по Ctrl-Break запрещаются. Адрес дискового буфера прерванной программы сохраняется, и устанавливается на соответствующий текущему контексту. То же производится и с идентификатором процесса. Затем вызывается утилита popup из popup.c. Функция popup сохраняет текущее положение курсора, вызывает вашу программу, после ее выполнения восстанавливает курсор и заканчивается. В листинге popup.c вызывается функция exec, вы можете подставить туда имя вашей программы. При завершении popup адреса идентификатора процесса, дискового буфера, вектор прерывания по критической ошибке, статус Ctrl-Break и указатель стека восстанавливаются в те значения, которые они имели до вызова TSR-программы, и выполняется возврат в прерванную программу. Удаление TSR-программы. ----------------------------------------------------------------- При удалении пользователем TSR-программы путем запуска ее копии с соответствующим параметром в командной строке, нерезидентная копия вызывает резидентную через коммуникационное прерывание. Функция terminate в resident.c проверяет, может ли быть снята программа путем просмотра, не изменились ли значения - 32 - векторов прерываний. Если изменились, выполнение TSR-программы приостанавливается. Если нет, она может быть снята. Для удаления TSR-программы необходимо проделать три процедуры. Сначала все файлы должны быть закрыты. Когда ДОС завершает программу, она проверяет все 20 элементов в массиве указателей файлов в PSP. Эта процедура закрывает все файлы на уровне указателей и не затрагивает потоковых файлов. Так как эти файлы должны быть закрыты, то terminate вызывает функцию closefiles в popup.c, закрывающую все открытые файлы. Вторая процедура - восстановление всех векторов прерываний в значение, которое они имели до присоединения их к себе TSR-программой. Завершающим шагом является освобождение всех блоков памяти, распределенных под TSR-программу. Память распределяется двумя способами - из ДОС и из программы через вызов функций ДОС. Эти два типа блоков памяти должны освобождаться тем же путем, которым и выделялись. Блоки памяти и управляющие блоки памяти. ----------------------------------------------------------------- Выделяемый ДОС блок памяти содержит 16-байтный блок управления памятью (БУП), следующий сразу за соответствующим распределяемым блоком памяти. Он содержит следующие поля: - однобайтный маркер, идентифицирующий БУП. Все, кроме последнего БУП в списке, имеют значение маркера 0x4d. Последний БУП имеет маркер 0x5a. - двубайтный идентификатор процесса, которому принадлежит блок памяти. Если блок свободен, это поле содержит 0. - 33 - - двубайтный размер блока памяти в параграфах. Размер БУП не учитывается в этом значении. БУП следует в памяти непосредственно за блоком памяти, который он представляет. Связки БУП-блок памяти располагаются в памяти смежно. Сегментный адрес следующего БУП равен адресу предыдущего БУП + размер блока памяти + 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 - 34 - #include #include #include static union REGS rg; unsigned sizeprogram = 48000/16; unsigned scancode = 52; unsigned keymask = 8; char *signature = "POPUP"; char notefile[64]; /*--------------------------------*/ int resident(char *,void interrupt(*)()); void resinit(void); void terminate(void); void restart(void); void wait(void); void resident_psp(void); void interrupted_psp(void); void exec(void); void cursor(int,int); void curr_cursor(int *,int *); main(argc,argv) char *argv[]; { void interrupt ifunc(); int ivec; if((ivec = resident(signature, ifunc)) != 0) { if(argc > 1) { rg.x.ax = 0; if(strcmp(argv[1],"quit") == 0) rg.x.ax = 1; else if(strcmp(argv[1],"restart") == 0) - 35 - rg.x.ax = 2; else if(strcmp(argv[1],"wait") == 0) rg.x.ax = 3; if(rg.x.ax) { int86(ivec, &rg, &rg); return; } } printf("\n Popup is already resident"); } else { /*load_help("tcprogs.hlp"); getcwd(notefile, 64); if(*(notefile+strlen(notefile)-1) != '\\') strcat(notefile,"\\"); strcat(notefile,"note.pad"); */ printf("\nResident popup is loaded"); resinit(); } } /*--------------------------------*/ void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax) { if(ax == 1) /* quit */ terminate(); else if(ax == 2) /* restart */ restart(); else if(ax == 3) /* wait */ wait(); } /*--------------------------------*/ /*void closefiles() { - 36 - extern FILE *helpfp; resident_psp(); if(helpfp) fclose(helpfp); interrupted_psp(); } */ /*--------------------------------*/ void popup() { int x,y; curr_cursor(&x, &y); exec(); cursor(x,y); } /*--------------------------------*/ void cursor(int x, int y) { rg.x.ax=0x0200; rg.x.bx=0; rg.x.dx=((y<<8) &0xff00) + x; int86(16,&rg,&rg); } /*--------------------------------*/ void curr_cursor(int *x, int *y) { rg.x.ax=0x0300; rg.x.bx=0; int86(16,&rg,&rg); *x=rg.h.dl; *y=rg.h.dh; } - 37 - Листинг 12.4. ------------- /*---- resident.c ----*/ #include #include static union REGS rg; static struct SREGS seg; static unsigned mcbseg; static unsigned dosseg; static unsigned dosbusy; static unsigned enddos; char far *intdta; static unsigned intsp; static unsigned intss; static char far *mydta; static unsigned myss; static unsigned stack; static unsigned ctrl_break; static unsigned mypsp; static unsigned intpsp; static unsigned pids[2]; static int pidctr = 0; static int pp; static void interrupt (*oldtimer)(); static void interrupt (*old28)(); static void interrupt (*oldkb)(); static void interrupt (*olddisk)(); static void interrupt (*oldcrit)(); static void interrupt (*ZeroDivVector)(); void interrupt newtimer(); void interrupt new28(); void interrupt newkb(); void interrupt newdisk(); void interrupt newcrit(); - 38 - extern unsigned sizeprogram; extern unsigned scancode; extern unsigned keymask; static int resoff = 0; static int running = 0; static int popflg = 0; static int diskflag = 0; static int kbval; static int cflag; void dores(),pidaddr(); /*------------------------------------------------*/ void resinit() { segread(&seg); myss=seg.ss; rg.h.ah=0x34; intdos(&rg, &rg); dosseg = _ES; dosbusy=rg.x.bx; mydta=getdta(); pidaddr(); oldtimer=getvect(0x1c); old28=getvect(0x28); oldkb=getvect(9); olddisk=getvect(0x13); setvect(0x1c,newtimer); setvect(0x9,newkb); setvect(0x28,new28); setvect(0x13,newdisk); stack=(sizeprogram - (seg.ds - seg.cs)) * 16 - 300; - 39 - setvect(0,ZeroDivVector); rg.x.ax=0x3100; rg.x.dx=sizeprogram; intdos(&rg, &rg); } /*------------------------------------------------*/ void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) { diskflag++; (*olddisk)(); ax=_AX; cx=_CX; dx=_DX; newcrit(); flgs=cflag; --diskflag; } /*------------------------------------------------*/ void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) { ax=0; cflag=flgs; } /*------------------------------------------------*/ void interrupt newkb() { if(inportb(0x60) == scancode) { kbval=peekb(0,0x417); if(!resoff && ((kbval & keymask) & keymask) == 0) { /* !!!!!!! & * / kbval=inportb(0x61); outportb(0x61,kbval|0x80); - 40 - outportb(0x61,kbval); outportb(0x20,0x20); if(!running) popflg=1; return; } } (*oldkb)(); } /*------------------------------------------------*/ void interrupt newtimer() { (*oldtimer)(); if(popflg && peekb(dosseg, dosbusy) == 0) if(diskflag == 0) { outportb(0x20,0x20); popflg=0; dores(); } } /*------------------------------------------------*/ void interrupt new28() { (*old28)(); if(popflg && peekb(dosseg, dosbusy) != 0) { popflg=0; dores(); } } /*------------------------------------------------*/ resident_psp() { intpsp=peek(dosseg,*pids); for(pp=0; pp < pidctr; pp++) - 41 - poke(dosseg,pids[pp],mypsp); } /*------------------------------------------------*/ interrupted_psp() { for(pp=0; pp < pidctr; pp++) poke(dosseg,pids[pp],intpsp); } /*------------------------------------------------*/ void dores() { running=1; disable(); intsp=_SP; intss=_SS; _SP=stack; _SS=myss; enable(); oldcrit = getvect(0x24); setvect(0x24,newcrit); rg.x.ax=0x3300; intdos(&rg, &rg); ctrl_break=rg.h.dl; rg.x.ax=0x3301; rg.h.dl=0; intdos(&rg, &rg); intdta=getdta(); setdta(mydta); resident_psp(); popup(); interrupted_psp(); setdta(intdta); setvect(0x24,oldcrit); rg.x.ax=0x3301; rg.h.dl=ctrl_break; intdos(&rg, &rg); - 42 - disable(); _SP=intsp; _SS=intss; enable(); running=0; } /*------------------------------------------------*/ static int avec=0; unsigned resident(signature, ifunc) char *signature; void interrupt (*ifunc)(); { char *sg; unsigned df; int vec; segread(&seg); df=seg.ds - seg.cs; for(vec=0x60; vec < 0x68; vec++) { if(getvect(vec) == NULL) { if(!avec) avec=vec; continue; } for(sg=signature; *sg; sg++) if(*sg!=peekb(peek(0,2+vec*4)+df,(unsigned)sg)) break; if(!*sg) return vec; } if(avec) setvect(avec, ifunc); return 0; } /*------------------------------------------------*/ - 43 - static void pidaddr() { unsigned adr=0; rg.h.ah=0x51; intdos(&rg, &rg); mypsp=rg.x.bx; rg.h.ah=0x52; intdos(&rg, &rg); enddos=_ES; enddos = peek(enddos, rg.x.bx-2); while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos<<4)) { if(peek(dosseg, adr) == mypsp) { rg.h.ah=0x50; rg.x.bx=mypsp+1; intdos(&rg, &rg); if(peek(dosseg, adr) == mypsp +1) pids[pidctr++]=adr; rg.h.ah=0x50; rg.x.bx=mypsp; intdos(&rg, &rg); } adr++; } } /*------------------------------------------------*/ static resterm() { /* closefiles();*/ setvect(0x1c,oldtimer); setvect(9,oldkb); setvect(0x28,old28); - 44 - setvect(0x13,olddisk); setvect(avec, (void interrupt (*)()) 0); rg.h.ah=0x52; intdos(&rg, &rg); mcbseg=_ES; mcbseg=peek(mcbseg, rg.x.bx-2); segread(&seg); while(peek(mcbseg, 0) == 0x4d) { if(peek(mcbseg, 1) == mypsp) { rg.h.ah=0x49; seg.es=mcbseg+1; intdosx(&rg, &rg, &seg); } mcbseg+=peek(mcbseg,3)+1; } } /*------------------------------------------------*/ terminate() { if(getvect(0x13) == (void interrupt (*)()) newdisk) if(getvect(9) == newkb) if(getvect(0x28) == new28) if(getvect(0x1c) == newtimer) { resterm(); return; } resoff=1; } /*------------------------------------------------*/ restart() { resoff=0; - 45 - } /*------------------------------------------------*/ wait() { resoff=1; } TSR-ПРОГРАММА - ПРИЛОЖЕНИЕ. ----------------------------------------------------------------- В popup.c функция popup вызывается, когда программа TSR-драйвера обнаруживает, что нажата клавиша "горячего ключа" и выполнение утилиты безопасно для ДОС._ .Функция popup сохраняет текущее положение курсора, вызывает exec, затем восстанавливает курсор и завершает выполнение._ .Функция exec - это вход в утилиту, в данном случае программа-пример из главы 10._ .Дополнительно к тому, что вы узнали о exec ранее, обратите внимание еще вот на что. Она работает точно так же как и нерезидентная menu.exe, но сейчас ее имя popup.exe, и она является TSR-программой. На листинге 12.5 дан файл проекта для построения popup.exe в Турбо _Си.. Листинг 12.5:popup.prj. popup (twindow.h) exec (twindow.h,keys.h) tetstmove (twindow.h,keys.h) promote (twindow.h,keys.h) ccolor (twindow.h,keys.h) fasttest (twindow.h) notepad (twindow.h) ordent (twindow.h) maxims (twindow.h,keys.h) poems (twindow.h,keys.h) editor (twindow.h,keys.h) entry (twindow.h,keys.h) - 46 - thelp (twindow.h,keys.h) tmenu (twindow.h,keys.h) twindow (twindow.h,keys.h) resident ibmpc.obj Чтобы запустить резидентную утилиту, построенную Турбо Си по этому проекту, введите следующую команду: C>popup Эта команда загрузит TSR-программу и оставит ее в памяти. При этом она выдаст сообщение: Resident popup is loaded. При попытке повторного запуска будет выдано сообщение: Popup is already resident. Когда программа резидентна, вы можете выполнять ее из командной строки для того, чтобы приостановить, продолжить выполнение или снять ее. Это делается следующими командами: C>popup wait C>popup restart C>popup quit ПРОВЕРКА TSR-ПРОГРАММ. ----------------------------------------------------------------- Если вы написали TSR-программу и хотите проверить ее как резидентную программу, ваши опыты могут разочаровать вас. Прежде всего у вас не будет возможности использовать Турбо Си для интерактивного тестирования. TSR-программа устанавливается из - 47 - командной строки и вызывается по нажатию клавиши. Далее, до тех пор, пока не заработает функция terminate, вам придется удалять ее путем перезагрузки. То же придется делать при загрузке других резидентных программ после нее. Так как ваша программа присоединяется к прерываниям, то ее выполнение может подвешивать вашу систему. Так что TSR-программу нелегко отлаживать как резидентную. Лучшее решение - это отлаживать TSR-программу как нерезидентную. Все интерфейсы с ДОС, необходимые для установки и действия TSR-программы, находятся в popup.c и resident.c. Почему бы не пропустить эти операции до тех пор, пока вы надежно не протестируете вашу программу? Как вы видите из примера, часть программы, выполняющая основные функции, была создана и протестирована отдельно без TSR-операций. По сути дела, эта часть используется как пример некоторых возможностей, не связанных с обсуждением TSR-программ. Вы можете тестировать свою программу таким же способом. Простейший путь тестировать вашу утилиту - это связать ее с корневой программой, обеспечивающей функцию main и любой начальный код, который вы потом включите в popup.c. Программа menu.c из главы 10 является хорошим примером. Другой способ - связать утилиту с popup.c и resident.c, как будто бы вы строите резидентную программу, но с одним изменением. Вместо вызова функции resinit из функции main в popup.c вставьте вызовы popup и closefile из popup.c.Программа будет иметь те же вид, структуру и размер, что и ее резидентная версия, но будет функционировать как нерезидентная программа. Используя этот способ, вы тестируете ее путем запуска. Вместо того, чтобы стать резидентной, программа действует, как будто бы был нажат "горячий ключ", выполняется один раз и завершается. После тестирования программы вы можете перекомпоновать свою - 48 - программу как TSR-программу и протестировать ее как резидентную. Начните с наивысшего значения переменной sizeprogram и уменьшайте ее, как описано в главе 11. Завершающий тест - это проверка, как ваша TSR-программа будет вести себя в окружении себе подобных. Этот тест может привести к непредвиденным результатам. Многие из популярных TSR-программ не могут выполняться вместе, поэтому вы должны выбрать те из них, которые совместимы друг с другом, и попробовать тесты, которые загружают различные TSR-программы в различной последовательности до тех пор, пока вы не добьетесь нормальной работы. Укажите этот порядок в установочную процедуру в руководстве пользователя по вашей программе. ВЫВОДЫ. ----------------------------------------------------------------- Используя программы из этой главы, вы будете иметь набор средств, который позволит вам писать резидентные утилиты на Турбо Си для IBM PC. Эти программы могут создаваться и тестироваться в интегрированной среде Турбо Си как нормальные нерезидентные программы для ДОС. Они могут использовать функции окон, ввода данных, текста и меню из предыдущих глав в этой книге. После доведения программ до рабочего состояния они могут быть интегрированы со средствами из этой главы для превращения их в полностью функционирующие TSR-программы. ЭПИЛОГ. ----------------------------------------------------------------- Хэккеры в сердце могут почуствовать проникновенность за работу, которая превратилась в программы из этой книги. Хэккер - это человек, который "разбирает" компьютерные системы для того, - 49 - чтобы узнать, как они работают. Этот термин в дальнейшем потерял свое значение ввиду его частого употребления в печати и общении, но мы предпочитаем его начальное значение. Программист, который интересуется техникой и принципами, лежащими в основе этих функций, обнаружит, что многие хэккеры обнаружили то же самое. Вы можете использовать эти средства без всяких вопросов. Но если вы пытливая натура, то это подтолкнет вас к дальнейшему проникновению во внутренности PC и ДОС. Если вы не работаете в Microsoft и не имеете исходных текстов каждой из версий ДОС (или времени на их изучение), то должны будете или сами разгадывать секреты ДОС или узнавать их от других. Мы советуем вам присоединиться к информационному обмену журнала Byte. Каждый выпуск журнала Byte содержит информацию, как это сделать. Там имеется больше полезных технических данных, чем во всех вместе взятых книгах, статьях журналов и руководствах, которые вы можете где-либо найти. Учитесь и используйте работу тех, кому посвящена эта книга.