Основы разработки прикладных виртуальных драйверов

Часть 10. Синхронизация обработчиков прерываний в 32-разрядных приложениях Windows

К.Г.Финогенов

Методика, описанная в предыдущей статье (см. КомпьютерПресс № 11’2001), далека от совершенства. Она годится лишь для таких систем реального времени, в которых все действия по обработке прерываний могут быть целиком возложены на обработчик прерываний. Например, получив сигнал об изменении состояния установки, обработчик прерываний путем посылки соответствующих команд в порты установки переключает ее управляющие элементы. Основная программа в этом процессе может не участвовать и даже не знать о поступлении сигнала прерывания.

Чаще сигнал прерывания свидетельствует о наступлении такого события, о котором должна быть извещена основная программа. Рассмотрим дальнейшее видоизменение программного комплекса по управлению таймером-счетчиком, в который теперь введена асинхронная обработка прерываний не только в виртуальном драйвере, но и в самой программе. Учитывая относительную сложность этой методики, ниже мы приводим все исходные тексты комплекса (здесь он носит имя INTR32-3), но фактически в них, по сравнению с предыдущими примерами, лишь внесены некоторые дополнения.

Тексты исходных файлов, входящих в приложение Windows

Заголовочный файл INTR32-3.H

//Определения констант  
#define MI_START 100  
#define MI_ADDR 101  
#define MI_EXIT 102  
#define MI_DATA 103  
#define DIOC_INIT 1  
#define DIOC_ADDR 2  
#define DIOC_EXIT 3  
//Прототипы функций  
void Register(HINSTANCE);  
void Create(HINSTANCE);  
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);  
BOOL OnCreate(HWND,LPCREATESTRUCT);  
void OnCommand(HWND,int,HWND,UINT);  
void OnDestroy(HWND);  
void InitCard();  
void GetAddr();  
void ReadData();  
DWORD WINAPI Isr(LPVOID);  
Файл ресурсов INTR32-3.RC  
#include "intr32-3.h"  
Main MENU{  
  POPUP "Режимы"  {  
    MENUITEM "Пуск",MI_START  
    MENUITEM "Адрес драйвера",MI_ADDR  
    MENUITEM "Чтение данных",MI_DATA  
    MENUITEM SEPARATOR  
    MENUITEM "Выход",MI_EXIT  
    }  
  }  

Программный файл INTR32-3.CPP

#define STRICT  
#include <windows.h>  
#include <windowsx.h>  
#include "intr32-3.h"  
char szClassName[]="MainWin";  
char szTitle[]="Аппаратные прерывания - 3";  
HANDLE hVMyD,hEvent,hThread;  
unsigned short int Data=0x1234;  
OVERLAPPED ovlp;  
DWORD dwThreadID,cbRet;  
HINSTANCE hInst;  
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int){  
  MSG msg;  
  Register(hInstance);  
  hInst=hInstance;  
  Create(hInstance);  
  while(GetMessage(&msg,NULL,0,0))  
    DispatchMessage(&msg);  
  return 0;  
  }  
void Register(HINSTANCE hInst){  
  WNDCLASS wc;  
  memset(&wc,0,sizeof(wc));  
  wc.lpszClassName=szClassName;  
  wc.hInstance=hInst;  
  wc.lpfnWndProc=WndProc;  
  wc.lpszMenuName="Main";  
  wc.hCursor=LoadCursor(NULL,IDC_ARROW);  
  wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);  
  wc.hbrBackground=GetStockBrush(WHITE_BRUSH);  
  RegisterClass(&wc);  
}  
void Create(HINSTANCE hInst){  
  HWND hwnd=CreateWindow(szClassName,szTitle,WS_OVERLAPPEDWINDOW,  
    10,10,250,140,HWND_DESKTOP,NULL,hInst,NULL);  
  ShowWindow(hwnd,SW_SHOWNORMAL);  
}  
LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam){  
  switch(msg)  {  
    HANDLE_MSG(hwnd,WM_CREATE,OnCreate);  
    HANDLE_MSG(hwnd,WM_COMMAND,OnCommand);  
    HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy);  
  default:  
    return(DefWindowProc(hwnd,msg,wParam,lParam));  
  }  
}  
BOOL OnCreate(HWND,LPCREATESTRUCT){  
  hVMyD=CreateFile("\\\\.\\VMYD",0,0,NULL,0,  
     FILE_FLAG_DELETE_ON_CLOSE|FILE_FLAG_OVERLAPPED,NULL);  
  return TRUE;  
  }  
