Даниель А. Нортон. Драйверы устройств в системе WINDOWS

Драйверы устройств, как правило, - наиболее критическая часть программного обеспечения компьютеров. По иронии судьбы это также и наиболее скрытая часть разработки программного обеспечения. Драйверы устройств системы 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а |
 +---------+-------------------+---------+-------+--------+

(Остальные биты игнорируются для дальнейшей совместимости.)

На рис. 2 показаны биты, используемые в порте управления. Бит 1 указывает устройству, что ЦПУ закончило обработку прерывания. Бит 0 показывает, что устройство может начать обработку ввода-вывода. (В данный момент не следует заострять внимание на том, что фактически устройство делает. Вместо этого, необходимо уделить внимание тому, как написать драйвер для такого устройства, которое обеспечивает аппаратные прерывания.)

а+--------------------------------------------------------+
 |аааааааааааааааа 7...2аааааааааааааааааааа 1аааааа 0ааа |
 +---------------------------------------+-------+--------+
 |аа 1аааа 1аааа 1ааааа 1ааааа 1ааааа 1а |а EOIа | STARTа |
 +---------------------------------------+-------+--------+

(Остальные биты должны быть установлены в 1 для дальнейшей совместимости.)

Драйвер устройства в системе MS-DOS

На листинге 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
 
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
 
;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
 
;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 и восстанавливает вектор прерывания.

Драйвер устройства в системе Windows

Чрезвычайно тривиальный драйвер устройства MS-DOS, описанный в предыдущем разделе, по существу довольно сложно реализовать в системе Windows. При написании драйвера устройства в системе Windows, обрабатывающего прерывания, необходимо использовать архитектуру, отличную от той, которая была использована для драйвера MS-DOS. В частности, необходимо отделить компоненту обработки прерывания от компоненты приложения. Вместо единственной программы, управляющей как программой ISR, так и интерфейсом пользователя, как сделано в системе MS-DOS, в системе Windows необходимо выделить эти функции в отдельные программные модули, называемые библиотекой динамической связи (DLL) и интерфейсом прикладных программ (Application Program Interface - API).

Библиотека DLL для драйвера

При написании приложений в системе 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
 
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
 
; 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
; конец файла

Драйверный интерфейс API

Кроме отдельного программного модуля для программы ISR (в форме библиотеки DLL системы Windows), для работы драйвера необходим также программный модуль пользовательского интерфейса, называемый интерфейс API. На листинге 3 приведена программа bogus.h, представляющая собой пример интерфейса API. Эта программа содержит 4 точки входа в библиотеку DLL.

Программа bogus.h.

#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) ;

В точке входа BogusCheck просто проверяется наличие устройства. Программа возвращает значение TRUE, если устройство обнаружено (бит 7 порта состояния), и значение FALSE в противном случае.

Точки входа BogusStart и BogusStop начинают и завершают работу устройства. Кроме того, точка входа BogusStart разрешает прерывания и обеспечивает связь с аппаратным прерыванием, а точка входа BogusStop выключает прерывания устройства и восстанавливает аппаратное прерывание.

Точка входа BogusGetEvent возвращает количество прерываний, обработанных со времени первого старта устройства, либо со времени последнего вызова точки входа BogusGetEvent. (Точка входа BogusGetEvent обнуляет счетчик прерываний при каждом ее вызове.)

Прерывания при стандартном режиме работы системы Windows

При написании драйвера, который будет выполняться в стандартном режиме работы системы 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).

Однако, как уже упоминалось, установления связи с вектором прерывания в защищенном режиме недостаточно. Необходимо также обеспечить связь с вектором прерывания в реальном режиме, а это не тривиальная задача.

Программа bogus.c:
 
/*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)
{
аааа WORD wCountRet ;
 
аааа _asm {
аааа mov ax,SEG wCount
аааа mov es,ax
аааа xor ax,ax
аааа xchgаа ax,es:wCountаааа ;получить счетчик, установить в нуль
аааа mov wCountRet,ax
аааа }
 
аааа return wCountRet ;
}
 
void EXPORT FAR PASCAL BogusStop(void)
{
аааа hWndEvent - 0x0000а ;а /*команда для ISR "завершить работу"*/
аааа if (!lpfnPrevISR)
аааа return ;ааааа /*а возвратиться, если программа не стартовала */
 
аааа _asm {
ааааааааа mov dx,FAKE_PORT
аааа l1:
ааааааааа inа al,dx
ааааааааа rcr al,1
ааааааааа jnc l1а ааа; цикл, если занято
 
ааааааааа cli
ааааааааа inа al,PIC01
ааааааааа orа al,INT_MASK
ааааааааа out PIC01,alаа ; маскировать уровень прерывания
ааааааааа sti
ааааааааа }
 
