http://sulfurzona.com/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
 
 

Паскаль для новичков (часть 42)

 

Переопределение прерываний (продолжение)

 
Автор: Владислав Демьянишин
 
Паскаль для новичковПри завершении явного блока процедуры значения этих переменных заносятся в соответствующие регистры процессора, чтобы мог действовать механизм возврата результата. На самом деле неявная завершающая часть блока процедуры извлекает значения этих сохранённых регистров из стека и заносит их в соответствующие регистры процессора. Затем для возврата из процедуры обработчика прерывания выполняется машинная команда IRET, которая извлекает из стека значение адреса команды прерванной программы и заносит их в регистровую пару CS:IP, а так же извлекает из стека значение регистра флагов Flags.
  
Заголовок процедуры обработчика должен завершаться служебным словом interrupt.
  
Такая процедура всегда компилируется как подпрограмма дальнего вызова. Эквивалентный ассемблерный код компилируемой процедуры обработчика прерывания выглядит так:
 
{Начало неявного блока. Сохранение значений регистров в стек}
PUSH   AX
PUSH   BX
PUSH   CX
PUSH   DX
PUSH   SI
PUSH   DI
PUSH   DS
PUSH   ES
PUSH   BP
{Сохранение вершины стека в регистре BP и отведение памяти стека под локальные переменные из var-блока}
MOV    BP, SP
SUB      SP, LocalSize
{Инициализация регистра DS значением адреса сегмента данных программы обработчика}
MOV    AX, SEG DATA
MOV    DS, AX
{Явный блок процедуры обработчика}
{Неявная завершающая часть блока процедуры выравнивает вершину стека}
MOV    SP, BP
{Восстановление значений сохранённых регистров и извлечение их из стека}
POP    BP
POP    ES
POP    DS
POP    DI
POP    SI
POP    DX
POP    CX
POP    BX
POP    AX
{Возвращение из процедуры обработчика}
IRET
 
При написании процедуры обработчика аппаратного прерывания следует избегать использования операторов ввода-вывода на диск, распределения памяти, и функций MS-DOS ввиду их нереентерабельности.
  
Это пока всё, что касается написания самой процедуры обработки.
Теперь осталось лишь выяснить, как переопределить прерывание.
На самом деле всё не так и сложно. При выполнении команды вызова прерывания процессор обращается к таблице векторов прерываний, которая традиционно, ввиду архитектурных особенностей процессора INTEL80x86 расположена в первом килобайте адресного пространства оперативной памяти. Таблица векторов содержит адреса 256 процедур обработки прерываний. Каждый адрес занимает 4 байта и является структурой сегмент:смещение (CS:IP) и называется вектором прерывания. Традиционно сначала следует двухбайтное значение смещения, а за ним значение сегмента адреса.
  
При загрузке системы из ROM BIOS код инициализации выполняет настройку таблицы векторов прерываний на все необходимые обработчики. Задействованные, но ничего не выполняющие обработчики обычно состоят из одной команды IRET. Адреса же незадействованных векторов содержат, обычно, нуль во всех четырёх байтах адреса в таблице векторов. Когда загружается MS-DOS, она в свою очередь, тоже инициализирует вектора некоторых прерываний на собственные обработчики.
  
Чтобы переопределить вектор прерывания на собственный обработчик, программе достаточно просто записать четырёхбайтный адрес новой процедуры обработки в ячейку с адресом IntNo*4, где IntNo – номер вектора переопределяемого прерывания.
  
Чтобы программисту не пришлось каждый раз изобретать велосипед, разработчики Turbo Pascal предусмотрели специальную процедуру для установки адреса нового обработчика:
 
procedure SetIntVec( IntNo : byte; Vector : pointer );
 
и процедуру для получения адреса старого обработчика, на тот случай, если перед завершением программы его нужно будет восстановить.
 
procedure GetIntVec( IntNo : byte; Vector : pointer );
 
Следующий пример иллюстрирует простой способ обработать прерывание клавиатуры таким образом, чтобы перехватить нажатие клавиши и затем передать обработку события стандартному обработчику клавиатуры. При запуске программа, первым делом, сохраняет старый вектор прерывания, и устанавливает вектор на собственный обработчик. Затем выполняет некоторые действия, пока ожидает нажатия клавиши ESC. После перехвата нажатия клавиши ESC программа восстанавливает вектор на старый обработчик.
 