void OnCommand(HWND hwnd,int id,HWND,UINT){  
  switch(id)  {  
    case MI_START:  
      InitCard();  
      break;  
    case MI_DATA:  
      ReadData();  
      break;  
    case MI_ADDR:  
      GetAddr();  
      break;  
    case MI_EXIT:  
      DestroyWindow(hwnd);  
    }  
  }  
void OnDestroy(HWND){  
  DeviceIoControl  
    (hVMyD,DIOC_EXIT,NULL,0,NULL,0,&cbRet,0);  
  PostQuitMessage(0);  
  }  
void InitCard(){  
  struct {  
    short unsigned int C0;  
    short unsigned int C1;  
    short unsigned int C2;  
    unsigned short int* dptr;  
  }InBuf;  
  InBuf.C0=20;             //Канал 0  
  InBuf.C1=50000;       //Канал 1  
  InBuf.C2=10000;       //Канал 2  
  InBuf.dptr=&Data;     //Адрес переменной Data  
  hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//Создадим событие  
  ovlp.hEvent=hEvent;  
  hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Isr,NULL,0,&dwThreadID);  
  DeviceIoControl(hVMyD,DIOC_INIT,&InBuf,10,NULL,0,&cbRet,&ovlp);  
  }  
void ReadData(){  
  char txt [80];  
  wsprintf(txt,"Отсчет = %d = %xh",Data,Data);  
  MessageBox(NULL,txt,"Info",MB_ICONINFORMATION);  
  }  
void GetAddr(){  
  char txt [80];  
  int unsigned Addr;  
  DeviceIoControl  
    (hVMyD,DIOC_ADDR,NULL,0,&Addr,4,&cbRet,0);  
  wsprintf(txt,"Базовый адрес = %Xh",Addr);  
  MessageBox(NULL,txt,"Info",MB_ICONINFORMATION);  
  }  
/*Асинхронная функция, вызываемая драйвером  
из обработчика аппаратного прерывания*/  
DWORD WINAPI Isr(LPVOID){  
  char txt [80];  
  GetOverlappedResult(hVMyD,&ovlp,&cbRet,TRUE);  
  wsprintf(txt,"Время истекло!\nData = %d = %xh",Data,Data);  
  MessageBox(NULL,txt,"Асинхронный вызов",MB_ICONINFORMATION);  
  return 0;  
  }  

В текст приложения внесено не так уж много изменений, хотя они носят весьма принципиальный характер. В полях данных появились два новых дескриптора типа HANDLE для события hEvent и потока hThread, а также переменная dwThreadID, в которую система вернет идентификатор созданного потока. И событие, и поток понадобятся для включения в программу асинхронной функции (она у нас названа Isr), которая будет активизироваться из обработчика аппаратного прерывания, включенного в состав виртуального драйвера. Соответственно в заголовочном файле INTR32-3.H описан ее прототип:

DWORD WINAPI Isr(LPVOID);  

В полях данных приложения объявлена также структурная переменная ovlp типа OVERLAPPED. Она  содержит информацию, используемую при выполнении асинхронных операций. Заметим, что слово overlapped («с перекрытием»), с которым мы еще встретимся в других местах программы, в данном контексте обозначает именно асинхронные операции.

Структура OVERLAPPED описана в файле WINBASE.H следующим образом:

