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

Часть 9. Аппаратные прерывания и передача данных в 32-разрядных приложениях Windows

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

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

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

short unsigned int Data=1234h;//Начальное значение для отладки

 

Для того чтобы драйвер мог обращаться к этой ячейке, необходимо переслать в драйвер ее адрес. Это удобно выполнить в функции инициализации InitCard(), включив указатель на беззнаковое короткое целое в передаваемую в драйвер структуру InBuffer и инициализировав этот дополнительный член структуры адресом переменной Data. Функция InitCard() примет теперь следующий вид:

void InitCard(){  
  struct {  
    short unsigned int C0;  
    short unsigned int C1;  
    short unsigned int C2;  
    short unsigned int* dptr;  
    }InBuffer;  
  InBuffer.C0=20;             //Канал 0  
  InBuffer.C1=50000;             //Канал 1  
  InBuffer.C2=10000;             //Канал 2  
  InBuffer.dptr=&Data;             //Адрес переменной Data  
//Вызовем процедуру драйвера DIOC_INIT  
  DeviceIoControl(hVMyD,DIOC_INIT,&InBuffer,8,NULL,0,&cbRet,0);  
  } 

 

Обратите внимание на изменение параметра, определяющего размер входного буфера: добавление в структуру InBuffer адреса переменной Data увеличило его длину до 8 байт.

Для чтения значения переменной Data после передачи в нее из драйвера результата измерений предусмотрим в меню пункт «Чтение данных». Для этого в файл заголовков следует включить определение идентификатора этого пункта меню:

#define MI_DATA 103 

 

а в файл ресурсов – предложение

 MENUITEM "Чтение данных", MI_DATA

 

Функция обработки сообщений от меню дополнится анализом на выбор пункта «Чтение данных» с идентификатором MI_DATA и примет следующий вид:

void OnCommand(HWND hwnd,int id,HWND,UINT){  
  switch(id)  {  
    case MI_START:  
      InitCard();  
      break;  
    case MI_ADDR:  
      GetAddr();  
      break;  
    case MI_DATA:  
      ReadData();  
      break;  
    case MI_EXIT:  
      DestroyWindow(hwnd);  
    }  
  } 

 

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

void ReadData(){  
  char txt[80];  
  wsprintf(txt,"Результат = %Xh = %u",Data,Data);  
  MessageBox(NULL,txt,"Info",MB_ICONINFORMATION);  
  } 

 

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

void OnDestroy(HWND){  
//Вызовем драйвер для разблокирования заблокированной ранее памяти  
  DeviceIoControl(hVMyD,DIOC_EXIT,NULL,0,NULL,0,&cbRet,0);  
  PostQuitMessage(0);  
  } 

 

Вызов не требует передачи каких-либо параметров, поэтому и адреса, и размеры обоих буферов обмена информацией с драйвером имеют нулевые значения. Определение кода действия DIOC_EXIT должно быть включено в заголовочный файл (а также в программу драйвера):

#define DIOC_EXIT 3

 

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

Виртуальный драйвер с доступом к данным приложения из обработчика аппаратных прерываний

;Функции драйвера:  
;0 Проверка версии  
;1 VMyD_Init — Инициализация и пуск платы; блокирование физической памяти   
;2 GetAddr — Передача в приложение базового адреса драйвера  
;3 VMyD_Exit — Разблокирование физической памяти  
DIOC_INIT=1  
DIOC_ADDR=2  
DIOC_EXIT=3  
.386p  
.XLIST  
include vmm.inc  
include vwin32.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;32-битовый адрес следующей далее структуры  
VPICD_IRQ_Descriptor <5,,OFFSET32 VMyD_Int_13>  
lpdata dd 0                     ;Адрес переменной Data приложения  
ndata dd 0                      ;Число блокируемых страниц  
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            ;(1)  
             in             AL,DX             ;(2)  
;Получим адрес буфера приложения и заблокируем физическую память переменной Data  
             mov             EDI,dword ptr [ESI.lpvInBuffer];(3)Адрес буфера приложения  
             mov             EAX,[EDI+6]             ;(4)Получим адрес переменной Data приложения  
             and             EAX,0FFFh             ;(5)Выделим младшие 12 бит — смещение на странице  
             mov             EBX,EAX             ;(6)Сохраним его в EBX  
             mov             EAX,[EDI+6]             ;(7)Получим адрес переменной Data приложения  
             mov             ECX,EAX             ;(8)Занесем его и в ECX  
             shr             ECX,12             ;(9)Полный номер начальной физической страницы  
             add             EAX,1             ;(10)Прибавим длину данных — 1 к адресу переменной  
             shr             EAX,12             ;(11)Полный номер конечной физической страницы с данными  
             sub             EAX,ECX             ;(12)Число страниц, требуемых для данных, — 1  
             inc             EAX             ;(13)Число страниц, требуемых для данных  
             mov             ndata,EAX             ;(14)Сохраним его  
             push             PAGEMAPGLOBAL;(15)Вид блокирования  
             push             EAX             ;(16)Число блокируемых страниц  
             push             ECX             ;(17)Полный номер начальной блокируемой страницы  
             VMMCall _LinPageLock;(18)EAX=новый линейный адрес страницы  
             add             EAX,EBX             ;(19)Новый линейный адрес данного  
             mov             lpdata,EAX             ;(20)Сохраним его  
             add             ESP,12             ;(21)Восстановление стека  
