К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лексей.