typedef struct _OVERLAPPED {  
    DWORD   Internal;   //Системное состояние   
    DWORD   InternalHigh;  //Длина передаваемых данных  
    DWORD   Offset;     //Позиция в файле  
    DWORD   OffsetHigh; //Старшее слово позиции в файле  
    HANDLE  hEvent;     //Дескриптор события, служащего для синхронизации потоков  
} OVERLAPPED, *LPOVERLAPPED;  

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

Для того чтобы виртуальный драйвер мог выполнять асинхронные операции, при его открытии функцией CreateFile() необходимо указать, кроме флага FILE_FLAG_DELETE_ON_CLOSE, еще и флаг FILE_FLAG_OVERLAPPED:

hVMyD=CreateFile("\\\\.\\VMYD",0,0,NULL,0,  
     FILE_FLAG_DELETE_ON_CLOSE|FILE_FLAG_OVERLAPPED,NULL);  

Синхронизация приложения Windows и драйвера (точнее, его обработчика прерываний) осуществляется с помощью двух фундаментальных понятий 32-разрядной среды (будем называть ее для краткости Win32) — потока и события. Экземпляр загруженной в память программы представляет собой процесс по терминологии Win32. Процесс не является активным объектом — ему просто принадлежит 4-гигабайтное адресное пространство, а также другие ресурсы, в частности файлы, с которыми работает программа. Для выполнения программы следует создать в рамках процесса по крайней мере один поток. Первичный поток, который является последовательностью выполнения предложений программы, операционная система всегда создает сама при инициализации процесса. В программе с одним потоком ход выполнения определяется последовательностью ее предложений, а все процессорное время отдается единственному потоку (разумеется, если в системе запущено несколько программ, то процессорное время будет разделяться между ними).

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

Наличие в процессе нескольких потоков требует их взаимной синхронизации. Для этого в Win32 предусмотрен целый ряд синхронизирующих объектов: критические секции, мьютексы, события и др. В наших целях удобно воспользоваться событием.

Событие, как и другие синхронизирующие объекты, может находится в одном из двух состояний: свободном (signaled) и занятом (nonsignaled). Поток же, в свою очередь, можно остановить в ожидании освобождения конкретного события. Если это событие занято, то поток спит и операционная система не выделяет ему процессорного времени. При этом система знает, какое именно событие может разбудить поток. Как только это событие перейдет с свободное состояние (то есть начнет сигнализировать о себе), поток просыпается и начинает свое выполнение.

В приведенном примере создание потока и синхронизирующего события выполняется в функции InitCard(), активизируемой выбором пункта «Пуск» главного меню приложения. Сначала с помощью функции CreateEvent() создается событие:

hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//Создадим событие  

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

Второй параметр позволяет задать тип события: со сбросом вручную (тогда этот параметр должен иметь значение TRUE) или с автосбросом (FALSE). Тип сброса определяет поведение события после освобождения синхронизируемых им потоков: при автосбросе событие автоматически переводится в занятое состояние, а при сбросе вручную для этого необходима функция ResetEvent(). Сброс вручную используется в основном при синхронизации одним событием нескольких потоков. Поскольку в нашем случае синхронизируется один поток, то мы задаем режим автосброса.

Третий параметр является флагом начального состояния события. Создаваемый нами дополнительный поток должен находиться в спящем состоянии до прихода прерывания, поэтому исходное состояние синхронизирующего его события должно быть занятым, чему соответствует значение FALSE.

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

Функция CreateEvent() в случае успешного выполнения возвращает дескриптор созданного события. Его необходимо заслать в последний элемент структуры OVERLAPPED:

ovlp.hEvent=hEvent;  

Создав событие и назначив ему занятое состояние (то есть состояние, которое будет блокировать выполнение связанного с этим событием потока), можно создать сам поток. Фактически потоком будет являться асинхронная процедура Isr(), входящая в состав нашего приложения. Поток создается функцией CreateThread():

hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Isr,NULL,0,&dwThreadID);  

Нулевые значения двух первых параметров этой функции определяют использование значений по умолчанию для атрибутов защиты потока и размера его стека. Третий параметр самый важный: он определяет адрес той функции, которая начнет выполняться, когда поток будет активизирован. Этот адрес представляет собой переменную типа LPTHREAD_START_ROUTINE; использование имени типа в скобках перед именем функции преобразует ее адрес в требуемый тип. Четвертый параметр передается создаваемому потоку, который может использовать его в качестве инициализирующего значения. Указание в качестве пятого параметра константы CREATE_SUSPENDED позволяет задержать начало выполнения создаваемого потока до выполнения функции ResumeThread(); нулевое значение этого параметра определяет немедленное исполнение потока (конкретно функции Isr()). В качестве последнего параметра указывается адрес переменной, в которую функция CreateThread() вернет идентификатор нового потока.

Итак, сразу после создания потока начинает выполняться функция Isr(). Нам же надо, чтобы она активизировалась лишь в случае прихода в виртуальный драйвер аппаратного прерывания. Для перевода потока с функцией Isr() в спящее состояние выполняется вызов:

GetOverlappedResult(hVMyD,&ovlp,&cbRet,TRUE);  

Если в качестве последнего параметра этой функции указывается значение TRUE, функция приостанавливает выполнение потока в ожидании установки в свободное состояние того события, дескриптор которого находится в структурной переменной типа OVERLAPPED. Адрес этой переменной указывается в качестве второго параметра функции GetOverlappedResult(). Первым параметром функции выступает дескриптор драйвера, участвующего в асинхронной операции, а третьим — адрес счетчика числа передаваемых в асинхронной операции байтов (у нас передача байтов отсутствует). Таким образом, запустив поток, мы тут же остановили его в ожидании установки события.

Вернемся к функции InitCard(). Завершающим действием в этой функции после создания события и потока является обращение к драйверу, чтобы, во-первых, передать ему константы инициализации платы и адрес переменной Data, а во-вторых, активизировать механизм асинхронных операций. Последнее достигается указанием в качестве последнего параметра функции DeviceIoControl() адреса структуры OVERLAPPED. Этот адрес помещается в элемент lpoOverlapped структуры DIOCParams, передаваемой драйверу вместе с сообщением W32_DeviceIOControl, и может быть извлечен оттуда драйвером так же, как извлекаются из этой структуры передаваемые в драйвер прикладные данные.

На рис. 1 показано главное окно приложения INTR32-3 с открытым меню (результаты выполнения приложения будут проиллюстрированы позже, после рассмотрения программы соответствующего ему виртуального драйвера).

Исходный текст виртуального драйвера INTR32-3.ASM

;Функции драйвера:  
;0 Проверка версии  
;1 VMyD_Init - Инициализация и пуск платы   
;2 VMyD_Addr - Передача в приложение базового адреса драйвера  
;3 VMyD_Exit - Разблокировка физической памяти  
DIOC_INIT=1  
DIOC_ADDR=2  
DIOC_EXIT=3  
.386p  
.XLIST  
include vmm.inc  
include vwin32.inc  
include shell.inc  
include vpicd.inc  
.LIST  
Declare_Virtual_Device VMyD,1,0,VMyD_Control,8000h, \  
     Undefined_Init_Order  
;=======================  
VxD_REAL_INIT_SEG  
...     ;Стандартная процедура инициализации реального режима  
VxD_REAL_INIT_ENDS  
;======================  
VxD_DATA_SEG  
result  dw 0   
IRQ_Handle dd 0  
VMyD_Int13_Desc label dword  
VPICD_IRQ_Descriptor <5,,OFFSET32 VMyD_Int_13>  
lpdata dd 0             ;Линейный адрес данного из приложения  
ndata dd 0              ;Число блокируемых страниц с данными  
lpovlp dd 0             ;Линейный адрес структуры ovlp из приложения  
novlp dd 0              ;Число блокируемых страниц с ovlp  
VxD_DATA_ENDS  
;======================  
VxD_CODE_SEG  
BeginProc VMyD_Control  
Control_Dispatch W32_DeviceIOControl,IOControl  
Control_Dispatch Device_Init, VMyD_Device_Init  
        clc  
        ret  
EndProc VMyD_Control  
;-----------------------------  
BeginProc VMyD_Device_Init  
        mov   EDI,OFFSET32 VMyD_Int13_Desc  
        VxDCall VPICD_Virtualize_IRQ  
        mov   IRQ_Handle,EAX  
        clc  
        ret  
EndProc VMyD_Device_Init  
;-----------------------  
BeginProc IOControl  
        cmp   dword ptr [ESI.dwIOControlCode],DIOC_GETVERSION  
        je    GetVer  
        cmp   dword ptr [ESI.dwIOControlCode],DIOC_INIT  
        je    VMyD_Init  
        cmp   dword ptr [ESI.dwIOControlCode],DIOC_ADDR  
        je    GetAddr  
        cmp   dword ptr [ESI.dwIOControlCode],DIOC_EXIT  
        je    VMyD_Exit  
        clc  
        ret  
EndProc IOControl  
;--------------------------  
BeginProc GetVer  
        xor   EAX,EAX  
        clc     
        ret  
EndProc GetVer  
;--------------------------  
BeginProc GetAddr  
        mov   EDI,dword ptr [ESI.lpvOutBuffer]  
        mov   [EDI],offset32 VMyD_Control  
        clc     
        ret  
EndProc GetAddr  
;-----------------------------  
BeginProc VMyD_Init  
;Общий сброс  платы  
        mov   DX,30Ch  
        in    AL,DX  
;Заблокируем данные Data приложения  
        mov   EDI,dword ptr [ESI.lpvInBuffer];Адрес буфера приложения  
        mov   EAX,[EDI+6]  ;Получим адрес переменной Data приложения  
        and   EAX,0FFFh  ;Выделим младшие 12 бит – смещение на странице  
        mov   EBX,EAX    ;Сохраним его в EBX  
        mov   EAX,[EDI+6] ;Получим адрес переменной Data приложения  
        mov   ECX,EAX    ;Занесем его также  в ECX  
        shr   ECX,12     ;Полный номер начальной физической страницы  
        add   EAX,1      ;Прибавим длину данных, равную  1, к адресу переменной  
        shr   EAX,12     ;Полный номер конечной физической страницы с данными  
        sub   EAX,ECX    ;Число страниц, требуемых для данных, равное  1  
        inc   EAX        ;Число страниц, требуемых для данных  
        mov   ndata,EAX  ;Сохраним его  
        push  PAGEMAPGLOBAL;Вид блокирования  
        push  EAX        ;Число блокируемых страниц  
        push  ECX        ;Полный номер начальной блокируемой страницы  
        VMMCall _LinPageLock;EAX=новый линейный адрес страницы  
        add   EAX,EBX    ;Новый линейный адрес данных  
        mov   lpdata,EAX ;Сохраним его  
        add   ESP,12     ;Восстановление стека  
