Виртуальный драйвер для обслуживания аппаратных прерываний - Программа виртуального драйвера
ОГЛАВЛЕНИЕ
Программа виртуального драйвера
Перейдем к рассмотрению программы виртуального драйвера, входящего в состав нашего программного комплекса.
Текст виртуального драйвера, обрабатывающего аппаратные прерывания
;При вызове AX=DS приложения, BX=C0, CX=C1, DX=C2, DI=селектор isr ,SI=смещение isr
.386p
.XLIST
include vmm.inc
include vpicd.inc
.LIST
Declare_Virtual_Device VMyD,1,0,VMyD_Control,8000h, \
Undefined_Init_Order,,API_Handler
;=======================
VxD_REAL_INIT_SEG
BeginProc VMyD_Real_Init
;Текст процедуры инициализации реального режима (см. часть 2 этого цикла)
EndProc VMyD_Real_Init
VxD_REAL_INIT_ENDS
;======================
VxD_DATA_SEG
Data dw 0 ;Ячейка для результата измерений
DSseg dw 0 ;Ячейка для хранения селектора приложения
Segment_Callback dw 0 ;Селектор функции isr приложения
Offset_Callback dd 0 ;Смещение функции isr приложения
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
;Включим в состав драйвера процедуру обработки системного сообщения
;Device_Init об инициализации драйвера
Control_Dispatch Device_Init, VMyD_Device_Init
clc
ret
EndProc VMyD_Control
;----------------------
;Процедура, вызываемая при инициализации драйвера системой
BeginProc VMyD_Device_Init
mov EDI,OFFSET32 VMyD_Int13_Desc;Адрес структуры VPICD_IRQ_Descriptor
VxDCall VPICD_Virtualize_IRQ;Виртуализация устройства
mov IRQ_Handle,EAX;Сохраним дескриптор виртуального IRQ
clc
ret
EndProc VMyD_Device_Init
;-------------------------
;API-процедура, вызываемая из приложения
BeginProc API_Handler
;Получим параметры из приложения
push [EBP.Client_DI]
pop Segment_Callback
push [EBP.Client_AX];DS
pop DSseg
movzx ESI,[EBP.Client_SI]
mov Offset_Callback,ESI
;Общий сброс
mov DX,30Ch
in AL,DX
;Размаскируем уровень 5 в физическом контроллере прерываний
mov EAX,IRQ_Handle
VxDCall VPICD_Physically_Unmask
;Засылаем управляющие слова по каналам
mov DX,303h
mov AL,36h ;Канал 0
out DX,AL
mov AL,70h ;Канал 1
out DX,AL
mov AL,0B6h ;Канал 2
out DX,AL
;Засылаем константы в каналы
mov DX,300h ;Канал 0
mov AX,[EBP.Client_BX];Константа С0
out DX,AL ;Младший байт частоты
xchg AL,AH
out DX,AL ;Старший байт частоты
mov DX,301h ;Канал 1
mov AX,[EBP.Client_CX];Константа С1
out DX,AL ;Младший байт интервала
xchg AL,AH
out DX,AL ;Старший байт интервала
mov DX,302h ;Канал 2
mov AX,[EBP.Client_DX];Константа С2
out DX,AL ;Младший байт частоты
xchg AH,AL
out DX,AL ;Старший байт частоты
;Установим флаг S2 разрешения счета
mov DX,30Bh
in AL,DX
ret
EndProc API_Handler
;-------------------------
;Процедура обработки аппаратного прерывания IRQ5 (вектор 13)
BeginProc VMyD_Int_13, High_Freq
;Получим результат измерений из выходного регистра счетчика
mov DX,309h ;Порт старшего байта
in AL,DX ;Получим старший байт
mov AH,AL ;Отправим его в AH
dec DX ;DX=308h
in AL,DX ;Получим младший байт
mov Data,AX ;Весь результат в Data
;Выполним завершающие действия в PIC и вызовем функцию приложения
mov EAX,IRQ_Handle
VxDCall VPICD_Phys_EOI;EOI в физический контроллер прерываний
VxDCall VPICD_Physically_Mask;Маскируем наш уровень
;Перейдем на синхронный уровень
mov EDX,0 ;Данные отсутствуют
mov ESI,OFFSET32 Reflect_Int;Адрес синхронной процедуры
VMMCall Call_VM_Event;Установим запрос на ее вызов из VMM
clc
ret
EndProc VMyD_Int_13
;-------------------------
;Процедура уровня отложенных прерываний
BeginProc Reflect_Int
Push_Client_State ;Выделим место на стеке для регистров клиента
VMMCall Begin_Nest_Exec;Начнем вложенный блок выполнения
mov AX,Data ;Отправим данное
VMMCall Simulate_Push;в стек клиента
mov AX,DSseg ;Отправим полученный ранее DS
VMMCall Simulate_Push;в стек клиента
mov CX,Segment_Callback;Зашлем полученный ранее адрес функции isr
mov EDX,Offset_Callback;в CS:IP клиента, чтобы после возврата из VMM
VMMCall Simulate_Far_Call;в виртуальную машину вызвалась эта функция
VMMCall Resume_Exec;Возврат из VMM в текущую виртуальную машину
VMMCall End_Nest_Exec;Завершим вложенный блок выполнения
Pop_Client_State ;Освободим место на стеке для регистров клиента
clc
ret
EndProc Reflect_Int
VxD_CODE_ENDS
end VMyD_Real_Init
В полях данных драйвера зарезервирован ряд ячеек для временного хранения полученных из приложения параметров, а также результата измерений. Особняком стоит ячейка IRQ_Handle, в которой хранится дескриптор виртуального прерывания. Этот дескриптор назначается системой на этапе инициализации драйвера и остается неизменным на все время его жизни, то есть до перезагрузки компьютера.
Макрос VPICD_IRQ_Descriptor позволяет описать в полях данных структуру с информацией о виртуализованном прерывании. Обязательными элементами этой структуры являются номер уровня виртуализуемого прерывания и адрес обработчика аппаратного прерывания (VMyD_Int_13 в нашем случае), включаемый в состав драйвера. Для того чтобы макросы виртуального контроллера прерываний были доступны ассемблеру, к программе необходимо подключить (оператором include) файл VPICD.INC.
Виртуализация прерывания осуществляется на этапе инициализации драйвера. До сих пор мы в явной форме не использовали процедуру VMyD_Control, в которой обрабатываются системные сообщения Windows. В рассматриваемом драйвере в состав этой процедуры с помощью макроса Control_Dispatch включена процедура VMyD_Device_Init (имя произвольно), которая будет вызвана при получении драйвером системного сообщения Device_Init. Для обработки большего числа сообщений Windows в процедуру VMyD_Control следует включить по макросу Control_Dispatch на каждое обрабатываемое сообщение (с указанием имен сообщения и процедуры его обработки).
Процедура VMyD_Device_Init содержит вызов функции виртуального контроллера прерываний (VPICD) VPICD_Virtualize_IRQ. Эта функция осуществляет виртуализацию указанного уровня прерываний и возвращает дескриптор виртуального прерывания, который сохраняется нами в ячейке IRQ_Handle с целью дальнейшего использования. Функция VPICD_Virtualize_IRQ фактически устанавливает в системе наш обработчик прерываний, имя которого включено нами в структуру VPICD_IRQ_Descriptor. Начиная с этого момента аппаратные прерывания IRQ5 будут вызывать по умолчанию, не обработчик этого уровня, находящийся в VPICD, а наш обработчик. Правда, для этого надо размаскировать уровень 5 в контроллере прерываний, чего мы еще не сделали.
При вызове драйвера из приложения управление передается API-процедуре драйвера API_Handler. В ней прежде всего извлекаются из структуры клиента переданные в драйвер параметры. Поскольку эти параметры (содержимое регистров клиента) хранятся в стеке уровня 0, то есть в памяти, их нельзя непосредственно перенести в ячейки данных драйвера. Для переноса параметров в некоторых случаях мы использовали стек, в других - регистры общего назначения.
Выполнив команду общего сброса программируемой платы, следует размаскировать прерывания в физическом (не виртуальном) контроллере прерываний. Эта операция осуществляется вызовом функции виртуального контроллера VPICD_Physically_Unmask с указанием ей в качестве параметра в регистре EAX дескриптора виртуального прерывания. Далее выполняется уже рассмотренная в предыдущей части статьи процедура инициализации платы (причем значения констант С0, С1 и С2 извлекаются из структуры клиента). После завершения API-процедуры управление возвращается в приложение до поступления аппаратного прерывания.
Аппаратное прерывание виртуализованного нами уровня через дескриптор таблицы прерываний IDT с номером 55h активизирует обработчик прерываний, входящий в состав VPICD, который, выполнив некоторые подготовительные действия (в частности, сформировав на стеке уровня 0 структуру клиента), передает управление непосредственно нашему драйверу, а именно процедуре обработки аппаратного прерывания VMyD_Int_13. Системные издержки этого перехода составляют около 40 команд процессора, то есть время от момента поступления прерывания до выполнения первой команды нашего обработчика составит на компьютере среднего быстродействия 10...15 мкс.
В процедуре VMyD_Int_13 после выполнения содержательной части (в нашем случае - чтения и запоминания результата измерений) необходимо послать в контроллер прерываний команду EOI, как это полагается делать в конце любого обработчика аппаратного прерывания. Для виртуализованного прерывания это действие выполняется с помощью функции VPICD_Phys_EOI, единственным параметром которой является дескриптор прерывания, сохраненный нами в ячейке IRQ_Handle. Последней операцией является вызов функции VPICD_Physically_Mask, с помощью которой маскируется уровень 5 в физическом контроллере прерываний.
Следует отметить, что названия функций VPICD могут быть обманчивыми. Функция VPICD_Phys_EOI в действительности не разблокирует контроллер прерываний, а размаскирует наш уровень в регистре маски физического контроллера (чего мы, между прочим, не заказывали!). Что же касается команды EOI, то она была послана в контроллер по ходу выполнения фрагмента VPICD еще до перехода на наш обработчик (упомянутые выше 40 команд). Тем не менее вызов функции VPICD_Phys_EOI в конце обработчика прерываний обязателен. Если ею пренебречь, то операционная система будет вести себя точно так же, как если бы в контроллер не была послана команда EOI: первое прерывание обрабатывается нормально, но все последующие - блокируются. Так происходит потому, что при отсутствии вызова функции VPICD_Phys_EOI нарушается работа функции VPICD_Physically_Unmask, которая выполняется у нас на этапе инициализации. Эта функция, выполнив анализ системных полей и обнаружив, что предыдущее прерывание не завершилось вызовом VPICD_Phys_EOI, обходит те свои строки, в которых в порте 21h устанавливается 0 бит нашего уровня прерываний. В результате этот уровень остается замаскированным и прерывания не проходят.
Если обработчик прерываний, включенный в драйвер, выполняет только обслуживание аппаратуры, то на этом его программа может быть завершена. Однако мы хотим оповестить о прерывании приложение, вызвав одну из его функций. VMM предусматривает такую возможность (так называемое вложенное выполнение VM), но для ее реализации следует прежде всего перейти с асинхронного уровня на синхронный.
Проблема заключается в том, что VMM является нереентерабельной программой. Если переход в виртуальный драйвер осуществляется синхронным образом, вызовом из текущего приложения, то, хотя этот переход происходит при участии VMM и, так сказать, через него, в активизированной процедуре виртуального драйвера допустим вызов всех функций VMM. Если же переход в драйвер произошел асинхронно, в результате аппаратного прерывания, то состояние VMM в этот момент неизвестно и в процедуре драйвера допустим вызов лишь небольшого набора функций, относящихся к категории асинхронных. К ним, в частности, относятся все функции VPICD, а также те функции VMM, с помощью которых программа переводится на синхронный уровень (его иногда называют уровнем отложенных прерываний). В справочнике, входящем в состав DDK, указано, какие функции являются асинхронными, и на эту характеристику функций следует обращать внимание.
Идея перехода на уровень отложенных прерываний заключается в том, что в обработчике аппаратных прерываний с помощью одной из специально предназначенных для этого асинхронных функций VMM устанавливается запрос на вызов callback-функции (функции обратного вызова). Эта функция будет вызвана средствами VMM в тот момент, когда переход на нее не нарушит работоспособность VMM. Вся эта процедура носит название <обработка события>.
В понятие <событие> входит не только callback-функция, но и набор условий, при которых она может быть вызвана или которыми должен сопровождаться ее вызов. Так, например, можно указать, что callback-функцию можно вызвать только вне критической секции или что вызов callback-функции должен сопровождаться повышением приоритета ее выполнения. Кроме того, при установке события можно определить данное (двойное слово), которое будет передано в callback-функцию при ее вызове. В составе VMM имеется целый ряд функций установки событий, различающихся условиями их обработки, например Call_When_Idle, Call_When_Not_Critical, Call_Restricted_Event, Schedule_Global_Event, Schedule_Thread_Event и др. Необходимо подчеркнуть, что момент фактического вызова callback-функции заранее определить невозможно. Она может быть вызвана немедленно либо спустя некоторое время, когда будут удовлетворены поставленные условия.
В нашем случае специальные условия отсутствуют и переход на синхронный уровень можно выполнить с помощью простой функции Call_VM_Event, в качестве параметра которой указывается 32-битовое смещение функции обратного вызова, располагаемой в тексте виртуального драйвера. В рассматриваемом примере эта функция названа Reflect_Int.
Команда ret, которой заканчивается обработчик прерываний виртуального драйвера, передает управление VMM, который в удобный для него момент времени вызывает функцию Reflect_Int (реально до вызова может пройти 50...200 мкс). В этой функции вызовом Push_Client_State исходная структура клиента еще раз сохраняется на стеке уровня 0, после чего функцией Begin_Nest_Exec открывается блок вложенного выполнения. Внутри этого блока можно, во-первых, организовать переход на определенную функцию приложения, а во-вторых, создать условия для передачи ей требуемых параметров. Передача параметров осуществляется в соответствии с установленным интерфейсом используемого языка программирования. Поскольку наше приложение написано на языке Си, для его функций действуют правила этого языка: параметры передаются функции через стек, причем расположение параметров в стеке должно соответствовать их перечислению в прототипе и заголовке функции, то есть в глубине стека должен находиться последний параметр, а на вершине стека - первый (в функциях типа <Паскаль>, в частности во всех системных функциях Windows, действует обратный порядок передачи параметров).
Помещение параметров в стек текущей VM осуществляется функцией VMM Simulate_Push, которая может проталкивать в стек как одинарные, так и двойные слова. В нашем случае в стек помещаются два слова - результат измерений и селектор сегмента данных приложения.