oldi

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

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

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

Настоящая статья посвящена виртуальным драйверам, предназначенным для обслуживания измерительной или управляющей аппаратуры автоматизированных установок, работающих в режиме прерываний, если программа управления установкой является 32-разрядным приложением Windows. Рассматриваемый ниже пример отлаживался на макете таймера-счетчика, описанного в части 5 этого цикла статей.

В 32-разрядных приложениях Windows, так же как и в 16-разрядных, прикладная обработка аппаратных прерываний требует решения следующих трех задач:

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

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

Исходные тексты первого программного комплекса (дадим ему имя INTR32-1) приведены ниже. Рассмотрим сначала приложение Windows.

Заголовочный файл INTR32-1.H  
//Определения констант  
#define MI_START 100  
#define MI_ADDR 101  
#define MI_EXIT 102  
#define DIOC_INIT 1  
#define DIOC_ADDR 2  
//Прототипы функций  
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();  
Файл ресурсов INTR32-1.RC  
#include "intr32-1.h"  
Main MENU{  
  POPUP "Режимы"  {  
    MENUITEM "Пуск",MI_START  
    MENUITEM "Адрес драйвера",MI_ADDR  
    MENUITEM SEPARATOR  
    MENUITEM "Выход",MI_EXIT  
    }  
  }  
Программный файл INTR32-1.CPP  
#define STRICT  
#include <windows.h>  
#include <windowsx.h>  
#include "intr32-1.h"        //Подсоединение прикладного заголовочного файла  
HANDLE hVMyD;         //Дескриптор открытого драйвера  
char szClassName[]="MainWin";//Имя класса главного окна  
char szTitle[]="Аппаратные прерывания - 1";//Заголовок окна  
DWORD cbRet;          //Длина передаваемых в приложение данных  
//Главная функция WinMain  
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int){  
  MSG msg;  
  Register(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,300,150,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);//Переход по сообщению WM_CREATE  
    HANDLE_MSG(hwnd,WM_COMMAND,OnCommand);//Переход по сообщению WM_COMMAND  
    HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy);//Переход по сообщению WM_DESTROY  
  default:  
    return(DefWindowProc(hwnd,msg,wParam,lParam));  
    }  
  }  
//Функция OnCreate обработки сообщения WM_CREATE при создании окна  
BOOL OnCreate(HWND,LPCREATESTRUCT){  
//Откроем драйвер и получим его дескриптор  
  hVMyD=CreateFile("\\\\.\\VMYD",0,0,NULL,0,FILE_FLAG_DELETE_ON_CLOSE,NULL);  
  return TRUE;  
  }  
//Функция OnCommand обработки сообщений WM_COMMAND от пунктов меню  
void OnCommand(HWND hwnd,int id,HWND,UINT){  
  switch(id)  {  
    case MI_START:  
      InitCard();  
      break;  
    case MI_ADDR:  
      GetAddr();  
      break;  
    case MI_EXIT:  
      DestroyWindow(hwnd);  
    }  
  }  
//Функция OnDestroy обработки сообщения WM_DESTROY о завершении приложения  
void OnDestroy(HWND){  
  PostQuitMessage(0);  
  }  
//Функция InitCard передачи драйверу констант инициализации таймера-счетчика  
void InitCard(){  
  struct {            //Набор констант каналов  
    short unsigned int C0;  
    short unsigned int C1;  
    short unsigned int C2;  
  }InBuffer;  
  InBuffer.C0=20;        //Задаем  
  InBuffer.C1=50000;        //значения  
  InBuffer.C2=10000;        //констант  
//Вызовем процедуру драйвера DIOC_INIT  
  DeviceIoControl(hVMyD,DIOC_INIT,&InBuffer,6,NULL,0,&cbRet,0);  
  }  
//Функция GetAddr получения из драйвера информации о его линейном адресе  
void GetAddr(){  
  char txt [80];  
  int unsigned Addr;  
//Вызовем процедуру драйвера DIOC_ADDR  
  DeviceIoControl(hVMyD,DIOC_ADDR,NULL,0,&Addr,4,&cbRet,0);  
  wsprintf(txt,"Базовый адрес = %Xh",Addr);  
  MessageBox(NULL,txt,"Info",MB_ICONINFORMATION);  
  }  

В предыдущих примерах программный проект включал в себя максимум два файла: исходный текст программы с расширением .CPP и файл ресурсов с расширением .RC, в котором описывался сценарий меню. В настоящем примере появился еще и заголовочный файл с расширением H.