;Заблокируем структуру ovlp в приложении  
        mov   EAX,dword ptr [ESI.lpoOVerlapped];Получим адрес структуры ovlp  
        and   EAX,0FFFh  ;Выделим младшие 12 бит – смещение на странице  
        mov   EBX,EAX    ;Сохраним его в EBX  
        mov   EAX,dword ptr [ESI.lpoOverlapped];Получим адрес структуры ovlp  
        mov   ECX,EAX    ;Занесем его  также  в ECX  
        shr   ECX,12     ;Полный номер начальной физической страницы  
        add   EAX,19     ;Прибавим длину блокируемых данных, равную 1  
        shr   EAX,12     ;Полный номер конечной физической страницы с ovlp  
        sub   EAX,ECX    ;Число страниц, требуемых для ovlp, равное 1  
        inc   EAX        ;Число страниц, требуемых для ovlp  
        mov   novlp,EAX  ;Сохраним его  
        push  PAGEMAPGLOBAL;Вид блокирования  
        push  EAX        ;Число блокируемых страниц  
        push  ECX        ;Полный номер начальной блокируемой страницы  
        VMMCall _LinPageLock;EAX=новый линейный адрес страницы с ovlp  
        add   EAX,EBX    ;Новый линейный адрес ovlp  
        mov   lpovlp,EAX ;Сохраним его  
        add   ESP,12     ;Восстановление стека  
;Получим адрес буфера с данными настройки  
        mov   EDI,dword ptr [ESI.lpvInBuffer]  
;Засылаем управляющие слова по каналам  
        mov   DX,303h    ;Регистр команд  
        mov   AL,36h     ;Канал 0, режим 3  
        out   DX,AL  
        mov   AL,70h     ;Канал 1, режим 0  
        out   DX,AL  
        mov   AL,0B6h    ;Канал 2, режим 3  
        out   DX,AL  
;Программируем канал 0 - 1-ю половину таймера  
        mov   AX,[EDI+0] ;1-й параметр  
        mov   DX,300h  
        out   DX,AL      ;Младший байт частоты  
        xchg  AL,AH  
        out   DX,AL      ;Старший байт частоты  
;Програмируем канал 1 - 2-ю половину таймера  
        mov   AX,[EDI+2] ;2-й параметр  
        mov   DX,301h  
        out   DX,AL      ;Младший байт интервала  
        xchg  AL,AH  
        out   DX,AL      ;Старший байт интервала  
;Программируем внутренний генератор  
        mov   AX,[EDI+4] ;3-й параметр  
        mov   DX,302h      
        out   DX,AL  
        xchg  AH,AL  
        out   DX,AL  
;Сбрасываем счетчик  
        mov   DX,308h  
        out   DX,AL  
;Установим флаг S2 разрешения счета  
        mov   DX,30Bh  
        in    AL,DX  
;Размаскируем прерывания  
        mov   EAX,IRQ_Handle  
        VxDCall VPICD_Physically_Unmask  
        clc  
        ret  
EndProc VMyD_Init  
;--------------------------  
BeginProc VMyD_Exit  
;Разблокируем переменную Data приложения  
        mov   EAX,lpdata ;Адрес-псевдоним  
        shr   EAX,12     ;Полный номер начальной страницы  
        push  PAGEMAPGLOBAL  
        push  ndata      ;Число страниц  
        push  EAX        ;Полный номер начальной страницы  
        VMMCall _LinPageUnLock  
        add   ESP,12  
;Разблокируем структуру ovlp приложения  
        mov   EAX,lpovlp  
        shr   EAX,12     ;Полный номер начальной страницы  
        push  PAGEMAPGLOBAL  
        push  novlp      ;Число страниц  
        push  EAX        ;Полный номер начальной страницы  
        VMMCall _LinPageUnLock  
        add   ESP,12  
        clc     
        ret  
EndProc VMyD_Exit  
;---------------------------------  
BeginProc VMyD_Int_13, High_Freq  
        pushad  
;Получим данное  
        mov   DX,309h  
        in    AL,DX  
        mov   AH,AL  
        dec   DX  
        in    AL,DX  
        mov   result,AX  
;Сброс флагов готовности и разрешения счета  
        mov   DX,30Ah  
        out   DX,AL  