Uses dos;
 
const ctEsc = 1;
 
var Int9_old : procedure;
       LastKey : byte;
 
Procedure MyInt9; interrupt;
Begin
LastKey := Port[$60];
asm pushf end;
Int9_old;
End;
 
begin
GetIntVec( 9, @Int9_OLD );
SetIntVec( 9, @MyInt9 );
while ( LastKey and $7F ) <> ctESC do begin
          …
          end;
SetIntVec( 9, @Int9_OLD );
end.
 

Практикум

 
Теория хороша лишь для теоретиков. На практике придётся столкнуться с множеством нюансов.
  
Кому не интересна тема резидентных программ, могут спокойно пойти на кухню и приготовить себе чайок с лимоном, или кофе. Заинтересовавшихся попрошу остаться, запастись терпением, освободиться от посторонних мыслей, дабы быть в полном сосредоточении для усвоения следующего материала.
 

Пишем собственное сервисное прерывание

 
Допустим, перед нами стоит задача обеспечить между программами обмен текстовой строкой. Такой себе буфер обмена аля Windows Clipboard.
  
Для этого нам понадобится написать резидентную программу, которая будет обеспечивать работу сервисного прерывания, например, с номером $B0 со следующей спецификацией интерфейса:
  
Отклик сервиса, чтобы дать понять внешней программе, присутствует сервис Clipboard в памяти или нет. Для этого внешней программе достаточно загрузить в регистр AX нуль, и вызвать прерывание сервиса. Сервис должен возвратить в регистре AX значение $1212. Любое другое значение – сервис отсутствует.
  
Запись текстовой строки в Clipboard. Внешняя программа должна поместить единицу в регистр AX, в регистровую пару ES:DI поместить адрес записываемой строки и вызвать прерывание сервиса.
  
Чтение текстовой строки из Clipboard. Внешняя программа должна поместить двойку в регистр AX и вызвать прерывание сервиса. Сервис должен в регистровой паре ES:DI вернуть адрес читаемой строки.
 
В качестве нашего сервисного прерывания будем использовать программное прерывание. Программные прерывания, в отличие от аппаратных, ничего не прерывают, а лишь вызывают часто используемые процедуры. Они вызываются ассемблерной командой int с номером прерывания от 0 до $0ff. Команда int занимает всего 2 байта, в отличие от пятибайтного далекого вызова. Аппаратные прерывания также могут вызываться таким путем, для irq0-irq7 предоставлены номера int 8-int $0f, а для irq8-irq15 - int $70-int $77.
 
Итак, вот листинг создаваемого сервиса. Код процедуры обработчика прерывания достаточно понятен.
  
Перейдём сразу к основному блоку программы. После завершения выполнения своего основного блока программа должна остаться в памяти резидентно. Для этого в Turbo Pascal предусмотрена стандартная процедура
 
procedure Keep( ExitCode : Word );
 
которая, как и стандартная команда Halt позволяет завершить выполнение программы, но при этом оставляет эту программу в памяти, то есть резидентно.
  
Оставляя программу резидентной, следует помнить, что при её запуске ей была отведена вся свободная память. И если оставить программу резидентной, то запустить другую программу уже не представится возможным. Поэтому следует директиву $M настроить продуманно. А поскольку наша резидентная программа не будет использовать динамическую память, то следует отказаться от неё и вовсе. Стек нам нужен небольшой.
 
Program ClpBrd.pas;
 
 
 
 
{$M 1024,0,0}
uses dos;
 
var ClipBoardStr : string;
 
procedure ClipBoardService( RegAX, UnuseBX, UnuseCX, UnuseDX, UnuseSI, RegDI, UnuseDS, RegES, UnuseBP : word ); interrupt;
var TmpStr : ^String;
begin
 case RegAX of
 0 : RegAX := $1212; {Init clipboard}
 1 : begin   {Write to clipboard}
       TmpStr := ptr( RegES, RegDI );
       ClipBoardStr := TmpStr^;
       end;
 2 : begin   {Read from clipboard}
       RegES := Seg( ClipBoardStr );
       RegDI := Ofs( ClipBoardStr );
       end;
 end;
