Информационный сервер для программистов: Исходники со всего света. Паскальные исходники со всего света
  Powered by Поисковый сервер Яndex: Найдется ВСЁ!
На Главную Pascal Форум Информер Страны мира
   Клавиатура    >>    vituskbd
   
 
 Замена обработчика прерываний клавиатуры   Виктор Вагнер 22.11.1996

Кaк пpaвильно зaменить обpaботчик пpеpывaний клaвиaтуpы нa PC



2k 
 

{ To: netters From: "Victor B. Wagner" <vitus@agropc.msk.su> Subject: Re: (?) Кaк пpaвильно зaменить обpaботчик пpеpывaний клaвиaтуpы нa PC? Date: 22 Nov 1996 12:51:32 +0300 > Имеется погpaммa, в ней цикл ожидaния, условие выходa из него либо >пpиход aппapaтного пpеpывaния, либо мaнипуляции с клaвиaтуpой. > > while not PassStarted do begin (* assStarted отлaвливaет IRQ *) > if KeyPressed then begin (* KeyPressed - стaндapтнaя ф-ция, *) > PassStarted:=false; (* отлaвливaет нaжaтие клaвиши *) > exit; > end; > end; > > НО! В дaнном вapиaнте выход из циклa ожидaния пpоисходит зa вpемя ~10-20 >мксек с сеpьезным paзбpосом (пpедположение, обосновaнное, но не пpовеpялось), > котоpое зaвисит, по-видимому от того нa кaком учaстке исполнялaсь ф-ция >KeyPressed в момент пpиходa IRQ. Попpобовaл отлaвливaть нaжaтие клaвиши >нaпpямую, пеpехвaтывaя пpеpывaние $09. KeyPressed вызывает функцию BIOS (INT 16H AH=01H) для проверки наличия символа в буфере. Можно сэкономить очень много, обращаясь к буферу клавиатуры непосредственно. Это делается так: var KBDHead: word absolute [$0040:$001A] var kbdTail: Word absolute [$0040:$001C] {Я мог голову с хвостом и перепутать. Для данной задачи это не важно, но рекомендую проверить по любой книжке, где описана область данных BIOS} ............... While not PassStarted do begin if KbdTail<>KbdHead then begin PassStarted:=False; exit; end; end; ............. Надо только не забыть прочитать символ из буфера клавиатуры, а то при повторном входе в цикл выход произойдет сразу же. Кстати, прямой доступ к буферу клавиатуры открывает неожиданные возможности: Например, он позволяет отличить Enter на цифровой клавиатуре от обычного и обрабатывать Alt-стрелки. Вариант с перехватом 9-го прерывания имеет свои преимущества. Например, выход из цикла может происходить в результате нажатия клавиши, которая кода не генерирует и в буфер клавиатуры ничего не пишет, например Alt. Но приведенный в письме обработчик содержит ряд ошибок: [skipped] > Обpaботчик пpеpывaния имеет следующий вид: > >{$F+} > procedure KeyDown; Обработчик прерывания ОБЯЗАН быть описан как Interrupt. Собственно, причина зависания именно в этом. В конце процедуры выполняется RETF, а не IRET, и, следовательно, в стеке остается лишнее слово - регистр флагов, которые туда был положен при вызове прерывания. Можно, конечно, извратиться и написать procedure KeyDown(dummy:integer); заставив процедуру рассматривать это слово как параметр, и выполнять RETF 2. Но кто будет заботится о сохранности ВСЕХ регистров и флагов? Interrupt это делает. Короче, писать надо так: procedure KeyDown;interrupt; > begin > asm CLI end; > inline ($9C); { PUSHF -- Push flags } Почему не asm CLI PUSHF end По-моему смешение в одной процедуре кусков asm и inline - дурной тон, если это не вызвано какой-то необходимостью. Я бы вообще написал где-нибудь перед ней procedure CLI;inline (...); procedure PushF; inline($9C); что сделало бы код более читаемым. > OldIntKbdVector; > KeyFlag:=true; Если вызывается OldIntKbdVector, то нет никакой необходимости в нижеследующей ассемблерной вставке. В старом обработчике это все равно сделано. Единственное, к чему может привести повторный сброс контроллера прерываний, это потеря СЛЕДУЮЩЕГО аппаратного прерывания. (Какое оно будет - таймер, клавиатура или что еще - это уж как повезет) > asm > MOV AL, $20 > OUT $20, AL > MOV AL, $20 > OUT $A0, AL > STI > end; > end; >{$F-} Вообще, писать обработчики прерываний на Pascal дело довольно опасное. Рекомендую посмотреть в Turbo Debugger сколько кода генерирует стандартный заголовок interrupt и сколько места в стеке используется при этом. Надо помнить что при работе вашей программы далеко не всегда активным стеком является ваш стек. Может быть, что обработчик аппаратного прерывания будет вызван в момент когда активна функция DOS и используется стек DOS. Тогда каждый лишний байт в стеке может оказаться смертельным. Поэтому я в случае аналогичных простых обработчиков использую не непосредственный вызов, а функцию (inline) JumpToOldISR из Turbo Professional. Она мне экономит как минимум 6 байт в стеке. В случае сложных обработчиков я пользуюсь явным переключением стека (SwapStackAndCall из той же Turbo Professional) Вообще, я бы написал обработчик для данной ситуации вот так: function KeyDown;assembler; asm cli push AX push DS mov AX,@DATA mov DS,AX mov ax,True mov KeyFlag,1 pushf call dword ptr OldIntKbdVector pop DS pop ax pop BP;{Для компенсации стандартного startup-кода assembler процедуры} iret{ и явный выход по iret} end; По сравнению с процедурой, описанной как Interrupt, это экономит место в стеке за счет того, что не сохраняются те регистры которые не используются в обработчике. Команда sti отсутствует потому, что iret все равно восстановит флаги (в том числе и IF) из стека. > Все компилиpуется, но после пеpвого же нaжaтия нa клaвишу мaшинa виснет! :-( > > Подскaжите кaк пpaвильно подменить обpaботчик, либо еще кaк-нибудь отловить >мaнипуляции с клaвиaтуpой, чтобы выход из циклa пpоисходил зa ФИКСИPОВAННОЕ >вpемя, ну или с ооочень мaленьким paзбpосом ~1 мксек. > >Зapaнее блaгодapен, Aлексей.