;Выполним завершающие действия в PIC и выведем результаты  
        mov   EAX,IRQ_Handle  
        VxDCall VPICD_Phys_EOI  
        VxDCall VPICD_Physically_Mask  
        mov   AX,result  
        mov   EDI,lpdata ;Передадим данное  
        mov   [EDI],AX   ;в буфер приложения  
        mov   EAX,lpovlp ;Адрес структуры OVERLAPPED  
        mov   EBX,[EAX.O_Internal];Значение члена Internal  
        VxDCall VWIN32_DIOCCompletionRoutine  
        popad  
        clc  
        ret  
EndProc VMyD_Int_13  
VxD_CODE_ENDS  
end VMyD_Real_Init  

Этот вариант виртуального драйвера отличается от предыдущего варианта  двумя особенностями. Во-первых, в нем осуществляется блокирование в памяти физических страниц не только для данного Data, но и для структурной переменной ovlp, к которой также надо обеспечить доступ из обработчика аппаратных прерываний. Во-вторых, вызовом функции VWIN32_DIOCCompletionRoutine перед завершением обработчика прерываний драйвер оповещает приложение об окончании асинхронной операции. Этот вызов приводит к установке события и переводу потока с функцией Isr() в состояние выполнения.

Для обеспечения блокирования переменной ovlp адрес этой структуры, как уже отмечалось выше, пересылается в драйвер на этапе инициализации платы вместе с константами инициализации платы и адресом переменной Data. В сегменте данных драйвера резервируются двухсловные ячейки lpdata и lpovlp для адресов блокируемых переменных приложения, а также ячейки ndata и novlp для хранения числа блокируемых страниц.

В процедуре VMyD_Init драйвера теперь блокируются два участка физической памяти приложения: с переменной Data и со структурой ovlp.

Установка события, разблокирующего спящий поток с функцией Isr() приложения, осуществляется в конце процедуры VMyD_Init_13 обработчика аппаратных прерываний драйвера. После чтения результата измерений, посылки в контроллер прерываний команды EOI, маскирования нашего уровня прерываний и передачи в приложение (в переменную Data) результата измерений выполняется вызов функции VWIN32_DIOCCompletionRoutine, которую предоставляет системный виртуальный драйвер VWIN32.VXD. Этот вызов оповещает систему о завершении асинхронной операции в виртуальном драйвере и переводит в свободное состояние то событие, дескриптор которого содержится в элементе hEvent структуры OVERLAPPED (вспомним, что, создав событие, мы поместили его дескриптор hEvent в элемент ovlp.hEvent этой структуры). Функция VWIN32_DIOCCompletionRoutine требует, чтобы в регистре EBX содержалось такое значение элемента Internal структуры OVERLAPPED, которое было передано в драйвер на этапе активизации механизма асинхронных операций. Поэтому вызов этой функции выглядит следующим образом:

        mov   EAX,lpovl  ;Адрес структуры OVERLAPPED  
        mov   EBX,[EAX.O_Internal];Значение члена Internal  
        VxDCall VWIN32_DIOCCompletionRoutine  

Для того чтобы обратиться к элементу Internal в структуре OVERLAPPED, надо знать символическое обозначение смещения этого элемента. Состав структуры OVERLAPPED, который был приведен выше и в котором фигурирует имя Internal, определен в файле WINBASE.H — заголовочном файле для программ, написанных на языке Си. В программах на языке ассемблера его, естественно, использовать нельзя. Однако такая же структура OVERLAPPED описана и в файле VWIN32.INC, входящем в состав пакета DDK и предназначенном для программ на языке ассемблера (только там она называется _OVERLAPPED). Первый элемент этой структуры  носит имя O_Internal, которое мы и использовали в приведенном выше фрагменте.

На рис. 2 показан ход   выполнения программы. Видно, что после выбора пункта «Пуск» и истечения заданного интервала времени на экран было выведено окно сообщения из асинхронной функции Isr(). После этого для контроля был выбран пункт меню «Чтение данных», в котором вызывается прикладная функция ReadData(). Она, естественно, вывела из переменной Data то же число (101 событие).

КомпьютерПресс 12'2001