Страница:
---------------------------------------------------------------
Date: 14 Apr 1997
From: Kazennov Vladimir
Перевод Владимира Казеннова
---------------------------------------------------------------
Драйверы устройств, как правило, - наиболее критическая часть
программного обеспечения компьютеров. По иронии судьбы это также и
наиболее скрытая часть разработки программного обеспечения. Драйверы
устройств системы Windows фирмы Microsoft не являются исключением.
Если вы когда-либо писали обычное приложение в системе Windows, то
вам известно, что требуется определенное количество скрытых способов,
чтобы приложение работало надежно. Как подмножество приложений
Windows, драйверы устройств системы Windows следуют этому же правилу.
В данной статье автор рассматривает работающий драйвер устройства,
который обеспечивает доступ к портам ввода-вывода и обрабатывает
прерывания, и виртуальный драйвер устройства (VxD), который имитирует
технические средства. Предполагается, что читатель знает основы
программирования в системе Windows, включая библиотеки динамической
связи (dynamic link libraries - DLLs).
Рассматриваемое устройство - это не часть технических средств,
которая была разработана, чтобы продемонстрировать, как писать
драйвер устройства в системе Windows. Скорее, это виртуальное
устройство, полностью реализованное в программном обеспечении.
Программа-пример выполняется только с виртуальным устройством,
которое автор определил работая с системой Windows в расширенном
режиме (Enhanced mode) процессора 386, и при условии, что установлен
виртуальный драйвер устройства (VxD). Далее в статье более детально
будет описан исходный код для этого устройства. На данный момент
следует знать, что устройство имеет два порта: порт состояния и порт
управления, оба на одном и том же адресе. На рис. 1 показаны биты,
используемые в порте состояния. Бит 2 указывает, что имела место
ошибка устройства, бит 1 показывает, что запрос на прерывание
является отложенным, а бит 0 указывает, что устройство занято. Бит 7
говорит о том, что устройство есть в наличии. В этом случае данный
бит равен нулю. Если же устройство не установлено или к нему нет
доступа, то бит принимает значение, равное 1.
+--------------------------------------------------------+
| 7 6...3 2 1 0 |
+---------+-------------------+---------+-------+--------+
| PRESENT | | ERROR | IRQ | BUSY |
+---------+-------------------+---------+-------+--------+
PRESENT - устройство есть в наличии;
ERROR - произошла ошибка устройства;
IRQ - прерывание отложено;
BUSY - устройство занято.
(Остальные биты игнорируются для дальнейшей совместимости.)
Рис. 1. Биты порта состояния устройства
На рис. 2 показаны биты, используемые в порте управления. Бит 1
указывает устройству, что ЦПУ закончило обработку прерывания. Бит 0
показывает, что устройство может начать обработку ввода-вывода. (В
данный момент не следует заострять внимание на том, что фактически
устройство делает. Вместо этого, необходимо уделить внимание тому,
как написать драйвер для такого устройства, которое обеспечивает
аппаратные прерывания.)
+--------------------------------------------------------+
| 7...2 1 0 |
+---------------------------------------+-------+--------+
| 1 1 1 1 1 1 | EOI | START |
+---------------------------------------+-------+--------+
EOI - сигнал для устройства, подтверждающий прием прерывания;
START - сигнал для устройства начинать пересылку ввода-вывода.
(Остальные биты должны быть установлены в 1 для дальнейшей
совместимости.)
Рис. 2. Биты порта управления устройства
На листинге 1 показана программа dostest.asm, представляющая
собой обычный драйвер устройства для системы MS-DOS, который общается
с устройством. Несмотря на простоту и малый размер данная программа
содержит основные компоненты драйвера устройства, который
обрабатывает прерывания.
_____________________________________________________________________
page ,132
; masm tisr ; >err
.286p
.xlist
include ..\..\include\bogus.inc
.list
Words struc
LoWord dw ?
HiWord dw ?
Words ends
EOI equ 020h ; команда EOI для контроллера PIC
INTA00 equ 020h ; управление главным контроллером PIC
INTA01 equ 021h ; регистр маски главного контроллера PIC
INT_MASTER_0 equ 08h ;номер INT главн. контроллера PIC
INTB00 equ 0A0h ; управление подчиненным контроллером PIC
INTB01 equ 0A1h ;регистр маски подчиненного контроллера PIC
INT_SLAVE_0 EQU 70h ; номер INT подчиненного контроллера PIC
;
; Установить переменные для нашего номера прерывания
;
ife (FAKE_IRQ GE 8)
INT_DEV equ (INT_MASTER_0+(FAKE_IRQ AND 7))
PIC00 equ INTA00
PIC01 equ INTA01
else
INT_DEV equ (INT_SLAVE_0+(FAKE_IRQ AND 7))
INT_MASK equ 1 SHL (FAKE_IRQ AND 7)
PIC00 equ INTB00
PIC01 equ INTB01
endif
page
CONST SEGMENT DWORD PUBLIC 'DATA'
sdNoBogus db 'I do not see the bogus device.',Odh,Oah,'$'
sdPrompt db Odh,Oah,'S)tart, or Q)uit: ','$'
sdCRLF db Odh,Oah,'$'
sdDot db '.','$'
CONST ENDS
DATA SEGMENT DWORD PUBLIC 'DATA'
dwCount1 dw 0
dwCount2 dw 0
lpPrevISR dd 0 ; адрес предыдущей программы ISR
fStopping db 0 ; значение TRUE при завершении
DATA ENDS
STACK SEGMENT DWORD STACK 'STACK'
db 512 dup (?)
STACK ENDS
DGroup GROUP CONST,DATA,STACK
page
;IP IntSvcRtn - The Interrupt Service Routine (Программа обслуживания
; прерывания)
; WARNINGS (предупреждения)
;
; NOTES (примечания)
; Данная программа ISR увеличивает счетчик прерываний (dwCount1)
; и заново маскирует устройство.
;
; Если установлен флаг "fStopping", устройство не маскируется заново.
;
FIXED_TEXT SEGMENT PARA PUBLIC 'CODE'
segData1 dw DGroup
assume CS:FIXED_TEXT,DS:NOTHING
IntSvcRtn proc far
push ax
push dx
push ds
mov ds,segDatal
assume ds:DGroup
inc dwCount1
mov al,NOT FAKE_CTL_EOI
mov dx,FAKE_PORT
out dx,al ; послать EOI устройству
mov al,EOI
out PIC00,al ; послать EOI контроллеру PIC
ife (PIC00 EQ INTA00)
out INTA00,al ; послать EOI главн. контроллеру PIC также
endif
cmp fStopping,0 ; существует?
jnz isr9 ; если да, то не нужен перезапуск
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ; перезапуск ввода-вывода
isr9:
pop ds
assume ds:NOTHING
pop dx
pop ax
iret
IntSvcRtn endp
FIXED_TEXT ENDS
page
;IP_main - точка входа в программу
; NOTES (примечания)
; Драйвер устройства выдает для пользователя приглашение:
;S)tart(начать) или Q)uit(выйти). Если пользователь нажимает S,
;программа разрешает прерывания и вооружает устройство, печатая точку
;каждый раз, как устройство прерывается.
;
_TEXT SEGMENT PARA PUBLIC 'CODE'
segData2 dw DGroup
segfixed dw FIXED_TEXT
assume cs:_TEXT,ds:NOTHING
_main label far
mov ds,segData2 ;инициализируется сегмент данных по умолчанию
assume ds:DGroup
mov dx,FAKE_PORT
in al,dx ; присутствует ли фиктивное устройство?
or al,al
jns m10 ;пропустить, если да
mov dx,OFFSET DGroup:sdNoBogus
mov ah,9
int 21h ; в противном случае печатать сообщение об ошибке
mov ax,4C01h
int 21h ; и выйти из системы
m10:
mov ax,3500h+INT_DEV
cli
int 21h ; запросить текущую программу ISR
mov lpPrevISR.LoWord,bx
mov lpPrevISR.HiWord,es ; сохранить ее
mov dx,OFFSET FIXED_TEXT:IntSvcRtn
push ds
mov ds,segFixed
assume ds:NOTHING
mov ax,2500h+INT_DEV
int 21h ; установить нашу программу ISR
pop ds
assume ds:DGroup
sti
mov dx,OFFSET DGroup:sdPrompt
mov ax,9
int 21h ; S)tart или Q)uit
ml1:
mov dl,0PFh
mov ah,6
int 21h ; читать с консоли, не ожидая
jz ml3
or al,40h
cmp al,'q'
je ml8 ; пропустить, если нажато "Q"
cmp al,'s'
jne ml3 ; пропустить, если не нажато "S"
cli
in al,PIC01 ; размаскировать прерывание
and al,NOT INT_MASK
out PIC01,al
sti
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ; начать ввод-вывод с устройства
ml3:
mov ax,dwCount1
cmp ax,dwCount2
je ml4 ; пропустить, если счетчик прерываний не изменился
mov dwCount2,ax
mov dx,OFFSET DGroup:sdDot
mov ah,9
int 21,h ;в противном случае выдать точку
ml4:
jmp ml1 ; цикл
ml8:
mov fStopping,1 ; указание для программы ISR завершить работу
mov dx,FAKE_PORT
ml9:
in al,dx
rcr al,1
jnc ml9 ; цикл, если занято
cli
in al,PIC01
or al,INT_MASK
out PIC01,al ; маскировать уровень прерывания
sti
push ds
lds dx,lpPrevISR
assume ds:NOTHING
mov ax,2500h+INT_DEV
int 21h ; восстановить предыдущую программу ISR
pop ds
assume ds:DGroup
mov ax,4C00h
int 21h ; выход
_TEXT ENDS
end_main
; Конец файла
_____________________________________________________________________
Драйвер устройства начинает работу с проверки старшего бита
порта состояния, чтобы убедиться в наличии устройства. Затем он
устанавливает связь с вектором прерывания MS-DOS для прерывания 11.
Драйвер сохраняет предыдущее значение, хранимое в этом векторе, так
чтобы можно было заменить значение, если программа существует.
Далее драйвер устройства выдает приглашение для пользователя :
Start(начать) или Quit(выйти). Если пользователь нажимает S,
программа начинает пересылку ввода-вывода. Если пользователь нажимает
Q, то программа отключает устройство, восстанавливает вектор
прерывания и завершается.
Чтобы начать операцию ввода-вывода, драйвер MS-DOS сначада
размаскирует программируемый контроллер прерываний (programmable
interrupt controller - PIC) для уровня прерывания устройства (в
примере прерывание 11). Затем драйвер начинает операцию ввода-вывода
для устройства путем записи 1 в бит 0 порта управления. Так как
прерывания включены, то при возникновении прерываний на устройстве
получит управление программа обслуживания прерываний (interrupt
service routine - ISR).
Если происходит прерывание на устройстве, то программа ISR
подтверждает прием прерывания, посылая значение EOI устройству (т.е.
записывая 1 в бит 1 порта управления устройства) и контроллеру PIC.
Если программа, выполняющая ввод-вывод, существует, то программа ISR
выполняется. В противном случае программа ISR осуществляет
инициализацию пересылки ввода-вывода вновь, записывая 1 в бит 0 порта
управления устройства. Итак, программа ISR возобновляет ввод-вывод
всякий раз, когда происходит прерывание, таким образом устройство
непрерывно выполняет операцию ввода-вывода. Кроме обеспечения
непрерывного ввода-вывода программа ISR увеличивает счетчик (dwCount1)
всякий раз, когда обрабатывает прерывание.
В процессе выполнения ввода-вывода программа следит за счетчиком
прерываний, отображает точку (".") для каждой законченной пересылки
ввода-вывода и продолжает сканировать клавиатуру, чтобы определить,
хочет ли пользователь остановить пересылку.
Чтобы завершить программу, пользователь нажимает клавишу Q.
Программа устанавливает флаг, который информирует программу ISR о
том, что следует остановить обработку. После того, как операция
ввода-вывода остановлена, программа маскирует уровень прерывания в
контроллере PIC и восстанавливает вектор прерывания.
Чрезвычайно тривиальный драйвер устройства MS-DOS, описанный
в предыдущем разделе, по существу довольно сложно реализовать в
системе Windows. При написании драйвера устройства в системе Windows,
обрабатывающего прерывания, необходимо использовать архитектуру,
отличную от той, которая была использована для драйвера MS-DOS. В
частности, необходимо отделить компоненту обработки прерывания от
компоненты приложения. Вместо единственной программы, управляющей как
программой ISR, так и интерфейсом пользователя, как сделано в системе
MS-DOS, в системе Windows необходимо выделить эти функции в отдельные
программные модули, называемые библиотекой динамической связи (DLL) и
интерфейсом прикладных программ (Application Program Interface - API).
При написании приложений в системе Windows обычно в программном
модуле имеют дело только с двумя типами сегментов: перемещаемым
(moveable) и выгружаемым (discardable). Сегменты данных программы
являются перемещаемыми, т.е. их линейные адреса в памяти могут
изменяться, когда программе управления памятью системы Windows
требуется организовать память. Селектор (selector) и смещение,
используемые для доступа к определенной ячейке памяти, остаются
фиксированными, но под схемой селектор-смещение система Windows
может перемещать фактические данные в линейной памяти.
Сегменты программ-кодов также перемещаемые, но имеют
дополнительный атрибут - выгружаемые. Их содержимое может быть
выгружено полностью, а при необходимости загружено с диска, так как
нельзя писать и (или) модифицировать информацию в сегменте
программы-кода. Если при обращении к сегменту из программы Windows,
он оказался выгруженным, программа управления памятью системы Windows
автоматически обратится к диску и прочитает ранее выгруженный сегмент.
Итак, каким образом это обстоятельство влияет на код для
программы ISR? Так как прерывание может произойти в любое время, а
код ISR может оказаться выгруженным, то возникнет проблема загрузить
код в память, если фиксируется прерывание. Вместо этого, можно
описать сегмент как FIXED (фиксированный), а не как MOVEABLE
(перемещаемый) или DISCARDABLE (выгружаемый). Сегмент с атрибутом
FIXED будет оставаться в единственном месте линейной памяти и не
будет выгружаться, даже если он содержит код. В этом случае, если
произойдет прерывание, код будет доступен и готов к выполнению.
Однако следует отметить один малоизвестный факт, а именно: в системе
Windows только те сегменты будут считаться FIXED, которые были
описаны в библиотеке DLL. Сегмент FIXED в обычном программном модуле
будет рассматриваться как MOVEABLE. Таким образом в системе Windows
нельзя будет поместить программу ISR в обычный программный модуль.
Вместо этого ее необходимо поместить в библиотеку DLL.
Листинг 2 представляет исходный код bogusa.asm на ассемблере для
библиотеки DLL, который содержит программу ISR и может выполняться в
окружении Windows. Программа IntSvcRtn очень похожа на свой дубликат,
работающий в системе MS-DOS. Однако кроме увеличения переменной-
счетчика данная программа ISR также записывает в очередь сообщение
Windows. Чтобы избежать переполнения очереди, запись сообщения
производится только в случае, когда переменная-счетчик wCount
изменяет значение от 0 к 1. Функция обнуления счетчика wCount после
того, как закончена обработка сообщения, передана высокоуровневой
программе системы Windows.
С первого взгляда все эти рассуждения кажутся простыми, однако
обработка прерываний в системе Windows совсем не так проста, как в
системе MS-DOS.
_____________________________________________________________________
page ,132
; masm tisr ; >err
.286p
.xlist
include bogus.inc
include pic.h
.list
WM_COMMAND =0111h
EXTRN POSTMESSAGE:FAR
Words struc
LoWord dw ?
HiWord dw ?
Words ends
;
; Установить переменные для нашего номера прерывания
;
ife (FAKE_IRQ GE 8)
INT_DEV equ (INT_MASTER_0+(FAKE_IRQ AND 7))
PIC00 equ INTA00
PIC01 equ INTA01
else
INT_DEV equ (INT_SLAVE_0+(FAKE_IRQ AND 7))
INT_MASK equ 1 SHL (FAKE_IRQ AND 7))
PIC00 equ INTB00
PIC01 equ INTB01
endif
page
FIXED_DATA SEGMENT DWORD PUBLIC 'DATA'
PUBLIC _hWndEvent,_wParamEvent,_wCount
_hWndEvent label word
hWndEvent dw 0 ; Окно для постирования событий
_wParamEvent label word
wParamEvent dw 0 ; Значение wParam для постирования
_wCount label word
wCount dw 0 ; Счетчик необработанных прерываний
FIXED_DATA ENDS
page
; IP IntSvcRtn - программа обслуживания прерываний
;
; WARNINGS (Предупреждения)
;
; NOTES (Примечания)
; Данная программа ISR увеличивает счетчик прерываний и заново
; маскирует устройство.
; Если предыдущее значение счетчика было равно 0, то записывается
; сообщение
; Если установлен флаг "fStopping", устройство не маскируется заново.
;
FIXED_TEXT SEGMENT PARA PUBLIC 'CODE'
selData1 dw FIXED_DATA
assume CS:FIXED_TEXT,DS:NOTHING
PUBLIC _IntSvcRtn
_IntSvcRtn label far
IntSvcRtn proc far
push ax
push dx
push ds
mov ds,selDatal
assume ds:FIXED_DATA
inc wCount
mov al,NOT FAKE_CTL_EOI
mov dx,FAKE_PORT
out dx,al ; посылаем устройству EOI
mov al,EOI
out PIC00,al ; посылаем EOI контроллеру PIC
ife (PIC00 EQ INTA00)
out INTA00,al ; посылаем EOI также главному контроллеру PIC
endif
cmp hWndEvent,0 ; завершать?
jz isr9 ; если да, то не делаем перезапуска и
; постирования
cmp wCount,1 ; требуется постирование?
jne isr8 ; пропускаем, если нет
push bx ; сохраняем оставшиеся регистры
push cx
push es
push hWndEvent
push WM_COMMAND
push wParamEvent
push 0 ; lParam равно 0
push 0
call POSTMESSAGE ; регистрируем событие
pop es
pop cx
pop bx
isr8:
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ; возобновляем ввод-вывод
isr9:
pop ds
assume ds:NOTHING
pop dx
pop ax
iret
IntSvcRtn endp
; требуется программе AllocIntReflector
PUBLIC _BogusCallback
_BogusCallback label far
BogusCallback proc far
pushf
call IntSvcRtn
ret
BogusCallback endp
FIXED_TEXT ENDS
end
; конец файла
_____________________________________________________________________
Кроме отдельного программного модуля для программы ISR (в форме
библиотеки DLL системы Windows), для работы драйвера необходим также
программный модуль пользовательского интерфейса, называемый интерфейс
API. На листинге 3 приведена программа bogus.h, представляющая собой
пример интерфейса API. Эта программа содержит 4 точки входа в
библиотеку DLL.
_____________________________________________________________________
#ifndef EXPORT
#define EXPORT
#endif
extern int EXPORT FAR PASCAL BogusCheck(void) ;
extern void EXPORT FAR PASCAL BogusStart(HWND hWnd,WPARAM wParam) ;
extern int EXPORT FAR PASCAL BogusGetEvent(void) ;
extern void EXPORT FAR PASCAL BogusStop(void) ;
/* Конец файла */
______________________________________________________________________
Листинг 3. Программа bogus.h.
В точке входа BogusCheck просто проверяется наличие устройства.
Программа возвращает значение TRUE, если устройство обнаружено (бит 7
порта состояния), и значение FALSE в противном случае.
Точки входа BogusStart и BogusStop начинают и завершают работу
устройства. Кроме того, точка входа BogusStart разрешает прерывания и
обеспечивает связь с аппаратным прерыванием, а точка входа BogusStop
выключает прерывания устройства и восстанавливает аппаратное
прерывание.
Точка входа BogusGetEvent возвращает количество прерываний,
обработанных со времени первого старта устройства, либо со времени
последнего вызова точки входа BogusGetEvent. (Точка входа
BogusGetEvent обнуляет счетчик прерываний при каждом ее вызове.)
При написании драйвера, который будет выполняться в стандартном
режиме работы системы Windows, необходимо учитывать возможность
появления прерывания, когда процессор работает в реальном режиме.
Даже если работают только приложения системы Windows, а не приложения
системы MS-DOS, процессор часто переключается из реального в
защищенный режим. Так как система Windows 3.1 не является
операционной системой, а скорее представляет собой окружение
пользовательского интерфейса, она возлагает выполнение определенного
количества основных функций, включая функцию ввода-вывода файлов, на
операционную систему (а именно MS-DOS).
Поэтому когда приложение системы Windows выполняет функцию
MS-DOS ввода-вывода файла и процессор при этом работает в реальном
режиме, устройство может прерывать ЦПУ. По умолчанию, если библиотека
DLL обеспечила связь с прерыванием, то система Windows переключит ЦПУ
в защищенный режим для обработки прерывания и, как только программа
ISR завершит работу, переключит ЦПУ обратно в реальный режим для
продолжения выполнения функций системы MS-DOS.
Хотя это в меньшей мере относится к ЦПУ 80386, переключение
процессора из защищенного режима в реальный режим, например на
процессоре 80286, создает огромные накладные расходы, требующие
контролируемого сброса ЦПУ, который выполняется в течении
миллисекунд. Если необходимо ускорить среднее время ответа, нужно
предотвратить переключение процессора в защищенный режим, если он
получает прерывание, работая в реальном режиме.
Обеспечение связи с вектором прерывания в защищенном режиме из
библиотеки DLL системы Windows - тривиально, что и показано в
программе SetPMVector, представленной в листинге 4 (программа
bogus.c). Установление связи с вектором производится таким же
способом, как и в системе MS-DOS, - с помощью функции setvector
системы MS-DOS. Однако в отличие от вызова в системе MS-DOS, в
системе Windows при обращении к функции передаются селектор и
смещение, а не сегмент и смещение. Ядро системы Windows следит за
всем. Функции следует передать нормальный селектор и смещение
(натуральный указатель far для системы Windows), а не сегмент и
смещение (натуральный указатель far для системы MS-DOS).
Однако, как уже упоминалось, установления связи с вектором
прерывания в защищенном режиме недостаточно. Необходимо также
обеспечить связь с вектором прерывания в реальном режиме, а это не
тривиальная задача.
______________________________________________________________________
/*EM BOGUS.C - Драйвер фиктивного устройства библиотеки DLL
*
* SUMMARY (Резюме)
* Базовые функции LibMain, WEP
*
* COMMENTS (Комментарии)
*
* WARNINGS (Предупреждения)
*
*/
#include
#include "bogusa.h"
#include "pic.h"
#include "dpmi.h"
#define EXPORT _export _loadds
#include "bogus.h"
#define FAKE_PORT 0x141 /* Уровень фиктивности (bogosity) - 9.4 */
#define FAKE_IRQ 11 /* Уровень фиктивности (bogosity) - 9.8 */
#define FAKE_CTL_START 0x01
/* команда "начать" фиктивного порта (устанавливается в нуль) */
#define FAKE_CTL_EOI 0x02
/* EOI фиктивного порта */
#define FAKE_STAT_BUSY 0x01
/* индикация занятости фиктивного порта (zero=>busy) */
#define FAKE_STAT_IRQ 0x02
/* IRQ фиктивного порта (zero=>IRQ) */
#define FAKE_STAT_ERROR 0x04
/* ошибка ввода-вывода (zero=>error) (сбрасывается при чтении) */
/* Установить переменные для нашего номера прерывания */
#if (FAKE_IRQ<8)
#define INT_DEV (INT_MASTER_0+(FAKE_IRQ & 7))
#define PIC00 INTA00
#define PIC01 INTA01
#else
#define INT_DEV (INT_SLAVE_0+(FAKE_IRQ & 7))
#define PIC00 INTB00
#define PIC01 INTB01
#endif
#define INT_MASK (1 << (FAKE_IRQ & 7))
BOOL FAR PASCAL LibMain(HANDLE hInstance
/* обработчик библиотечного экземпляра*/
,WORD wDataSeg
/* сегмент данных по умолчанию */
,WORD cbHeap
/* размер динамической области по умолчанию */
,LPSTR lpszCmdLine) ;
/* командная строка */
int FAR PASCAL WEP(int fSystemExit) ;
#pragma alloc_text(INIT_TEXT,LibMain)
/* держать вместе с LIBENTRY.ASM */
#pragma alloc_text(FIXED_TEXT,WEP)
HANDLE hLibInstance ;
FARPROC lpfnPrevISR ; /* Сохраненная предыдущая программа ISR*/
DWORD lpfnPrevRMISR ;
/* Сохраненная предыдущая программа ISR реального режима*/
HANDLE hReflector ;
DWORD DPMI_AllocateRMCallback(FARPROC lpfnCallback,
_RMCS FAR *lpRMCS)
{
DWORD dwRet ;
_asm {
push ds
lds si,lpfnCallback
les di,lpRMCS
mov ax,DPMI_ALLOCRMC
int IVEC_DPMI
pop ds
jc lbl1
mov word ptr dwRet,dx ; возврат адреса обратного вызова
mov word ptr dwRet+2,cx
jmp short lbl2
lbl1:
mov word ptr dwRet,ax ; код ошибки в регистре ax
mov word ptr dwRet+2,0 ; возвратить seg=0,если произошла ошибка
lbl2:
}
return dwRet ;
}
DWORD DPMI_FreeRMCallback(FARPROC lpfnCallback)
{
DWORD wRet ;
_asm {
mov dx,word ptr lpfnCallback
mov cx,word ptr lpfnCallback+2
mov ax,DPMI_FREERMC
int IVEC_DPMI
jc lbl1
xor ax,ax
lbl1:
mov wRet,ax
}
return wRet ;
}
DWORD DPMI_GetRMVector(int iVector)
{
DWORD dwRet ;
_asm {
mov ax,DPMI_GETRMVEC
mov bl,byte ptr iVector
int 31h
mov word ptr dwRet,dx
mov word ptr dwRet+2,cx
}
return dwRet ;
}
void DPMI_SetRMVector(int iVector, DWORD lpfnRMISR)
{
_asm {
mov ax,DPMI_SETRMVEC
mov bl,byte ptr iVector
mov dx,word ptr lpfnRMISR
mov cx,word ptr lpfnRMISR+2
int 31h
}
}
FARPROC GetPMVector(int iVector)
{
FARPROC dwRet ;
_asm {
mov bl,byte ptr iVector
mov ah,35h
int 21h
mov word ptr dwRet,bx
mov word ptr dwRet+2,es ; Сохранить
}
return dwRet ;
}
void SetPMVector(int iVector, FARPROC lpfnISR)
{
_asm {
push ds
lds dx,lpfnISR
mov al,byte ptr iVector
mov ah,25h
int 21h ; Установить нашу программу ISR
pop ds
}
}
HANDLE AllocIntReflector(int iVector, FARPROC lpfnCallback)
{
DWORD dwDosMem ;
LPSTR lpLowRMISR ;
DWORD lpfnRMCallback ;
_RMCS FAR *lpSaveRegs ;
/* Распределить память DOS для программы обслуживания прерывания ISR,
*работающей в реальном режиме */
dwDosMem = GlobalDosAlloc(16 + sizeof (int) + sizeof (_RMCS) ;
if (dwDosMem == 0)
return 0;
lpLowRMISR = (LPSTR) MAKELONG(0,LOWORD(dwDosMem)) ;
lpSaveRegs = (_RMCS FAR *) (&lpLowRMISR[16]) ;
/* Распределить обратный вызов (callback), работающий в реальном
* режиме */
lpfnRMCallback =
DPMI_AllocateRMCallback((FARPROC)lpfnCallback,
lpSaveRegs)
;
if (HIWORD((DWORD)lpfnRMCallback == 0)
{
GlobalDosFree(LOWORD(dwDosMem)) ;
return 0;
}
/* Сгенерировать код в нижних адресах памяти (только 6 байтов)*/
lpLowRMISR[0] = 0x9A ; /* Вызов указателя на FAR */
*((DWORD FAR *)&(lpLowRMISR[1])) = lpfnRMCallback ;
lpLowRMISR[5] = 0xCF ; /*IRET */
*((int FAR *)&(lpLowRMISR[6])) = iVector ;
/* Установить связь с вектором прерываний реального режима */
DPMI_SetRMVector(iVector,MAKELONG(0,HIWORD(dwDosMem))) ;
return (HANDLE) LOWORD(dwDosMem) ;
/* возврат обработчика-отражателя */
}
void FreeIntReflector(HANDLE hReflector)
{
LPSTR lpLowRMISR ;
DWORD lpfnRMCallback ;
/* Получить адрес нижнего ISR в защищенном режиме */
lpLowRMISR = (LPSTR)MAKELONG(0,(WORD)hReflector) ;
/* Следует убедиться, что это отражатель */
if ((lpLowRMISR[0] != 0x9A) || (lpLowRMISR[5] != 0xCF))
return ; /* выход, если не отражатель */
/* Выбрать адрес обратного вызова и освободить обратный вызов */
lpfnRMCallback = *((DWORD FAR *)&((lpLowRMISR[1])) ;
DPMI_FreeRMCallback(lpfnRMCallback) ;
/* Освободить программу обслуживания прерываний реального режима*/
GlobalDosFree((WORD)hReflector) ;
}
/*XP< LibMain - основная библиотечная точка входа */
*
* ENTRY (вход)
*
* EXIT (выход)
*
* RETURNS (возврат)
* Если инициализация завершается успешно принимает значение, равное
* TRUE, в противном случае - FALSE
*
* WARNINGS (предупреждения)
*
* CALLS (вызовы)
*
* NOTES (примечание)
* Настоящая библиотечная точка входа находится в ассемблерном модуле
* LIBENTRY.ASM, а в данную точку просто передается управление
*
*/
BOOL FAR PASCAL LibMain(HANDLE hInstance
/* обработчик библиотечного экземпляра*/
,WORD wDataSeg
/* сегмент данных по умолчанию */
,WORD cbHeap
/* размер динамической области по умолчанию */
,LPSTR lpszCmdLine) ;
/* командная строка */
/*>*/
{
lpszCmdLine = lpszCmdLine ;
/* Избегать предупреждения -W4 */
wDataSeg = wDataSeg ;
cbHeap = cbHeap ;
hInstance = hInstance ;
/* Это может понадобиться позже для доступа к ресурсам из нашего
*исполнительного модуля */
return TRUE ;
}
/*XP< WEP - процедура выхода в системе Windows */
*
* ENTRY (вход)
* fSystemExit указывает на завершение сессии в системе Windows. В
* противном случае происходит только разгрузка данной библиотеки DLL.
* RETURNS (возврат)
* Всегда возвращается значение 1
*
* WARNINGS (предупреждения)
* Из-за ошибок в системе Windows 3.0 и более ранних версиях (а
* возможно и в более поздних версиях) данная функция должна быть
*помещена в фиксированный сегмент. Эти же ошибки приводят к тому, что
* значение DS сомнительно, а поэтому нельзя его использовать (также
* как и любые статические данные).
*
* В любом случае, несомненно не надо ничего делать в этой точке.
*
* CALLS (вызовы)
* Нет
* NOTES (примечания)
* Это стандартная процедура выхода DLL.
*
*/
int FAR PASCAL WEP(int fSystemExit)
/*>*/
{
fSystemExit = fSystemExit
/* Избегать предупреждения -W4 */
return 1 ; /* всегда указывает на успешное завершение */
}
int EXPORT FAR PASCAL BogusCheck(void)
{
BYTE bPortVal ;
_asm {
mov dx,FAKE_PORT
in al,dx ; Присутсвует фиктивное устройство ?
mov bPortVal,al
}
return ((bPortVal & 0x80) == 0) ;
/* Возвращает значение TRUE, если устройство присутствует */
}
void EXPORT FAR PASCAL BogusStart(HWND hWnd, WPARAM wParam)
{
wParamEvent = wParam ;
hWndEvent = hWnd ;
if (!lpfnPrevISR)
{
/* Сохранить предыдущую программу ISR и загрузить новую */
_asm cli
lpfnPrevISR = GetPMVector(INT_DEV) ;
SetPMVector(INT_DEV,(FARPROC)IntSvcRtn) ;
_asm sti
/* Сохранить предыдущую программу ISR реального режима и
*отразить на новую */
lpfnPrevRMISR = DPMI_GetRMVector(INT_DEV) ;
hReflector = AllocIntReflector(INT_DEV,(FARPROC)BogusCallback) ;
/* Разрешить прерывание и начать операцию ввода-вывода на
*устройстве */
_asm {
cli
in al,PIC01 ; разрешить прерывание
and al,NOT INT_MASK
out PIC01,al
sti
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ;начать операцию ввода-вывода на устройстве
}
}
}
int EXPORT FAR PASCAL BogusGetEvent(void)
>
Date: 14 Apr 1997
From: Kazennov Vladimir
Перевод Владимира Казеннова
---------------------------------------------------------------
Драйверы устройств, как правило, - наиболее критическая часть
программного обеспечения компьютеров. По иронии судьбы это также и
наиболее скрытая часть разработки программного обеспечения. Драйверы
устройств системы Windows фирмы Microsoft не являются исключением.
Если вы когда-либо писали обычное приложение в системе Windows, то
вам известно, что требуется определенное количество скрытых способов,
чтобы приложение работало надежно. Как подмножество приложений
Windows, драйверы устройств системы Windows следуют этому же правилу.
В данной статье автор рассматривает работающий драйвер устройства,
который обеспечивает доступ к портам ввода-вывода и обрабатывает
прерывания, и виртуальный драйвер устройства (VxD), который имитирует
технические средства. Предполагается, что читатель знает основы
программирования в системе Windows, включая библиотеки динамической
связи (dynamic link libraries - DLLs).
Рассматриваемое устройство - это не часть технических средств,
которая была разработана, чтобы продемонстрировать, как писать
драйвер устройства в системе Windows. Скорее, это виртуальное
устройство, полностью реализованное в программном обеспечении.
Программа-пример выполняется только с виртуальным устройством,
которое автор определил работая с системой Windows в расширенном
режиме (Enhanced mode) процессора 386, и при условии, что установлен
виртуальный драйвер устройства (VxD). Далее в статье более детально
будет описан исходный код для этого устройства. На данный момент
следует знать, что устройство имеет два порта: порт состояния и порт
управления, оба на одном и том же адресе. На рис. 1 показаны биты,
используемые в порте состояния. Бит 2 указывает, что имела место
ошибка устройства, бит 1 показывает, что запрос на прерывание
является отложенным, а бит 0 указывает, что устройство занято. Бит 7
говорит о том, что устройство есть в наличии. В этом случае данный
бит равен нулю. Если же устройство не установлено или к нему нет
доступа, то бит принимает значение, равное 1.
+--------------------------------------------------------+
| 7 6...3 2 1 0 |
+---------+-------------------+---------+-------+--------+
| PRESENT | | ERROR | IRQ | BUSY |
+---------+-------------------+---------+-------+--------+
PRESENT - устройство есть в наличии;
ERROR - произошла ошибка устройства;
IRQ - прерывание отложено;
BUSY - устройство занято.
(Остальные биты игнорируются для дальнейшей совместимости.)
Рис. 1. Биты порта состояния устройства
На рис. 2 показаны биты, используемые в порте управления. Бит 1
указывает устройству, что ЦПУ закончило обработку прерывания. Бит 0
показывает, что устройство может начать обработку ввода-вывода. (В
данный момент не следует заострять внимание на том, что фактически
устройство делает. Вместо этого, необходимо уделить внимание тому,
как написать драйвер для такого устройства, которое обеспечивает
аппаратные прерывания.)
+--------------------------------------------------------+
| 7...2 1 0 |
+---------------------------------------+-------+--------+
| 1 1 1 1 1 1 | EOI | START |
+---------------------------------------+-------+--------+
EOI - сигнал для устройства, подтверждающий прием прерывания;
START - сигнал для устройства начинать пересылку ввода-вывода.
(Остальные биты должны быть установлены в 1 для дальнейшей
совместимости.)
Рис. 2. Биты порта управления устройства
На листинге 1 показана программа dostest.asm, представляющая
собой обычный драйвер устройства для системы MS-DOS, который общается
с устройством. Несмотря на простоту и малый размер данная программа
содержит основные компоненты драйвера устройства, который
обрабатывает прерывания.
_____________________________________________________________________
page ,132
; masm tisr ; >err
.286p
.xlist
include ..\..\include\bogus.inc
.list
Words struc
LoWord dw ?
HiWord dw ?
Words ends
EOI equ 020h ; команда EOI для контроллера PIC
INTA00 equ 020h ; управление главным контроллером PIC
INTA01 equ 021h ; регистр маски главного контроллера PIC
INT_MASTER_0 equ 08h ;номер INT главн. контроллера PIC
INTB00 equ 0A0h ; управление подчиненным контроллером PIC
INTB01 equ 0A1h ;регистр маски подчиненного контроллера PIC
INT_SLAVE_0 EQU 70h ; номер INT подчиненного контроллера PIC
;
; Установить переменные для нашего номера прерывания
;
ife (FAKE_IRQ GE 8)
INT_DEV equ (INT_MASTER_0+(FAKE_IRQ AND 7))
PIC00 equ INTA00
PIC01 equ INTA01
else
INT_DEV equ (INT_SLAVE_0+(FAKE_IRQ AND 7))
INT_MASK equ 1 SHL (FAKE_IRQ AND 7)
PIC00 equ INTB00
PIC01 equ INTB01
endif
page
CONST SEGMENT DWORD PUBLIC 'DATA'
sdNoBogus db 'I do not see the bogus device.',Odh,Oah,'$'
sdPrompt db Odh,Oah,'S)tart, or Q)uit: ','$'
sdCRLF db Odh,Oah,'$'
sdDot db '.','$'
CONST ENDS
DATA SEGMENT DWORD PUBLIC 'DATA'
dwCount1 dw 0
dwCount2 dw 0
lpPrevISR dd 0 ; адрес предыдущей программы ISR
fStopping db 0 ; значение TRUE при завершении
DATA ENDS
STACK SEGMENT DWORD STACK 'STACK'
db 512 dup (?)
STACK ENDS
DGroup GROUP CONST,DATA,STACK
page
;IP IntSvcRtn - The Interrupt Service Routine (Программа обслуживания
; прерывания)
; WARNINGS (предупреждения)
;
; NOTES (примечания)
; Данная программа ISR увеличивает счетчик прерываний (dwCount1)
; и заново маскирует устройство.
;
; Если установлен флаг "fStopping", устройство не маскируется заново.
;
FIXED_TEXT SEGMENT PARA PUBLIC 'CODE'
segData1 dw DGroup
assume CS:FIXED_TEXT,DS:NOTHING
IntSvcRtn proc far
push ax
push dx
push ds
mov ds,segDatal
assume ds:DGroup
inc dwCount1
mov al,NOT FAKE_CTL_EOI
mov dx,FAKE_PORT
out dx,al ; послать EOI устройству
mov al,EOI
out PIC00,al ; послать EOI контроллеру PIC
ife (PIC00 EQ INTA00)
out INTA00,al ; послать EOI главн. контроллеру PIC также
endif
cmp fStopping,0 ; существует?
jnz isr9 ; если да, то не нужен перезапуск
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ; перезапуск ввода-вывода
isr9:
pop ds
assume ds:NOTHING
pop dx
pop ax
iret
IntSvcRtn endp
FIXED_TEXT ENDS
page
;IP_main - точка входа в программу
; NOTES (примечания)
; Драйвер устройства выдает для пользователя приглашение:
;S)tart(начать) или Q)uit(выйти). Если пользователь нажимает S,
;программа разрешает прерывания и вооружает устройство, печатая точку
;каждый раз, как устройство прерывается.
;
_TEXT SEGMENT PARA PUBLIC 'CODE'
segData2 dw DGroup
segfixed dw FIXED_TEXT
assume cs:_TEXT,ds:NOTHING
_main label far
mov ds,segData2 ;инициализируется сегмент данных по умолчанию
assume ds:DGroup
mov dx,FAKE_PORT
in al,dx ; присутствует ли фиктивное устройство?
or al,al
jns m10 ;пропустить, если да
mov dx,OFFSET DGroup:sdNoBogus
mov ah,9
int 21h ; в противном случае печатать сообщение об ошибке
mov ax,4C01h
int 21h ; и выйти из системы
m10:
mov ax,3500h+INT_DEV
cli
int 21h ; запросить текущую программу ISR
mov lpPrevISR.LoWord,bx
mov lpPrevISR.HiWord,es ; сохранить ее
mov dx,OFFSET FIXED_TEXT:IntSvcRtn
push ds
mov ds,segFixed
assume ds:NOTHING
mov ax,2500h+INT_DEV
int 21h ; установить нашу программу ISR
pop ds
assume ds:DGroup
sti
mov dx,OFFSET DGroup:sdPrompt
mov ax,9
int 21h ; S)tart или Q)uit
ml1:
mov dl,0PFh
mov ah,6
int 21h ; читать с консоли, не ожидая
jz ml3
or al,40h
cmp al,'q'
je ml8 ; пропустить, если нажато "Q"
cmp al,'s'
jne ml3 ; пропустить, если не нажато "S"
cli
in al,PIC01 ; размаскировать прерывание
and al,NOT INT_MASK
out PIC01,al
sti
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ; начать ввод-вывод с устройства
ml3:
mov ax,dwCount1
cmp ax,dwCount2
je ml4 ; пропустить, если счетчик прерываний не изменился
mov dwCount2,ax
mov dx,OFFSET DGroup:sdDot
mov ah,9
int 21,h ;в противном случае выдать точку
ml4:
jmp ml1 ; цикл
ml8:
mov fStopping,1 ; указание для программы ISR завершить работу
mov dx,FAKE_PORT
ml9:
in al,dx
rcr al,1
jnc ml9 ; цикл, если занято
cli
in al,PIC01
or al,INT_MASK
out PIC01,al ; маскировать уровень прерывания
sti
push ds
lds dx,lpPrevISR
assume ds:NOTHING
mov ax,2500h+INT_DEV
int 21h ; восстановить предыдущую программу ISR
pop ds
assume ds:DGroup
mov ax,4C00h
int 21h ; выход
_TEXT ENDS
end_main
; Конец файла
_____________________________________________________________________
Драйвер устройства начинает работу с проверки старшего бита
порта состояния, чтобы убедиться в наличии устройства. Затем он
устанавливает связь с вектором прерывания MS-DOS для прерывания 11.
Драйвер сохраняет предыдущее значение, хранимое в этом векторе, так
чтобы можно было заменить значение, если программа существует.
Далее драйвер устройства выдает приглашение для пользователя :
Start(начать) или Quit(выйти). Если пользователь нажимает S,
программа начинает пересылку ввода-вывода. Если пользователь нажимает
Q, то программа отключает устройство, восстанавливает вектор
прерывания и завершается.
Чтобы начать операцию ввода-вывода, драйвер MS-DOS сначада
размаскирует программируемый контроллер прерываний (programmable
interrupt controller - PIC) для уровня прерывания устройства (в
примере прерывание 11). Затем драйвер начинает операцию ввода-вывода
для устройства путем записи 1 в бит 0 порта управления. Так как
прерывания включены, то при возникновении прерываний на устройстве
получит управление программа обслуживания прерываний (interrupt
service routine - ISR).
Если происходит прерывание на устройстве, то программа ISR
подтверждает прием прерывания, посылая значение EOI устройству (т.е.
записывая 1 в бит 1 порта управления устройства) и контроллеру PIC.
Если программа, выполняющая ввод-вывод, существует, то программа ISR
выполняется. В противном случае программа ISR осуществляет
инициализацию пересылки ввода-вывода вновь, записывая 1 в бит 0 порта
управления устройства. Итак, программа ISR возобновляет ввод-вывод
всякий раз, когда происходит прерывание, таким образом устройство
непрерывно выполняет операцию ввода-вывода. Кроме обеспечения
непрерывного ввода-вывода программа ISR увеличивает счетчик (dwCount1)
всякий раз, когда обрабатывает прерывание.
В процессе выполнения ввода-вывода программа следит за счетчиком
прерываний, отображает точку (".") для каждой законченной пересылки
ввода-вывода и продолжает сканировать клавиатуру, чтобы определить,
хочет ли пользователь остановить пересылку.
Чтобы завершить программу, пользователь нажимает клавишу Q.
Программа устанавливает флаг, который информирует программу ISR о
том, что следует остановить обработку. После того, как операция
ввода-вывода остановлена, программа маскирует уровень прерывания в
контроллере PIC и восстанавливает вектор прерывания.
Чрезвычайно тривиальный драйвер устройства MS-DOS, описанный
в предыдущем разделе, по существу довольно сложно реализовать в
системе Windows. При написании драйвера устройства в системе Windows,
обрабатывающего прерывания, необходимо использовать архитектуру,
отличную от той, которая была использована для драйвера MS-DOS. В
частности, необходимо отделить компоненту обработки прерывания от
компоненты приложения. Вместо единственной программы, управляющей как
программой ISR, так и интерфейсом пользователя, как сделано в системе
MS-DOS, в системе Windows необходимо выделить эти функции в отдельные
программные модули, называемые библиотекой динамической связи (DLL) и
интерфейсом прикладных программ (Application Program Interface - API).
При написании приложений в системе Windows обычно в программном
модуле имеют дело только с двумя типами сегментов: перемещаемым
(moveable) и выгружаемым (discardable). Сегменты данных программы
являются перемещаемыми, т.е. их линейные адреса в памяти могут
изменяться, когда программе управления памятью системы Windows
требуется организовать память. Селектор (selector) и смещение,
используемые для доступа к определенной ячейке памяти, остаются
фиксированными, но под схемой селектор-смещение система Windows
может перемещать фактические данные в линейной памяти.
Сегменты программ-кодов также перемещаемые, но имеют
дополнительный атрибут - выгружаемые. Их содержимое может быть
выгружено полностью, а при необходимости загружено с диска, так как
нельзя писать и (или) модифицировать информацию в сегменте
программы-кода. Если при обращении к сегменту из программы Windows,
он оказался выгруженным, программа управления памятью системы Windows
автоматически обратится к диску и прочитает ранее выгруженный сегмент.
Итак, каким образом это обстоятельство влияет на код для
программы ISR? Так как прерывание может произойти в любое время, а
код ISR может оказаться выгруженным, то возникнет проблема загрузить
код в память, если фиксируется прерывание. Вместо этого, можно
описать сегмент как FIXED (фиксированный), а не как MOVEABLE
(перемещаемый) или DISCARDABLE (выгружаемый). Сегмент с атрибутом
FIXED будет оставаться в единственном месте линейной памяти и не
будет выгружаться, даже если он содержит код. В этом случае, если
произойдет прерывание, код будет доступен и готов к выполнению.
Однако следует отметить один малоизвестный факт, а именно: в системе
Windows только те сегменты будут считаться FIXED, которые были
описаны в библиотеке DLL. Сегмент FIXED в обычном программном модуле
будет рассматриваться как MOVEABLE. Таким образом в системе Windows
нельзя будет поместить программу ISR в обычный программный модуль.
Вместо этого ее необходимо поместить в библиотеку DLL.
Листинг 2 представляет исходный код bogusa.asm на ассемблере для
библиотеки DLL, который содержит программу ISR и может выполняться в
окружении Windows. Программа IntSvcRtn очень похожа на свой дубликат,
работающий в системе MS-DOS. Однако кроме увеличения переменной-
счетчика данная программа ISR также записывает в очередь сообщение
Windows. Чтобы избежать переполнения очереди, запись сообщения
производится только в случае, когда переменная-счетчик wCount
изменяет значение от 0 к 1. Функция обнуления счетчика wCount после
того, как закончена обработка сообщения, передана высокоуровневой
программе системы Windows.
С первого взгляда все эти рассуждения кажутся простыми, однако
обработка прерываний в системе Windows совсем не так проста, как в
системе MS-DOS.
_____________________________________________________________________
page ,132
; masm tisr ; >err
.286p
.xlist
include bogus.inc
include pic.h
.list
WM_COMMAND =0111h
EXTRN POSTMESSAGE:FAR
Words struc
LoWord dw ?
HiWord dw ?
Words ends
;
; Установить переменные для нашего номера прерывания
;
ife (FAKE_IRQ GE 8)
INT_DEV equ (INT_MASTER_0+(FAKE_IRQ AND 7))
PIC00 equ INTA00
PIC01 equ INTA01
else
INT_DEV equ (INT_SLAVE_0+(FAKE_IRQ AND 7))
INT_MASK equ 1 SHL (FAKE_IRQ AND 7))
PIC00 equ INTB00
PIC01 equ INTB01
endif
page
FIXED_DATA SEGMENT DWORD PUBLIC 'DATA'
PUBLIC _hWndEvent,_wParamEvent,_wCount
_hWndEvent label word
hWndEvent dw 0 ; Окно для постирования событий
_wParamEvent label word
wParamEvent dw 0 ; Значение wParam для постирования
_wCount label word
wCount dw 0 ; Счетчик необработанных прерываний
FIXED_DATA ENDS
page
; IP IntSvcRtn - программа обслуживания прерываний
;
; WARNINGS (Предупреждения)
;
; NOTES (Примечания)
; Данная программа ISR увеличивает счетчик прерываний и заново
; маскирует устройство.
; Если предыдущее значение счетчика было равно 0, то записывается
; сообщение
; Если установлен флаг "fStopping", устройство не маскируется заново.
;
FIXED_TEXT SEGMENT PARA PUBLIC 'CODE'
selData1 dw FIXED_DATA
assume CS:FIXED_TEXT,DS:NOTHING
PUBLIC _IntSvcRtn
_IntSvcRtn label far
IntSvcRtn proc far
push ax
push dx
push ds
mov ds,selDatal
assume ds:FIXED_DATA
inc wCount
mov al,NOT FAKE_CTL_EOI
mov dx,FAKE_PORT
out dx,al ; посылаем устройству EOI
mov al,EOI
out PIC00,al ; посылаем EOI контроллеру PIC
ife (PIC00 EQ INTA00)
out INTA00,al ; посылаем EOI также главному контроллеру PIC
endif
cmp hWndEvent,0 ; завершать?
jz isr9 ; если да, то не делаем перезапуска и
; постирования
cmp wCount,1 ; требуется постирование?
jne isr8 ; пропускаем, если нет
push bx ; сохраняем оставшиеся регистры
push cx
push es
push hWndEvent
push WM_COMMAND
push wParamEvent
push 0 ; lParam равно 0
push 0
call POSTMESSAGE ; регистрируем событие
pop es
pop cx
pop bx
isr8:
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ; возобновляем ввод-вывод
isr9:
pop ds
assume ds:NOTHING
pop dx
pop ax
iret
IntSvcRtn endp
; требуется программе AllocIntReflector
PUBLIC _BogusCallback
_BogusCallback label far
BogusCallback proc far
pushf
call IntSvcRtn
ret
BogusCallback endp
FIXED_TEXT ENDS
end
; конец файла
_____________________________________________________________________
Кроме отдельного программного модуля для программы ISR (в форме
библиотеки DLL системы Windows), для работы драйвера необходим также
программный модуль пользовательского интерфейса, называемый интерфейс
API. На листинге 3 приведена программа bogus.h, представляющая собой
пример интерфейса API. Эта программа содержит 4 точки входа в
библиотеку DLL.
_____________________________________________________________________
#ifndef EXPORT
#define EXPORT
#endif
extern int EXPORT FAR PASCAL BogusCheck(void) ;
extern void EXPORT FAR PASCAL BogusStart(HWND hWnd,WPARAM wParam) ;
extern int EXPORT FAR PASCAL BogusGetEvent(void) ;
extern void EXPORT FAR PASCAL BogusStop(void) ;
/* Конец файла */
______________________________________________________________________
Листинг 3. Программа bogus.h.
В точке входа BogusCheck просто проверяется наличие устройства.
Программа возвращает значение TRUE, если устройство обнаружено (бит 7
порта состояния), и значение FALSE в противном случае.
Точки входа BogusStart и BogusStop начинают и завершают работу
устройства. Кроме того, точка входа BogusStart разрешает прерывания и
обеспечивает связь с аппаратным прерыванием, а точка входа BogusStop
выключает прерывания устройства и восстанавливает аппаратное
прерывание.
Точка входа BogusGetEvent возвращает количество прерываний,
обработанных со времени первого старта устройства, либо со времени
последнего вызова точки входа BogusGetEvent. (Точка входа
BogusGetEvent обнуляет счетчик прерываний при каждом ее вызове.)
При написании драйвера, который будет выполняться в стандартном
режиме работы системы Windows, необходимо учитывать возможность
появления прерывания, когда процессор работает в реальном режиме.
Даже если работают только приложения системы Windows, а не приложения
системы MS-DOS, процессор часто переключается из реального в
защищенный режим. Так как система Windows 3.1 не является
операционной системой, а скорее представляет собой окружение
пользовательского интерфейса, она возлагает выполнение определенного
количества основных функций, включая функцию ввода-вывода файлов, на
операционную систему (а именно MS-DOS).
Поэтому когда приложение системы Windows выполняет функцию
MS-DOS ввода-вывода файла и процессор при этом работает в реальном
режиме, устройство может прерывать ЦПУ. По умолчанию, если библиотека
DLL обеспечила связь с прерыванием, то система Windows переключит ЦПУ
в защищенный режим для обработки прерывания и, как только программа
ISR завершит работу, переключит ЦПУ обратно в реальный режим для
продолжения выполнения функций системы MS-DOS.
Хотя это в меньшей мере относится к ЦПУ 80386, переключение
процессора из защищенного режима в реальный режим, например на
процессоре 80286, создает огромные накладные расходы, требующие
контролируемого сброса ЦПУ, который выполняется в течении
миллисекунд. Если необходимо ускорить среднее время ответа, нужно
предотвратить переключение процессора в защищенный режим, если он
получает прерывание, работая в реальном режиме.
Обеспечение связи с вектором прерывания в защищенном режиме из
библиотеки DLL системы Windows - тривиально, что и показано в
программе SetPMVector, представленной в листинге 4 (программа
bogus.c). Установление связи с вектором производится таким же
способом, как и в системе MS-DOS, - с помощью функции setvector
системы MS-DOS. Однако в отличие от вызова в системе MS-DOS, в
системе Windows при обращении к функции передаются селектор и
смещение, а не сегмент и смещение. Ядро системы Windows следит за
всем. Функции следует передать нормальный селектор и смещение
(натуральный указатель far для системы Windows), а не сегмент и
смещение (натуральный указатель far для системы MS-DOS).
Однако, как уже упоминалось, установления связи с вектором
прерывания в защищенном режиме недостаточно. Необходимо также
обеспечить связь с вектором прерывания в реальном режиме, а это не
тривиальная задача.
______________________________________________________________________
/*EM BOGUS.C - Драйвер фиктивного устройства библиотеки DLL
*
* SUMMARY (Резюме)
* Базовые функции LibMain, WEP
*
* COMMENTS (Комментарии)
*
* WARNINGS (Предупреждения)
*
*/
#include
#include "bogusa.h"
#include "pic.h"
#include "dpmi.h"
#define EXPORT _export _loadds
#include "bogus.h"
#define FAKE_PORT 0x141 /* Уровень фиктивности (bogosity) - 9.4 */
#define FAKE_IRQ 11 /* Уровень фиктивности (bogosity) - 9.8 */
#define FAKE_CTL_START 0x01
/* команда "начать" фиктивного порта (устанавливается в нуль) */
#define FAKE_CTL_EOI 0x02
/* EOI фиктивного порта */
#define FAKE_STAT_BUSY 0x01
/* индикация занятости фиктивного порта (zero=>busy) */
#define FAKE_STAT_IRQ 0x02
/* IRQ фиктивного порта (zero=>IRQ) */
#define FAKE_STAT_ERROR 0x04
/* ошибка ввода-вывода (zero=>error) (сбрасывается при чтении) */
/* Установить переменные для нашего номера прерывания */
#if (FAKE_IRQ<8)
#define INT_DEV (INT_MASTER_0+(FAKE_IRQ & 7))
#define PIC00 INTA00
#define PIC01 INTA01
#else
#define INT_DEV (INT_SLAVE_0+(FAKE_IRQ & 7))
#define PIC00 INTB00
#define PIC01 INTB01
#endif
#define INT_MASK (1 << (FAKE_IRQ & 7))
BOOL FAR PASCAL LibMain(HANDLE hInstance
/* обработчик библиотечного экземпляра*/
,WORD wDataSeg
/* сегмент данных по умолчанию */
,WORD cbHeap
/* размер динамической области по умолчанию */
,LPSTR lpszCmdLine) ;
/* командная строка */
int FAR PASCAL WEP(int fSystemExit) ;
#pragma alloc_text(INIT_TEXT,LibMain)
/* держать вместе с LIBENTRY.ASM */
#pragma alloc_text(FIXED_TEXT,WEP)
HANDLE hLibInstance ;
FARPROC lpfnPrevISR ; /* Сохраненная предыдущая программа ISR*/
DWORD lpfnPrevRMISR ;
/* Сохраненная предыдущая программа ISR реального режима*/
HANDLE hReflector ;
DWORD DPMI_AllocateRMCallback(FARPROC lpfnCallback,
_RMCS FAR *lpRMCS)
{
DWORD dwRet ;
_asm {
push ds
lds si,lpfnCallback
les di,lpRMCS
mov ax,DPMI_ALLOCRMC
int IVEC_DPMI
pop ds
jc lbl1
mov word ptr dwRet,dx ; возврат адреса обратного вызова
mov word ptr dwRet+2,cx
jmp short lbl2
lbl1:
mov word ptr dwRet,ax ; код ошибки в регистре ax
mov word ptr dwRet+2,0 ; возвратить seg=0,если произошла ошибка
lbl2:
}
return dwRet ;
}
DWORD DPMI_FreeRMCallback(FARPROC lpfnCallback)
{
DWORD wRet ;
_asm {
mov dx,word ptr lpfnCallback
mov cx,word ptr lpfnCallback+2
mov ax,DPMI_FREERMC
int IVEC_DPMI
jc lbl1
xor ax,ax
lbl1:
mov wRet,ax
}
return wRet ;
}
DWORD DPMI_GetRMVector(int iVector)
{
DWORD dwRet ;
_asm {
mov ax,DPMI_GETRMVEC
mov bl,byte ptr iVector
int 31h
mov word ptr dwRet,dx
mov word ptr dwRet+2,cx
}
return dwRet ;
}
void DPMI_SetRMVector(int iVector, DWORD lpfnRMISR)
{
_asm {
mov ax,DPMI_SETRMVEC
mov bl,byte ptr iVector
mov dx,word ptr lpfnRMISR
mov cx,word ptr lpfnRMISR+2
int 31h
}
}
FARPROC GetPMVector(int iVector)
{
FARPROC dwRet ;
_asm {
mov bl,byte ptr iVector
mov ah,35h
int 21h
mov word ptr dwRet,bx
mov word ptr dwRet+2,es ; Сохранить
}
return dwRet ;
}
void SetPMVector(int iVector, FARPROC lpfnISR)
{
_asm {
push ds
lds dx,lpfnISR
mov al,byte ptr iVector
mov ah,25h
int 21h ; Установить нашу программу ISR
pop ds
}
}
HANDLE AllocIntReflector(int iVector, FARPROC lpfnCallback)
{
DWORD dwDosMem ;
LPSTR lpLowRMISR ;
DWORD lpfnRMCallback ;
_RMCS FAR *lpSaveRegs ;
/* Распределить память DOS для программы обслуживания прерывания ISR,
*работающей в реальном режиме */
dwDosMem = GlobalDosAlloc(16 + sizeof (int) + sizeof (_RMCS) ;
if (dwDosMem == 0)
return 0;
lpLowRMISR = (LPSTR) MAKELONG(0,LOWORD(dwDosMem)) ;
lpSaveRegs = (_RMCS FAR *) (&lpLowRMISR[16]) ;
/* Распределить обратный вызов (callback), работающий в реальном
* режиме */
lpfnRMCallback =
DPMI_AllocateRMCallback((FARPROC)lpfnCallback,
lpSaveRegs)
;
if (HIWORD((DWORD)lpfnRMCallback == 0)
{
GlobalDosFree(LOWORD(dwDosMem)) ;
return 0;
}
/* Сгенерировать код в нижних адресах памяти (только 6 байтов)*/
lpLowRMISR[0] = 0x9A ; /* Вызов указателя на FAR */
*((DWORD FAR *)&(lpLowRMISR[1])) = lpfnRMCallback ;
lpLowRMISR[5] = 0xCF ; /*IRET */
*((int FAR *)&(lpLowRMISR[6])) = iVector ;
/* Установить связь с вектором прерываний реального режима */
DPMI_SetRMVector(iVector,MAKELONG(0,HIWORD(dwDosMem))) ;
return (HANDLE) LOWORD(dwDosMem) ;
/* возврат обработчика-отражателя */
}
void FreeIntReflector(HANDLE hReflector)
{
LPSTR lpLowRMISR ;
DWORD lpfnRMCallback ;
/* Получить адрес нижнего ISR в защищенном режиме */
lpLowRMISR = (LPSTR)MAKELONG(0,(WORD)hReflector) ;
/* Следует убедиться, что это отражатель */
if ((lpLowRMISR[0] != 0x9A) || (lpLowRMISR[5] != 0xCF))
return ; /* выход, если не отражатель */
/* Выбрать адрес обратного вызова и освободить обратный вызов */
lpfnRMCallback = *((DWORD FAR *)&((lpLowRMISR[1])) ;
DPMI_FreeRMCallback(lpfnRMCallback) ;
/* Освободить программу обслуживания прерываний реального режима*/
GlobalDosFree((WORD)hReflector) ;
}
/*XP< LibMain - основная библиотечная точка входа */
*
* ENTRY (вход)
*
* EXIT (выход)
*
* RETURNS (возврат)
* Если инициализация завершается успешно принимает значение, равное
* TRUE, в противном случае - FALSE
*
* WARNINGS (предупреждения)
*
* CALLS (вызовы)
*
* NOTES (примечание)
* Настоящая библиотечная точка входа находится в ассемблерном модуле
* LIBENTRY.ASM, а в данную точку просто передается управление
*
*/
BOOL FAR PASCAL LibMain(HANDLE hInstance
/* обработчик библиотечного экземпляра*/
,WORD wDataSeg
/* сегмент данных по умолчанию */
,WORD cbHeap
/* размер динамической области по умолчанию */
,LPSTR lpszCmdLine) ;
/* командная строка */
/*>*/
{
lpszCmdLine = lpszCmdLine ;
/* Избегать предупреждения -W4 */
wDataSeg = wDataSeg ;
cbHeap = cbHeap ;
hInstance = hInstance ;
/* Это может понадобиться позже для доступа к ресурсам из нашего
*исполнительного модуля */
return TRUE ;
}
/*XP< WEP - процедура выхода в системе Windows */
*
* ENTRY (вход)
* fSystemExit указывает на завершение сессии в системе Windows. В
* противном случае происходит только разгрузка данной библиотеки DLL.
* RETURNS (возврат)
* Всегда возвращается значение 1
*
* WARNINGS (предупреждения)
* Из-за ошибок в системе Windows 3.0 и более ранних версиях (а
* возможно и в более поздних версиях) данная функция должна быть
*помещена в фиксированный сегмент. Эти же ошибки приводят к тому, что
* значение DS сомнительно, а поэтому нельзя его использовать (также
* как и любые статические данные).
*
* В любом случае, несомненно не надо ничего делать в этой точке.
*
* CALLS (вызовы)
* Нет
* NOTES (примечания)
* Это стандартная процедура выхода DLL.
*
*/
int FAR PASCAL WEP(int fSystemExit)
/*>*/
{
fSystemExit = fSystemExit
/* Избегать предупреждения -W4 */
return 1 ; /* всегда указывает на успешное завершение */
}
int EXPORT FAR PASCAL BogusCheck(void)
{
BYTE bPortVal ;
_asm {
mov dx,FAKE_PORT
in al,dx ; Присутсвует фиктивное устройство ?
mov bPortVal,al
}
return ((bPortVal & 0x80) == 0) ;
/* Возвращает значение TRUE, если устройство присутствует */
}
void EXPORT FAR PASCAL BogusStart(HWND hWnd, WPARAM wParam)
{
wParamEvent = wParam ;
hWndEvent = hWnd ;
if (!lpfnPrevISR)
{
/* Сохранить предыдущую программу ISR и загрузить новую */
_asm cli
lpfnPrevISR = GetPMVector(INT_DEV) ;
SetPMVector(INT_DEV,(FARPROC)IntSvcRtn) ;
_asm sti
/* Сохранить предыдущую программу ISR реального режима и
*отразить на новую */
lpfnPrevRMISR = DPMI_GetRMVector(INT_DEV) ;
hReflector = AllocIntReflector(INT_DEV,(FARPROC)BogusCallback) ;
/* Разрешить прерывание и начать операцию ввода-вывода на
*устройстве */
_asm {
cli
in al,PIC01 ; разрешить прерывание
and al,NOT INT_MASK
out PIC01,al
sti
mov al,NOT FAKE_CTL_START
mov dx,FAKE_PORT
out dx,al ;начать операцию ввода-вывода на устройстве
}
}
}
int EXPORT FAR PASCAL BogusGetEvent(void)
>