аааа DPMI_SetRMVector(INT_DEV, lpfnPrevRMISR) ;
аааа /* Восстановить вектор реального режима */
аааа FreeIntReflector(hReflector) ;
аааа /* Освободить отражатель */
аааа SetPMVector(INT_DEV, lpfnPrevISR) ;
аааа /* Восстановить вектор защищенного режима */
 
аааа lpfnPrevISR = NULL ;
}
/* конец файла*/

Интерфейс системы MS-DOS для защищенного режима

Чтобы установить связь с вектором реального режима из кода системы Windows защищенного режима, необходимо работать с интерфейсом системы MS-DOS для защищенного режима (MS-DOS Protected Mode Interface - DPMI). (Текущая версия DPMI представляет собой уровень 1.0, но система Windows наиболее полно реализует только уровень 0.9. Некоторые функции уровня 1.0 реализованы в системе Windows 3.1.)

Функция DPMI_SetRMVector вызывает интерфейс DPMI, чтобы установить вектор реального режима. Можно видеть, что интерфейс DPMI взаимодействует через регистры (регистр AX содержит функциональный код) и INT31h. Автор включил высокоуровневый интерфейс в данную и другие функции DPMI (доступен только на диске кодов или в интерактивном режиме), чтобы можно было иметь доступ к интерфейсу DPMI из языка Си и выделил код, написанный на языке ассемблер, на случай, если возникнет необходимость использовать что-то отличное от компилятора Си фирмы Microsoft.

Функция DPMI_AllocateRMCallback вызывает интерфейс DPMI, чтобы распределить обратный вызов (callback), представляющий собой адрес, вызываемый из реального режима, который передает управление коду защищенного режима. Например, программа TSR системы MS-DOS может вызвать код в библиотеке DLL системы Windows через обратный вызов.

Функция DPMI_AllocateRMCallback принимает два параметра: адрес кода защищенного режима, который будет вызываться обратно, и регистровую структуру, которая обновляется при выполнении реального обратного вызова, таким образом код защищенного режима может исследовать содержимое регистров реального режима во время обратного вызова.

Функция DPMI_FreeRMCallback освобождает все структуры, которые были распределены в результате обращения к функции DPMI_AllocateRMCallback. Функция DPMI_FreeRMCallback должна вызываться только тогда, когда больше нет необходимости в обратном вызове.

Программа ISR в реальном режиме

Несмотря на то, что автор рекомендовал обеспечивать раздельную программу ISR в реальном режиме, в данном примере эта рекомендация не была выполнена. Вместо этого, автор предоставил программы, необходимые при реализации программы ISR на языке Си. Фактически, данный пример устанавливает связь с прерываниями реального режима только для того, чтобы переключить ЦПУ в защищенный режим для обработки прерывания. Таково по умолчанию поведение системы Windows, когда с прерываниями реального режима не устанавливается связь вообще, таким образом автор рассматривает несколько циклов, которые не имеют никакого другого назначения, кроме как показать, каким образом все работает.

Рассмотрим код для точки входа BogusStart. По существу он работает так же, как работал бы в системе MS-DOS. Код сохраняет старое значение прерывания, обеспечивает связь с текущим значением и подает устройству знак начать работу. Однако вместо обеспечения связи только с вектором защищенного режима, он устанавливает связь как с вектором реального режима, так и с вектором защищенного режима. Устанавливая связь с вектором реального режима, код вызывает AllocIntReflector, чтобы обеспечить ссылку вектора прерываний реального режима на обратный вызов, который просто обращается к программе ISR защищенного режима. Точка входа BogusStart подает знак устройству начинать работу одинаковым образом при обоих режимах работы: защищенном и реальном. Она размаскирует бит IRQ для контроллера PIC и подает знак устройству начинать работу, записывая 1 в бит START порта управления устройством. Как только приложение обращается к данной программе, начинается обработка прерываний и регистрация сообщений в соответствии с программой ISR.

Программа BogusStop тривиальна и просто отключает устройство и разрывает связи, установленные программой BogusStart. Итак, осталось привести пример прикладной программы, чтобы показать работу операций ввода-вывода.

Приложение WINTEST

Приложение wintest.c, показывающее работу ввода-вывода (см. листинг 5), состоит главным образом из немодульного диалогового блока, в котором непрерывно высвечивается количество прерываний, обработанных с начала работы программы.