;Засылаем управляющие слова по каналам, сбрасываем счетчик,   
;устанавливаем флаг S2 разрешения счета  
;...         Эта часть программы драйвера полностью повторяет   
;             соответствующий фрагмент предыдущей статьи  
;Размаскируем прерывания  
             mov             EAX,IRQ_Handle  
             VxDCall VPICD_Physically_Unmask  
             clc  
             ret  
EndProc VMyD_Init  
;--------------------------  
BeginProc VMyD_Exit  
;Разблокируем данные приложения  
             mov             EAX,lpdata             ;Возьмем новый линейный адрес начальной страницы  
             shr             EAX,12             ;Полный номер начальной страницы  
             push             PAGEMAPGLOBAL;Тип разблокирования  
             push             ndata             ;Число разблокируемых страниц  
             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            ;в буфер приложения  
             popad  
             clc  
             ret  
EndProc VMyD_Int_13  
VxD_CODE_ENDS  
end VMyD_Real_Init 

 

В начале текста драйвера определяется значение добавленного кода действия DIOC_EXIT. Соответственно в процедуру IOControl включены предложения проверки пришедшего из приложения кода действия на значение DIOC_EXIT и перехода в случае равенства на процедуру VMyD_Exit.

Сущность изменений, внесенных в программу драйвера, заключается в том, что в процедуре VMyD_Init помимо инициализации платы осуществляется блокирование физической памяти с теми данными, к которым нужно получить доступ, а в новой процедуре VMyD_Exit — разблокирование этой памяти. Блокирование осуществляется целыми страницами (4 Кбайт) с помощью функции VMM _LinPageLock; функция в случае своего успешного выполнения возвращает новый линейный адрес первой заблокированной страницы, посредством которого драйвер получает доступ к полям данных приложения.

Рассмотрим подготовку параметров для функции _LinPageLock в процедуре VMyD_Init нашего драйвера (для удобства ссылок предложения этой процедуры пронумерованы). В предложении 3 из структуры DIOCParams извлекается и помещается в регистр EDI адрес входного буфера. В предложении 4 линейный адрес интересующей нас переменной приложения Data забирается из входного буфера (где он располагается в байтах 6-7 от начала буфера) и помещается в регистр — EAX. Как известно, в линейном адресе биты 22-31 определяют номер элемента в каталоге страниц, то есть в сущности, номер таблицы страниц, биты 12-21 — номер страницы, а биты 0-11 — смещение адресуемого данного на странице (рис. 1).

Функция _LinPageLock требует в качестве параметра часть линейного адреса, расположенную в битах 12-31, которую можно условно рассматривать как полный номер физической страницы (не ее адрес!). В предложении 5 из линейного адреса выделяются его младшие 12 бит — смещение на логической и физической страницах, а в предложении 6 оно сохраняется для дальнейшего использования в регистре EBX. Далее формируются полные номера физических страниц с данными — начальной (в регистре ECX) и конечной (в регистре EAX). В предложении 7 в регистре EAX восстанавливается линейный адрес Data, а в следующем предложении он помещается еще и в ECX. Сдвигом вправо на 12 бит содержимое ECX преобразуется в полный номер (начальной) страницы. В предложении 9 к линейному адресу данного в регистре EAX прибавляется величина на 1 меньшая, чем полная длина данных (в нашем случае всего 1 байт), что дает линейный адрес последнего байта блокируемых данных. Эта величина также сдвигается вправо на 12 бит с образованием полного номера (конечной) страницы. Разность двух номеров страниц (предложение 12) после прибавления 1 (предложение 13) дает число страниц, подлежащих блокированию. Необходимость в столь сложной процедуре определения числа блокируемых страниц объясняется тем, что блокируемые данные (даже при небольшой длине) могут начинаться на одной физической странице, а заканчиваться на другой. Полученное число страниц, подлежащих блокированию, сохраняется в ячейке ndata с целью использования в процедуре разблокирования.

В предложениях 15-17 в стек проталкиваются параметры функции VMM _LinPageLock: константа PAGEMAPGLOBAL, используемая в случае блокирования страниц ради доступа к ним из другого контекста, то есть из асинхронной операции, число блокируемых страниц и полный номер начальной блокируемой страницы. Функция _LinPageLock в случае своего успешного выполнения возвращает в регистре EAX новый линейный адрес заблокированного участка памяти. Однако этот адрес характеризует начало страницы; наши данные располагались на странице с некоторым смещением, которое мы выделили и сохранили в регистре EBX. Теперь это смещение можно прибавить к линейному адресу начальной страницы, чтобы получить точный адрес начала данных (предложение 19). В двух последующих предложениях полученный адрес сохраняется в предусмотренной для этого ячейке lpdat и восстанавливается состояние указателя стека, каким оно было перед помещением в стек трех параметров функции _LinPageLock.

Далее в процедуре VMyD_Init, как и в предыдущем примере, таймер-счетчик инициализируется константами, полученными из приложения через входной буфер, и размаскируется наш уровень прерываний.

В обработчике прерываний VMyD_Int_13 выполняются те же действия, что и в предыдущем примере (получение из счетчика результата измерений, сброс флагов готовности и разрешения счета, посылка в контроллер прерываний команды EOI), после чего результат измерений передается в приложение по новому «адресу-псевдониму», сохраненному ранее в ячейке lpdata.

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

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

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует