Основы разработки прикладных виртуальных драйверов
Часть 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