Программа MainDlgProc вызывает программу BogusStart во время выполнения WM_INITDIALOG, передавая в качестве параметра обработчик окна диалогового блока. Программа ISR регистрирует сообщения к данному обработчику в тех случаях, когда счетчик прерываний изменяется от нуля к единице.

Программа MainDlgProc сохраняет текущее суммарное значение счетчика в переменной wCountTotal. Всякий раз, когда диалог получает сообщение WM_COMMAND с параметром wParam, равным IDM_BOGUSEVENT, программа обновляет суммарный счетчик, отображаемый в диалоговом блоке. Следует отметить, что хотя программа ISR регистрирует сообщение только тогда, когда счетчик изменяется от нуля к единице, возможна (и весьма вероятно) обработка количества прерываний до того, как сообщение WM_COMMAND фактически будет передано диалоговой процедуре. Методика, показанная в данной программе, при которой программа ISR регистрирует сообщение только при первом переходе, а программа BogusCheck чистит счетчик, обеспечивает точный подсчет количества прерываний, даже если на уровне приложения нельзя учесть каждое прерывание в момент его возникновения.

При выполнении данной программы можно наблюдать, что счетчик прерывания в диалоговом блоке непрерывно увеличивается, указывая количество выполненных операций ввод-вывода.

Программа wintest.c:

#include
#include "bogus.h"
#include "wintest.h"
 
HANDLE hPgmInstance ;
#define IDM_BOGUSEVENTа 0x3000
 
void CenterWindow(HWND hWnd)
аааа {
аааа int xSize, ySixe, xPos, yPos ;
аааа RECT rc ;
 
аааа xSize = GetSystemMetrics(SM_CXSCREEN) ;
аааа ySize = GetSystemMetrics(SM_CYSCREEN) ;
аааа GetWindowRect(hWnd, &rc) ;
аааа xPos = (xSize - (rc.right - rc.left)) / 2 ;
аааа yPos = (ySize - (rc.bottom - rc.top)) / 2 ;
аааа SetWindowRect(hWnd, NULL, xPos, yPos, 0, 0,
аааааааа SWP_DRAWFRAME | SWP_NOSIZE | SWP_NOZORDER) ;
аааа }
 
LRESULT _loadds FAR PASCAL MainDlgProc(HWND hwndDlg,
UINT msg, WPARAM wParam, LPARAM lParam)
{
аааа static WORD wCountTotal = 0;
аааа WORD wCount ;
 
аааа lParam = lParam ;
аааа switch (msg)
аааа {
аааа case WM_INITDIALOG:
ааааааааа RemoveMenu(GetSystemMenu(hwndDlg,0),
SC_CLOSE,MF_BYCOMMAND) ;
ааааааааа BogusStart(hwndDlg, IDM_BOGUSEVENT) ;
ааааааааа break ;
 
аааа case WM_SHOWWINDOW:
ааааааааа if (wParam)
ааааааааа CenterWindow(hwndDlg) ;
ааааааааа break ;
 
аааа case WM_COMMAND:
аааа аааааswitch (wParam)
ааааааааа {
ааааааааа case IDM_BOGUSEVENT:
аааааааааааааа wCount = BogusGetEvent() ;
аааааааааааааа while 9wCount)
аааааааааааааа {
аааааааааааааа wCountTotal += wCount ;
аааааааааааааа wCount = BogusGetEvent() ;
аааааааааааааа }
аааа ааааааааааSetDlgItemInt(hwndDlg, IDM_COUNT, wCountTotal, FALSE);
аааааааааааааа break ;
 
ааааааааа case IDCANCEL:
аааааааааааааа EndDialog(hwndDlg, 0) ;
аааааааааааааа break ;
ааааааааа }
ааааааааа break ;
 
аааа default:
ааааааааа return FALSE ;
аааа }
аа ааreturn TRUE ;
}
 
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, intnCmdShow)
{
аааа hPgmInstance = hInstance ;
аааа hPrevInstance = hPrevInstance ;
аааа lpCmdLine = lpCmdLine ;
аааа nCmdShow = nCmdShow ;
 
аааа if (!hPrevInstance)
аааа {
аааа if (BogusCheck())
ааааааааа {
ааааааааа if (MessageBox(0, "Press OK to begin bogus I/O",
аааааааааааааааааа "WinTest", MB_OKCANCEL|MB_APPLMODAL) == IDOK)
ааааааааа {
ааааааааа DialogBox(hPgmInstance, "MainDlg", 0,
аааааааааааааааааа (FARPROC) MainDlgProc) ;
ааааааааа BogusStop() ;
ааааааааа }
ааааааааа }
аааа else
ааааааааа MessageBox(0, "Bogus device not found", "WinTest",
MB_ICOMMAND|MB_OK|MB_APPLMODAL) ;
аааа }
аааа else
аааа MessageBox(0, "Another instance already running",
аа "WinTest", MB_ICONEXCLAMATION|MB_OK|MB_APPLMODAL) ;
 
аааа return 0 ;
}

