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


 

В полях данных драйвера зарезервирован ряд ячеек для временного хранения полученных из приложения параметров, а также результата измерений. Особняком стоит ячейка 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, которая может проталкивать в стек как одинарные, так и двойные слова. В нашем случае в стек помещаются два слова - результат измерений и селектор сегмента данных приложения.

Следующая операция - подготовка вызова требуемой функции приложения. Эта операция осуществляется с помощью функции VMM Simulate_Far_Call, которая помещает передаваемые ей в качестве параметров селектор и смещение требуемой функции приложения в поля структуры клиента Client_CS и Client_IP. В результате, когда VMM, передавая управление приложению, снимет со стека структуру клиента и выполнит переход по оставшимся в стеке значениям Client_CS и Client_IP, то в регистрах CS:IP окажется адрес интересующей нас функции, которая и начнет немедленно выполняться. Для того чтобы не потерять то место в приложении, на котором произошла его приостановка из-за прихода прерывания, текущее содержимое полей Client_CS и Client_IP сохраняется в созданной перед этим копии структуры клиента.

Наконец, вызовом Resume_Exec управление передается в приложение. Еще раз подчеркнем, что этот вызов функции приложения является вложенным в VMM и что возможности вызываемой функции весьма ограниченны. Фактически она работает в чуждой для приложения операционной среде. В частности, как уже отмечалось, содержимое сегментных регистров (кроме CS) не соответствует сегментам приложения. Для того чтобы функция isr() могла обратиться к глобальным переменным приложения (адресуемым через регистр DS), мы передаем ей селектор сегмента данных приложения.

 
« Предыдущая статья   Следующая статья »