В заголовочном файле INTR32-1.H определены константы двух видов. Константы с префиксом MI_ являются идентификаторами пунктов меню и будут использованы как в файле ресурсов, где они сопоставляются с конкретными пунктами меню, так и в тексте программы для определения того, какой пункт меню выбран пользователем. Константы с префиксом DIOC_ являются кодами действий, которые программа будет пересылать в драйвер, чтобы заказать требуемое действие. Эти числа по существу являются номерами API-функций драйвера.

Вслед за определением констант в заголовочном файле приведены прототипы всех функций, использованных в приложении (кроме, естественно, системных функций, которые описаны в системных заголовочных файлах).

Файл ресурсов INTR32-1.RC начинается с оператора #include, который подключает заголовочный файл INTR32-1.H. В процессе трансляции файла ресурсов из заголовочного файла будут извлекаться числовые значения констант-идентификаторов пунктов меню.

Сценарий меню предполагает наличие в меню трех пунктов: «Пуск» — для инициализации таймера-счетчика и запуска процесса накопления событий, «Адрес драйвера» — для вывода на экран линейного адреса первой процедуры драйвера, который можно использовать в процессе его отладки, и «Выход» — для завершения программы.

Общая структура приложения Windows не отличается от рассмотренной в предыдущих примерах. После создания главного окна с полоской меню приложение входит в цикл обработки сообщений, ожидая выбора пользователем тех или иных пунктов меню. Получив из Windows идентификатор выбранного пункта, приложение переходит на соответствующую прикладную функцию (OnCreate(), OnCommand() или OnDestroy()), после завершения которой возвращается в цикл обработки сообщений на ожидание следующей команды пользователя.

Сообщение WM_CREATE посылается в приложение системой Windows в процессе создания окна, чтобы программист мог заставить свое приложение выполнить необходимые инициализирующие действия, перехватив это сообщение. В рассматриваемом примере в функции OnCreate() вызовом функции Windows CreateFile() открывается драйвер так же, как это делалось в примерах предыдущей части статьи. Полученный в качестве возвращаемого значения дескриптор драйвера сохраняется в глобальной переменной hVMyD для дальнейшего использования.

Приложение обращается к драйверу в двух случаях. Выбор пользователем пункта меню «Адрес драйвера» приводит к вызову прикладной функции GetAddr(), которая вызовом DeviceIoControl() посылает в драйвер код действия DIOC_ADDR. В ответ драйвер через буфер вывода Addr возвращает в приложение линейный адрес своей первой процедуры VMyDControl. По этому адресу можно вычислить (по листингу трансляции драйвера) другие характерные адреса драйвера и использовать их в качестве точек останова при отладке или исследовании драйвера с помощью отладчика SoftICE.

Более содержательное обращение к драйверу выполняется в функции InitCard(), инициируемой при выборе пользователем пункта меню «Пуск». Здесь в вызове DeviceIoControl() содержатся код действия DIOC_INIT и адрес входного (для драйвера) буфера InBuffer с константами C0, C1 и C2 настройки платы таймера-счетчика. Поскольку эти три 2-байтовые константы в сумме занимают 6 байт, в параметрах вызова DeviceIoControl() в качестве размера входного буфера указано число 6. В процедуре драйвера, соответствующей коду действия DIOC_INIT, выполняются инициализация и пуск таймера-счетчика. Окончание временного интервала вызывает прерывание уровня 5, обрабатываемое обработчиком, который входит в состав драйвера. Приложение не получает информации ни об истечении временного интервала, ни о числе сосчитанных событий (хотя драйвер, как мы увидим позже, выводит результаты измерений в свое окно сообщения).

Чтение числа накопленных событий в таком варианте программы возможно только синхронным образом, с помощью еще одного запроса DeviceIoControl() с кодом действия, отличным от уже использованных. Драйвер, обрабатывая этот код действия, может передать в выходной буфер (то есть непосредственно в приложение) результат измерений точно так же, как передается адрес в ответ на запрос DIOC_ADDR. Однако приложение, не получая от драйвера информации о приходе прерывания, не имеет возможности узнать, когда именно в выходном буфере появится результат измерений.

Рассмотрим теперь исходный текст программы драйвера.

Виртуальный драйвер программного комплекса INTR32-1