Драйвер виртуального устройства

Файл vxd2.asm (листинги 6 и 7) представляет собой исходный код драйвера фиктивного устройства. Следует отметить, что для того, чтобы построить этот драйвер, необходимо иметь комплект драйверов устройств (Device Driver Kit - DDK) системы Windows фирмы Microsoft, т.к. код написан для 32-битового ассемблера, предусмотренного в комплекте DDK (MASM5). Результирующий модуль может быть скомпонован только DDK-компоновщиком LINK386 и утилитой послекомпоновочной обработки ADDHDR. Кроме того, данный исходный код ссылается на определенное количество включаемых файлов (include files), которые входят в состав только комплекта DDK.

Как было указано, типичный драйвер VxD содержит обязательные включаемые файлы, а кроме того он начинается с вызова макроса Declare _Virtual_Device, который создает блок данных, описывающий виртуальный драйвер для ядра системы Windows. Этот блок данных, фактически, -единственное обозначение, экспортируемое из драйвера VxD. Все остальные точки входа являются производными от данных, содержащихся внутри. Кроме всего прочего, данный макрос описывает имя устройства, порядок его инициализации и его точки входа. Виртуальный драйвер VxD может обслуживать запросы приложений как в реальном, так и в защищенном режимах. Точки входа для такого обслуживания также описываются данным макросом.

Программа vxd2.asm:

PAGE ,132
title VxD2B.ASM - Пример драйвера устройства #2b
;EM VxD2B - Пример драйвера устройства #2b
;
;а Copyright 1992, Cherry Hill Software
;а All rights reserved
;
;а SUMMARY (Резюме)
;ааааа Данныйаа драйвераа имитируетаа прерываемоеаа устройство.аа Порт
;ааааа управления (выход) имеет следующее назначение битов:
;
;ааааа Бит 0 -а Начать (Start)а ввод-вывод. Записьа нуля в данныйа бит
;аааааааааааааа начинаета пересылкуа ввода-вывода.а Пересылкааа длится
;аааааааааааааа околоа 1/10а секунды.а Записьа единицы в этота бита не
;аааааааааааааа дает результата.
;
;ааааа Бит 1 -а Устройствуа посылается EOI. Записьа нуля в данныйа бит
;аааааааааааааа приводит ка посылке признака "Конец прерывания"а (End-
;аааааааааааааа of-interrupt - EOI) устройству и удаляет любойа запрос
;аааааааааааааа на отложенное прерывание. Запись единицы в этот бит не
;аааааааааа аааадает результата.
;ааааа Все остальные биты: Всегда записываются единицы дляа дальнейшей
;ааааа совместимости.
;
;ааааа При чтении порта следующие значения возвращаются:
;
;ааааа Бит 0 - Первоначальноаааа присваиваетсяааа значениеаа 1,ааа бит
;аааааааа ааааасбрасывается, когда бит 1 выходного порта сбрасывается,
;ааааааааааааа иаа устанавливается,а когдааа добавляетсяаа запросаа на
;ааааааааааааа прерывание.а Данныйа бита равена нулю, когда устройство
;ааааааааааааа передает данные и устанавливаетсяа в 1, чтобыа указать,
;ааааааааааааа когда передача завершена.
;
;ааааа Бит 1 - Первоначальноааа присваиваетсяаааа значениеааа 1,аа бит
;ааааааааааааа сбрасывается, когда добавляется запрос на прерываниеа и
;ааааааааааааа устанавливается,а когдаа устройствоа удаляета запрос на
;ааааааааааааа прерывание.а Значениеаа данногоаа бита,а равноеаа нулю,
;ааааааааааааа указывает на отложенное прерывание, бит устанавливается
;ааааааааааааа в 1, если нет отложенного прерывания.
;
;ааааа Всеа остальныеа биты: возвращаемоеа значениеа игнорируетсяа для
;ааааа дальнейшей совместимости.
;
; WARNINGS (Предупреждения)
;
;
.386p
 
.xlist
include vmm.inc
include debug.inc
include v86mmgr.inc
include vpicd.inc
include ..\include\bogus.inc
.list
 
