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

         

Библиотека 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

; конец файла

_____________________________________________________________________

     Листинг 2. Программа bogusa.asm


Драйвер устройства в системе 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

_____________________________________________________________________

     Листинг 1. Программа dostest.asm

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



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


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

_____________________________________________________________________

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

_____________________________________________________________________

     Листинг 6. Программа vxd2.asm

_____________________________________________________________________

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

_____________________________________________________________________

     Листинг 7. Программа vxd2.def


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


     Кроме отдельного программного модуля  для программы 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  фирмы  Microsoft не являются исключением. Если вы  когда-либо писали  обычное приложение  в системе  Windows, то вам известно, что требуется определенное количество скрытых  способов, чтобы  приложение  работало   надежно.  Как  подмножество   приложений Windows, драйверы устройств системы Windows следуют этому же  правилу. В  данной  статье  автор  рассматривает работающий драйвер устройства, который  обеспечивает  доступ  к  портам  ввода-вывода  и обрабатывает прерывания, и виртуальный драйвер устройства (VxD), который  имитирует технические  средства.   Предполагается,  что  читатель  знает  основы программирования в  системе Windows,  включая библиотеки  динамической связи (dynamic link libraries - DLLs).



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


     Когда  к  порту  ввода-вывода  (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.)



Интерфейс системы 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 должна вызываться только тогда, когда больше нет необходимости в обратном вызове.




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

{

     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 ;

}

/* конец файла*/

_____________________________________________________________________

     Листинг 4. Программа bogus.c


Приложение WINTEST


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

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

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

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

_____________________________________________________________________

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

}

______________________________________________________________________

а аааTшёЄшэу 5. ¦ЁюуЁрььр wintest.c.


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


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

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

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



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


     По  мере  того,  как  система  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) в виртуальной машине.

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


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



Устройство


     Рассматриваемое устройство  - это  не часть  технических средств, которая  была  разработана,   чтобы  продемонстрировать,  как   писать драйвер  устройства  в  системе   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. Биты порта управления устройства



следует учитывать, насколько более  сложными


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