;Функции драйвера:  
;0 GetAddr - Проверка версии  
;1 VMyD_Init - Инициализация и пуск платы   
;2 GetAddr - Передача в приложение базового адреса драйвера  
DIOC_INIT=1  
DIOC_ADDR=2  
.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  
Mesg db '********-VMyD message',0  
result dw 0           ;Результат измерений  
IRQ_Handle dd 0        ;Дескриптор виртуального прерывания   
VMyD_Int13_Desc label dword;32-битовый адрес следующей далее структуры  
VPICD_IRQ_Descriptor <5,,OFFSET32 VMyD_Int_13>;Структура с информацией  
                      ;о виртуализованном прерывании  
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  
;-----------------------------  
;Процедура, вызываемая при инициализации драйвера системой Windows  
BeginProc VMyD_Device_Init  
        mov        EDI,OFFSET32 VMyD_Int13_Desc  
        VxDCall VPICD_Virtualize_IRQ  
        mov        IRQ_Handle,EAX  
        clc  
        ret  
EndProc VMyD_Device_Init  
;-----------------------  
;Точка входа в драйвер по вызовам DeviceIoControl  
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  
        clc  
        ret  
EndProc        IOControl  
;--------------------------  
;Процедура подтверждения поддержки драйвером IOCTL-интерфейса  
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  
;Получим адрес буфера с данными настройки  
        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_Int_13, High_Freq  
        pushad  
;Получим результат измерений  
        mov        DX,309h       ;Порт старшего байта  
        in        AL,DX        ;Получим старший байт результата  
        mov        AH,AL        ;Отправим его в старшую половину AX  
        dec        DX        ;Порт младшего байта  
        in        AL,DX        ;Получим младший байт результата  
        mov        result,AX        ;Сохраним весь результат в полях данных драйвера  
;Сброс флагов готовности и разрешения счета  
        mov        DX,30Ah  
        out        DX,AL  
;Выполним завершающие действия в PIC и выведем результаты  
        mov        EAX,IRQ_Handle;Дескриптор IRQ  
        VxDCall VPICD_Phys_EOI;Пошлем в контроллер команду EOI  
        VxDCall VPICD_Physically_Mask;Замаскируем наш уровень прерываний  
;Выведем полученный результат в виде сообщения драйвера  
        mov        ESI,offset32 mesg  
        mov        AX,result  
        call        Word_ascii  
        call        Info  
        popad  
        clc  
        ret  
EndProc VMyD_Int_13  
;----------------------------  
;Процедуры преобразования чисел в символьную форму и вывода сообщения   
BeginProc Info  
        ...  
EndProc Info  
BeginProc Word_ascii  
        ...  
EndProc Word_ascii  
BeginProc Bin_ascii  
        ...  
EndProc Bin_ascii  
VxD_CODE_ENDS  
end VMyD_Real_Init  

В управляющей процедуре теперь обрабатываются два сообщения — Device_Init, посылаемое в драйвер системой Windows в процессе его инициализации, и W32_DeviceIOControl, которое является следствием вызова в приложении функции IOCTL-интерфейса DeviceIoControl(). В процедуре драйвера IoControl пришедший вместе с этим сообщением код действия сравнивается с заданными, после чего управление передается на ту или иную процедуру драйвера.

В драйвере можно выделить три процедуры, имеющие отношение к обслуживанию прерываний. На этапе инициализации драйвера система Windows (процедура VMyD_Device_Init) выполняет виртуализацию запросов на прерывания уровня 5 и сохранение дескриптора виртуализованного прерывания в двухсловной ячейке IRQ_Handle.

В ответ на выбор пользователем пункта меню «Пуск» (процедура VMyD_Init) после приема из приложения констант настройки и инициализации таймера-счетчика выполняется размаскирование уровня 5 в контроллере прерываний вызовом функции виртуального контроллера прерываний VPICD_Physically_Unmask.

Наконец, в обработчике прерываний (процедура VMyD_Int_13) после получения из счетчика результата измерений выполняются обычные завершающие действия — посылка в контроллер команды EOI и маскирование уровня 5 контроллера прерываний. В конце этой процедуры выполняются формирование и вывод на экран сообщения драйвера с результатом измерений.

В целом можно сказать, что приведенный выше текст отличается от программы аналогичного драйвера для 16-разрядного приложения (см. часть 6 данной статьи в КомпьютерПресс на CD-ROM № 8’2001) лишь средствами связи с приложением.

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