VM_Not_Executable equ VM_Not_Executeable ; acckk;
 
subttlа VxD Declaration/Definition
 
VxD2B_Init_Order equ VNETBIOS_Init_Order+100 ; Данная операция
ааааааааааааааааааааааа выполняется после запуска виртуальной сети
VxD2B_Device_ID equ Bogus_Device_ID
 
Declare_Virtual_Device VXD2, 1, 0, Vxd2B_Control, VxD2B_Device_ID, \
аааааааааааааа VxD2B_Init_Order
 
VxD_DATA_SEG
 
;
; Структура дескриптора виртуального прерывания
;
;а Даннаяаа структураа передаетсяаа VPIDC_Virtualize_IRQ.а Вааа данной
;а структуреа описываетсяа уровеньа прерывания,а процедураа прерывания
;а аппаратныха средств,а иа процедура,а которуюа вызывает VPICD, когда
;а прерываниеа диспетчируетсяа ва виртуальнойа машинеа VMа иа когда VM
;а возвращается из прерывания.
;
IRQD VPICD_IRQ_Descriptor
 
hIRQаааааааа ddаа -1аааа ; обработчик IRQ
hOwnerаааааа ddаа -1аааа ; обработчик, владеющий VM
hTimeoutаааа ddааа 0аааа ; обработчик к обратному вызову по тайм-ауту
bFakeDataааа ddа 01111111b ; имитировать данные порта ввода-вывода
 
VxD_DATA_ENDS
 
subttl Dispatch VxD Control
 
VxD_LOCKED_CODE_SEG
 
BeginProc CheckOwner, NO_LOG
аааа cmp ebx,hOwner
аааа jne short col
аааа retаа ; выйти, если вызывается владелец
col:
аааа cmp hOwner,-1
аааа jne short co2а ; пропустить, если вызов не владельца
аааа mov hOwner,ebx ;установить владельца
аааа ret
co2:
аааа mov al,-1
аааа ret
EndProc CheckOwner
 
BeginProc TimeoutProc
аааа mov hTimeout,0 ;почистить обработчик
аааа cmp edx,hOwner ; все еще тот же владелец?
аааа jne short tolа ; пропустить, если нет
аааа testаа bFakeData,FAKE_STAT_BUSYа ;отложенный ввод-вывод?
аааа jnz short tolа ; пропустить, если нет
аааа cmp hOwner,-1а ; имеется ли владелец?
аааа jeа short tolа ; пропустить, если нет
аааа mov eax,hIRQ
аааа mov ebx,hOwner
аааа VxDcall VPICD_Set_Int_Request ;добавить прерывание
аааа mov al,bFakeData
а аааand al,NOT (FAKE_STAT_IRQ) ; указывает также в порте состояния
аааа orа al,FAKE_STAT_BUSY ; указывает, что больше не занято
аааа mov bFakeData,al
tol:
аааа ret
End Proc TimeoutProc
 
;IP Port_IO_Callback - выполняет доступ к FAKE_PORT
;
; ENTRY (вход)
; аааEAX - выходное значение (для выходных операторов)
;ааа EBX - обработчик к текущему VM
;ааа ECX - тип операции ввода-вывода
;ааа DS,ES - FLAT
;
; EXIT (выход)
;ааа EAX - входное значение (для входных операторов)
;
; WARNINGS (предупреждения)
;
; NOTES (примечания)
;ааааа Следует отметить, что мы даже не смотрим регистровый фрейм
;ааааа клиента.
;
;ааааа Мы просто читаем и увеличиваем.
;
; CALLS (вызовы)
 
BeginProc Port_IO_Callback, NO_LOG
аааа Dispatch_Byte_IO Fall_through,Port_Output_Callback
 
Port_Input_Callback:
аааа callаа CheckOwner
аааа jcа short ioexit
аааа mov al,bFakeData
аааа orа bFakeData,FAKE_STAT_ERRORа ; почистить отложенную ошибку
ioexit:
аааа ret
 