end;
 
begin
ClipBoardStr := '';
SetIntVec( $B0, @ClipBoardService );
SwapVectors;
keep( 0 );
end.
 
Основной блок программы выполняет инициализацию строки ClipBoardStr пустой строкой, командой SetIntVec( $B0, @ClipBoardService ) устанавливает новый обработчик прерывания на сервисный, восстанавливает стандартное системное окружение командой SwapVectors и завершает программу оставляя её резидентной при помощи команды keep(0).
 
Теперь напишем программу, которая будет записывать строку в Clipboard. Сначала составим функцию InitClipBoard для обнаружения сервиса Clipboard. Для этого достаточно проверить, инициализирован ли вектор искомого прерывания. Если так, то следует вызвать функцию отклика сервиса, которая должна вернуть заветное значение $1212, иначе никакого сервиса нет.
  
Процедура WriteStrToClipBoard для записи строки в Clipboard довольно проста. Главное следовать принятой выше спецификации.
 
Program SetClp.pas;
uses dos;
 
var S : string;
 
function InitClipBoard : boolean;
var Reg : Registers;
       p : pointer;
begin
InitClipBoard := false;
GetIntVec( $B0, p );
if p = nil then exit;
Reg.AX := 0;
intr( $B0, Reg );
if Reg.AX = $1212 then InitClipBoard := true;
end;
 
procedure WriteStrToClipBoard( AStr : string );
var Reg : Registers;
begin
Reg.AX := 1;
Reg.ES := Seg( AStr );
Reg.DI := Ofs( AStr );
intr( $B0, Reg );
end;
 
begin
if not InitClipBoard then begin
   writeln( 'Error: ClipBoard driver not found' );
   halt;
   end;
if ParamCount = 0 then
   WriteStrToClipBoard( 'Hello users!' )
   else WriteStrToClipBoard( paramstr( 1 ) );
end.
 
Основной блок программы SetClp.pas выполняет проверку наличия используемого сервиса. В случае неудачи программа завершается. При успехе программа считывает строку параметра, с которым была запущена. Для этого применяется стандартная функция ParamStr. Если строка не пустая, то она записывается в Clipboard. Иначе, по умолчанию в Clipboard записывается строка 'Hello users!'.
 
Осталось написать программу, которая будет читать строку из Clipboard. Рассмотрим. Функция InitClipBoard была описана выше.
Функция ReadStrFromClipBoard для получения строки из Clipboard не требует особых разъяснений.
 
Program GetClp.pas;
uses dos;
 
function InitClipBoard : boolean;
end;
 
function ReadStrFromClipBoard : string;
var Reg : Registers;
       S : ^String;
begin
Reg.AX := 2;
intr( $B0, Reg );
S := ptr( Reg.ES, Reg.DI );
ReadStrFromClipBoard := S^;
end;
 
begin
if not InitClipBoard then begin
   writeln('Error: ClipBoard driver not found');
   halt;
   end;
writeln(ReadStrFromClipBoard);
end.
 
Основной блок программы GetClp.pas после успешной проверки присутствия используемого сервиса считывает строку из сервиса Clipboard и выводит её на экран.
 
Чтобы убедиться в работоспособности разработанного нами сервиса, достаточно откомпилировать эти три программы. Первой следует запустить программу ClpBrd.exe. После этого сервис будет готов к работе. Затем достаточно запустить программу SetClp.exe без параметра, которая запишет в Clipboard строку 'Hello users!'. После этого запустив программу GetClp.exe можно будет увидеть на экране всё ту же строку 'Hello users!'. Таким образом, наше сервисное прерывание справилось с задачей на “отлично”.
 

Пишем блокиратор клавиатуры

 
Хочу на простом примере продемонстрировать, как можно заблокировать нажатия некоторых клавиш при помощи резидентной программы, частично перехватывающей прерывания, поступающие от клавиатуры.
  
Сначала читателей следует ознакомить с технической стороной дела, от которой прямо зависит алгоритмическая реализация данной задачи.
  
В рамках МК я не имею возможности рассказать о работе оборудования компьютера, и в частности о контроллерах клавиатуры и прерываний во всех аспектах. Для этого вам следует обратиться к более полному руководству в списке литературы. Я лишь постараюсь затронуть те необходимые детали, которые помогут вам понять данный материал.
  
Ранее вниманию читателей МК (см. главу “Запряжём клаву”
МК №17/240/ за 28.04.2003) я предлагал описание исходного кода модуля для работы с клавиатурой, где приводилась таблица ASCII кодов клавиш клавиатуры. Данные коды являются лишь интерпретацией истинных кодов нажатия клавиш клавиатуры. Это сделано для упрощения работы программ.
  
При нажатии или отпускании клавиши контроллер клавиатуры (микросхема Intel 8042) посылает контроллеру прерываний (микросхема Intel 8259) сигнал запроса на прерывание работы центрального процессора с тем, чтобы процессор немедленно прочёл из порта контроллера клавиатуры скан-код нажатия/отпускания клавиши. Для этого достаточно прочесть скан-код клавиши из порта $60 (порт A). После этого, необходимо послать контроллеру клавиатуры сигнал подтверждения того, что скан-код был успешно прочитан, иначе контроллер будет ожидать этого сигнала до бесконечности и ввод с клавиатуры будет заморожен (короче поможет только Reset). Чтобы послать такой сигнал, достаточно на очень короткое время установить бит 7 порта $61 (порт B) в единицу, и тут же сбросить его в ноль. Но и это ещё не всё.
  
Поскольку данное аппаратное прерывание находится в процессе выполнения, то контроллер прерываний в это время не может принимать другие запросы на прерывание работы процессора. Он будет попросту игнорировать поступающие к нему запросы, пока не получит подтверждения о том, что критическая часть кода процедуры обработки аппаратного прерывания завершена. Послать такой сигнал (End Of Interrupt - EOI) следует в виде значения $20 в порт контроллера прерываний с названием Interrupt Service Register (ISR).
  
В компьютере установлены два контроллера прерываний. Один из них ведущий, а другой ведомый. За счёт этого возможны 16 уровней запросов на прерывание (Interrupt ReQuest - IRQ) от различных устройств компьютера. Поскольку запрос на прерывание от клавиатуры ввиду конструктивных особенностей поступает на первый (ведущий) контроллер прерываний и его базовый порт $20, то сигнал подтверждения окончания обработки аппаратного прерывания EOI следует послать в порт $20, иначе дальнейшие аппаратные прерывания будут игнорироваться, что неизбежно приведёт, например, к полной блокировке ввода с клавиатуры.
 
Продолжение следует…
 
© Владислав Демьянишин
 

Литература

1. Р. Джордейн. Справочник программиста персональных компьютеров типа IBM PC, XT и AT. – М.: Финансы и статистика, 1992. – 543 с.
2. Диалоговая справочная система Norton Guide.
3. Пpогpаммно-технические сpедства пеpсональных ЭВМ семейства IBM PC (описание портов)
4. Дмитрий Меламуд "TSR и нерезидентные обработчики прерываний" + Шеховцов Александр "Уменьшение размера резидентных программ, написанных на Turbo-Pascal 6.0" + Демьянишин Владислав "Примеры резидентных программ на Turbo Pascal".
 
Вы находитесь на официальном сайте Владислава Демьянишина - разработчика игры Dune IV (Dune 4). На нашем сайте можно бесплатно скачать игры Dune IV (Dune 4), Battle City (Танчики с Dendy/Nintendo), читы к играм и многое другое. Также Вы можете скачать бесплатно программы и полезные утилиты. Среди доступных программ есть мобильная читалка книг, менеджер переноса файлов с фото- и видеокамер на компьютер, текстовый редактор, WYSIWYG редактор, 3D аниматор, GIF аниматор, AVI аниматор, пакетный конвертор изображений, редактор электрических схем, программа для скриншотов, диспетчер тем рабочего стола и другие. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 42): Переопределение прерываний (продолжение)
 
 
 
32
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Рейтинг@Mail.ru Рейтинг Сайтов YandeG