Port_Output_Callback:
аааа callаа CheckOwner
аааа jcа short ioexitааа ;игнорировать ввод-вывод, если не владелец
аааа testаа al,FAKE_CTL_START
аааа jnz short,poc1а ;пропустить, если не начинается ввод-вывод
аааа testа bFakeData,FAKE_START_BUSY
аааа jzа short,poc1а ;пропустить, если уже занято
аааа testа bFakeData,FAKE_START_IRQ
аааа jzа short,poc1а ;пропустить, если отложенное IRQ
аааа pushа eax
аааа pushа edx
аааа and bFakeData,NOT (FAKE_STAT_ERROR)а ;а предположить ошибку
аааа mov eax,100аааа ; обратный вызов в 1/10 секунды
аааа mov edx,hOwnerа ; передать владельца обратному вызову
аааа mov esi,OFFSET32 TimeoutProc
аааа VMMcall Set_VM_Time_Out
аааа pop edx
аааа pop eax
аааа orа esi,esi
аааа jzа short,poc1а ;пропустить, если ошибка
аааа and bFakeData,NOT (FAKE_STAT_BUSY)а ;а указать на занятость
аааа or bFakeData,FAKE_STAT_ERRORа ; в противном случае почистить
аааааааааааааааааааааааааааааааааааа индикацию ошибки
аааа mov hTimeout,esiа ;сохранить обработчик тайм-аута
poc1:
аааа testаа al,FAKE_CTL_EOI
аааа jnz short poc2а ; пропустить, если не посылается EOI
аааа testаа bFakeData,FAKE_STAT_IRQ ;прерывание отложено?
аааа jnz short poc2а ; пропустить, если нет
аааа orаа bFakeData,FAKE_STAT_IRQ ;показать, что прерывание уже не-
аааааааааааааааааааааааааааааааааа отложеное
аааа push eax
аааа mov eax,hIRQ
аааа VxDcall VPICD_Clear_Int_Request
аааа pop eax
poc2:
аааа ret
EndProc Port_IO_Callback
 
; ECX == 0 if unmasking (enabling), ECX != 0 if masking (disabling).
BeginProc VxD2_Mask_Change_Proc
аааа call CheckOwner
аааа jcа short mcp9а ; игнорировать, если нет владельца
аааа jcxzаа mcp9аааа ; пропустить, если не маскировано (включено)
;
; Владелец освобождает управление. Разрешается другой VM войти в
; систему.
;
аааа mov hOwner,-1аа ; почистить владельца
 
mcp9:
аааа ret
EndProc VxD2_Mask_Change_Proc
 
; Вызывается, когда выполняется программа ISR
BeginProc VxD2_VInt_Proc
аааа mov eax,High_Pri_Device_Boost
аааа VMMCall Adjust_Exec_Priorityа ;повышенный приоритет для начальной
ааааааааааааааааааааааааааааааааааа обработки
аааа ret
EndProc VxD2_VInt_Proc
 
;а вызывается при возврате из программы ISR (IRETs)
BeginProc VxD2_IRET_Proc
аааа mov eax,-(High_Pri_Device_Boost)
аааа VMMCall Adjust_Exec_Priorityа ;восстановить приоритет
аааа ret
EndProc VxD2_IRET_Proc
 
ifdef DEBUG
BeginProc VxD2B_Debug_Query
аааа Trace_Outа "VxD2 has no debug command support."
аааа clc
аааа ret
End Proc VxD2B_Debug_Query
endif
 
;
; VxD2B_Control
;
 
CtlDisp macro x
 Control_Dispatch x, VxD2B_&x
 endm
 
Begin_Control_Dispatch VxD2B
аааа CtlDisp Device_Init
ifdef DEBUG
аааа CtlDisp Debug_Query
endif
End_Control_Dispatch VxD2B
 
VxD_LOCKED_CODE_ENDS
VxD_CODE_SEG
VxD_CODE_ENDS
 
subttlа VxD Initialization
 
VxD_ICODE_SEG
 
; EP VxD2B_Device_Init - Некритическая инициализация устройства
;
;а ENTRY (вход)
;аааааааа EBP - фрейм клиента
;аааааааа EBX - системный обработчик VM
;аааааааа DS,ES - FLAT
;
;а EXITа (выход)
;а SUCCESS (успешный)
;а Carry clear ("нет переноса")
;а FAILUREа (аварийный)
;а Carry setа ("есть перенос")
;
;
;а WARNINGS (предупреждения)
;
;а NOTES (примечания)
;
;а CALLS (вызовы)
;
BeginProc VxD2B_Device_Init
ааааааа Debug_Out "VxD2B_Device_Init"
 
ааааааа mov edi,OFFSET32 IRQD
ааааааа VxDcall VPICD_Virtualize_IRQа ; виртуализировать прерывание
ааааааа jcа short vdi1аа ;а выход, если ошибка
ааааааа mov hIRQ,eaxаааа ; сохранить обработчик
 
ааааааа mov edx,FAKE_PORT
ааааааа mov esi,OFFSET32 Port_IO_Callback
ааааааа VMMCall Install_IO_Handler
ааааааа VMMCall Enable_Global_Trappingаа ;
 
ааааааа clcа ; нет ошибки
vdi1:
ааааааа ret
EndProc VxD2B_Device_Init
 
VxD_ICODE_ENDS
 
VxD_REAL_INIT_SEG
 
VxD2B_Real_Init LABEL FAR ;вызывается перед тем, как система Windows
аааааааааааааааааааааааааа входит в защищенный режим
ааааааа mov ax,Device_Load_OKа ;позволяет VxD загрузиться
ааааааа xor bx,bxа ; нет исключенных (Exclude) страниц EMM
ааааааа xor si,siа ; нет элементов экземпляров данных
аааааааааааааааааа ; передать edx немодифицированным
ааааааа ret
 
VxD_REAL_INIT_ENDS
 
ааааааа END VxD2B_Real_Init

Программа vxd2.def

LIBRARYа VXD2
 
DESCRIPTION 'Enhanced Windows VXD2(B) Deviceа (Version 1.0)'
 
EXETYPE DEV386
 
SEGMENTS
ааааааааа _LTEXT PRELOAD NONDISCARDABLE
ааааааааа _LDATA PRELOAD NONDISCARDABLE
ааааааааа _ITEXT CLASS 'ICODE' DISCARDABLE
ааааааааа _IDATA CLASS 'ICODE' DISCARDABLE
ааааааааа _TEXT аCLASS 'PCODE' NONDISCARDABLE
ааааааааа _DATAа CLASS 'PCODE' NONDISCARDABLE
 
EXPORTS
аааааа VXD2_DDBа @1

События, управляющие устройством

По мере того, как система Windows в своей работе проходит различные стадии, начиная со стадии инициализации самой системы, через инициализацию виртуальной машины VM и так далее, каждый установленный драйвер VxD вызывается неоднократно, а именно один раз на каждую стадию. В таблице, приведенной ниже, перечисляются фазы системы Windows и главные события, для которых вызывается каждый драйвер VxD.

аааааааааааааааааааааааааааааааааааааааааааааааааааа Таблица
ааааааааааа Управляющие сообщения драйвера VxD
-------------------+--------------------------------------------------
Sys_Critical_Initа | Первоеааа управляющееааа событие;ааааа прерывания
аааааааааааааааааа | отключаются.а Драйвер VxDа определяета готовность
аааааааааааааааааа | устройства.
-------------------+--------------------------------------------------
Device_Initааааааа | Прерывания разрешаются; драйвер VxD инициализиру-
аааааааааааааааааа | ет устройство;а могут бытьа вызваныаа программы и
аааааааааааааааааа | драйверы системы DOS.
-------------------+--------------------------------------------------
Init_Completeааааа | Указывает,а чтоа всеа драйверы VxDа прошли стадию
аааааааааааааааааа | Device_Init.
-------------------+--------------------------------------------------
System_Exitааааааа | Указывает,а чтоа системааа Windowsаа готовитсяа к
аааааааааааааааааа | закрытиюа и возвратуа в системуа DOS. Памятьа для
аааааааааааааааааа | системы DOSа восстановленаа в состояние,а которое
аааааааааааааааааа | было до работы системы Windows.
-------------------+--------------------------------------------------
Sys_Critical_Exitа | Последнееаа управляющееааа событие;ааа прерывания
аааааааааааааааааа | отключаются.
-------------------+--------------------------------------------------
Create_VMааааааааа | Вызываетсяа переда моментома создания виртуальной
аааааааааааааааааа | машины аVM;а драйвера VxD указывает,а доступны ли
аааааааааааааааааа | ресурсы для создания виртуальной машины VM.
-------------------+--------------------------------------------------
VM_Critical_Initаа | Вторая фаза создания виртуальной машины VM.
-------------------+--------------------------------------------------
VM_Initааааааааааа | Третьяа фазаа созданияаа виртуальнойа машиныа VM.
Sys_VM_Initааааааа | Драйвера VxDа можета аварийноаа завершитьа работу
аааааааааааааааааа | виртуальной машины VM.
-------------------+--------------------------------------------------
Query_Destroyааааа | Позволяет драйверу VxD предупредитьа пользователя
аааааааааааааааааа | о затруднениях при разрушении виртуальнойа машины
аааааааааааааааааа | VM.
-------------------+--------------------------------------------------
VM_Terminateаааааа | Перваяа стадияа успешногоа завершения виртуальной
Sys_VM_Terminateаа | машины VM.а Еслиаа этоаа системнаяааа виртуальная
аааааааааааааааааа | машина VM,аа тоаа сообщениеаа указывает,ааааа что
аааааааааааааааааа | производится нормальное, вызванное пользователем,
аааааааааааааааааа | завершение системы Windows.
-------------------+--------------------------------------------------
VM_Not_Executeable | Виртуальная машина VMа закрывается. Первая стадия
аааааааааааааааааа | аварийного завершения виртуальной машины VM.
-------------------+--------------------------------------------------

Драйвер VxD примера выполняет управление только фазой Device_Init. На этой стадии устанавливается связь с портом ввода-вывода и уровнем прерывания 11, а также производится их виртуализация. Обычно драйвер VxD виртуализирует порты ввода-вывода и прерывание в соответствии с физическим аппаратным оборудованием. Но в данном случае драйвер VxD может виртуализировать и делает это с портом и прерыванием, которые не имеют соответствующего подключенного аппаратного оборудования.

Код Install_IO_Handler вызывается, чтобы виртуализировать единственный порт ввода-вывода. Затем всякий раз, когда осуществляется доступ к описанному порту ввода-вывода из виртуальной машины VM, программа управления виртуальной машиной системы Windows (Virtual Machine Manager - VMM) вызывает обратно драйвер VxD для того, чтобы разрешить ему имитировать операции ввода-вывода.

Код VPICD_Virtualize_IRQ вызывается, чтобы виртуализировать уровень прерывания. Выполняя его, можно имитировать прерывание аппаратного оборудования (в частности IRQ 11) в виртуальной машине.

"Фиктивное" устройство

Когда к порту ввода-вывода (141) устройства осуществляется доступ виртуальной машиной VM (либо в реальном, либо в защищенном режиме), то машина вызывает программу драйвера VxD Port_IO_Callback (см. Листинг 6). В этой программе подпрограмма Dispatch_Byte_IO сводит большое количество возможных типов доступа ввода-вывода (а именно: byte, word, dword, string и т.д.) к двум: байтовому вводу и байтовому выводу.

Для устройства из примера байтовый ввод представляет собой чтение из регистра состояния устройства. Он возвращает просто переменную, которая сохраняется в памяти.

Байтовый вывод - немного более сложная операция, так как представляет фактическую работу устройства. При запуске устройства также запускается таймер, который выполняет обратный вызов (к коду TimeoutProc) в течении 1/10 секунды и устанавливает состояние BUSY. Если вывод подтверждает прием прерывания, то производится очистка виртуального запроса на прерывание путем вызова кода VPICD_Clear_Int_Request и очистка состояния в регистре состояния.

Обратный вызов кода TimeoutProc представляет завершение операции ввода-вывода на устройстве и именно в данный момент он моделирует прерывание аппаратного оборудования к виртуальной машине VM путем вызова кода VPICD_Clear_Int_Request и очистки состояния занятости устройства. Драйвер устройства в приложениях dostest и wintest будет обычно обрабатывать прерывание путем подтвержения приема его (посылая EOI) и повторного запуска процесса на всем протяжении снова.

Следует отметить процедуры VxD2_VInt_Proc и VxD2_IRET _Proc. На данные две процедуры существует ссылка в структуре, которая передается коду VPICD_Virtualize_IRQ. Они вызываются в начале и конце процесса виртуализации прерывания в виртуальную машину VM. Все их функции сводятся к увеличению и сохранению приоритета виртуальной машины VM, которая временно обрабатывает данное прерывание. Таким способом драйвер VxD может управлять приоритетом виртуальной машины VM, которая считается соответствующей. (Всегда желательно, чтобы программа обслуживания прерывания в любой виртуальной машине VM имела приоритет выше, чем приоритет обычной обработки в других виртуальных машинах VM.)

Установка драйвера VxD

После построения драйвера VxD, до первого обращения к нему программы Windows необходимо добавить его как строку device= в секцию [386Enh] кода system.ini. Система Windows должна быть запущена заново, чтобы включить драйвер VxD и виртуальное устройство. После этого, можно выполнять и тестировать приложения dostest и wintest.

Заключение

Хотя драйверы устройств системы Windows кажутся в настоящее время очень сложными, обычные и виртуальные драйверы устройств предоставляют огромное количество возможностей. Однако следует учитывать, насколько более сложными они должны быть на машине MIPS, эксплуатирующей систему Windows NT и код эмулятора 80x86, чтобы обеспечить работу виртуальной машины системы MS-DOS.

 

Hosted by uCoz