Глава 7. Структура и написание драйверов .VXD
Последнюю главу нашей книги я посящаю написанию виртуальных драйверов.
Сокращение VxD
следует понимать как Virtual Device
Driver
, "x
" относится к слову Device (имеется в виду любое
устройство). Вопрос несколько устаревает, так как в Windows 98 принята на
вооружение несколько иная концепция драйверов устройств, а Windows NT никогда не
поддерживала VxD-драйверы, а использует модель, которая называется kernel
mode
(режим ядра). Все же программирование VxD остается актуальным по сей
день, и знать основные положения должен каждый программирующий на языке
ассемблера. В данной главе, отходя от нашей обычной практики, мы будем
интенсивно использовать макроопределения, содержащиеся во включаемых (inc)
файлах пакета DDK. Тем самым нам удастся поместить весь материал в одну главу.
Кроме того, данная глава ориентирована на работу с MASM32.
I
Для программирования VxD-драйверов нам понадобятся файлы
VMM.INC
, SHELL.INC
, VCOND.INC
и др.,
которые можно найти в пакете DDK (Device Development Kit), свободно
распространяемом фирмой Microsoft.
При загрузке Windows программа WIN.COM
загружает драйвер
VMM32.VXD
, называемый Менеджером Виртуальной Машины (Virtual
Machine Manager
), который инициализирует другие VxD-драйверы. Затем
VMM
переключает процессор в защищенный режим и инициализирует
системную виртуальную машину. Кроме уже сказанного, VMM обеспечивает сервис для
других драйверов VXD. При запуске DOS-приложения для него выделяется виртуальная
машина, так что приложению "кажется", что оно работает с настоящей машиной. При
запуске обычных 32-битных приложений они работают в системной виртуальной
машине. Приложения, работающие в разных виртуальных машинах, не подозревают о
существовании других виртуальных машин. Одним из главных назначений виртуальных
драйверов является обеспечение бесконфликтного коллективного доступа к
физической аппаратуре для всех одновременно работающих виртуальных машин. Еще
одна задача, которую приходится решать виртуальным драйверам, - это
осуществление взаимодействия системной виртуальной машиной с другими
виртуальными машинами.
Следует отметить, что в Windows существуют и так называемые обычные драйверы,
имеющие расширение DRV
и структуру DLL-библиотеки, которые
экспортируют API-функции для работы с некоторыми внешними устройствами
(например, видеоадаптером). Эти драйверы получают доступ к внешним устройствам
не напрямую, а посредством VxD-драйверов. Поскольку драйверы VxD работают в
нулевом кольце защиты, они имеют доступ к любому участку памяти и напрямую,
посредством портов ввод-вывода, обращаются к внешним устройствам.
Все виртуальные драйверы делятся на два класса - статические и динамические.
Статические драйверы загружаются во время загрузки системы и остаются в памяти
до выхода из системы. Статические драйверы существовали еще в Windows 3.x.
Динамические драйверы могут загружаться и выгружаться системой по мере
необходимости. Они в основном используются для обслуживания устройств
"Plug and Play
" и загружаются менеджером конфигурации. Загрузить
динамический виртуальный драйвер можно и из обычного приложения при помощи
обычных функций работы с файлами.
Имеется три механизма взаимодействия виртуальных драйверов друг с другом.
- Управляющие сообщения. Эти сообщения посылает виртуальным драйверам менеджер виртуальной машины. Драйверы также могут обмениваться информацией посредством таких сообщений. Это очень напоминает сообщения Windows и взаимодействия приложений друг с другом и операционной системой через эти сообщения.
- Функции обратного вызова. Виртуальный драйвер может позволить вызов функции обратного вызова другому драйверу.
- Виртуальные драйверы и менеджер виртуальной машины могут экспортировать определенные функции для вызова из других виртуальных драйверов. Для вызова функции необходимо знать уникальный номер виртуального драйвера, который экспортирует данную функцию и номер этой функции.
Формат виртуальных драйверов называется LE
-форматом, сокращенно
от "linear executable
". Данный формат поддерживает наличие как
16-битного, так и 32-битного кода. Это актуально для статических виртуальных
драйверов, которые часть работы (инициализация) выполняют в реальном
(незащищенном) режиме. В Windows NT драйверы грузятся в защищенном режиме, по
этой причине данный формат в этой операционной системе не используется.
Код и данные в файле LE
-формата располагаются в сегментах. Ниже
мы опишем возможные классы сегментов.
LCODE
- код или данные, заключенные в этом коде, не могут
сбрасываться системой на диск (paging
).
PCODE
- код может временно помещаться на диск.
PDATE
- аналогично предыдущему, но здесь хранятся данные.
ICODE
- здесь располагается код инициализации, после
инициализации сегмент удаляется из памяти.
DBOCODE
- используется при запуске драйвера "под отладчиком".
SCODE
- статические код и данные. Всегда остается в памяти, даже
если драйвер выгружается.
RCODE
— содержит 16-битный код для инициализации в реальном
режиме.
16ICODE
- 16-битный код инициализации в защищенном режиме.
MCODE
- содержит строки сообщений.
Перечисленные классы сегментов не задаются непосредственно в тексте
программы. Сегменты и классы объявляются в DEF
-файле. Файл
vmm.inc
содержит огромное количество макросов, и нам не избежать
пользоваться ими. Это позволит материал, который я хочу изложить, вместить в
одну главу.
II
Начнем с содержимого DEF
-файла. Содержимое показано на Рис. 4.7.
Здесь перечислены сегменты на все случаи жизни. Вам нет необходимости
использовать все определенные здесь сегменты. Т.о. образом данный файл можно
использовать при создании любого виртуального драйвера. Сегменты, принадлежащие
к одному классу, после компоновки объединяются в один сегмент. Менять следует
только первую строку, где задается имя драйвера. Отметим, что имя драйвера
следует задавать заглавными буквами. Кроме того, в первой строке можно задать
тип драйвера. По умолчанию этот тип статический. Если бы мы записали строку
VXD VXD1 DYNAMIC
, то компоновщик создал бы динамический виртуальный
драйвер.
VXD VXD1 SEGMENTS _LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE _TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE _TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE _BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE _LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _IMSGTABLE CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _IMSGDATA CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE _PTEXT CLASS 'PCODE' NONDISCARDABLE _PMSGTABLE CLASS 'MCODE' NONDISCARDABLE IOPL _PMSGDATA CLASS 'MCODE' NONDISCARDABLE IOPL _PDATA CLASS 'PDATA' NONDISCARDABLE SHARED _STEXT CLASS 'SCODE' RESIDENT _SDATA CLASS 'SCODE' RESIDENT _DBOSTART CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBOCODE CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBODATA CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _16ICODE CLASS '16ICODE' PRELOAD DISCARDABLE _RCODE CLASS 'RCODE' EXPORTS VXD1_DDB @1
Рис. 4.7.1. Файл VXD.DEF используемый для компоновки виртуального драйвера.
В конце файла указывается единственная экспортируемая переменная — блок
описания устройства. DDB - Device Descriptor Block
. В этот блок,
состоящий из 22 полей, который определен в файле vmm.inc
, содержит
информацию о виртуальном драйвере (см. ниже).
В файле vmm.inc
определены макроимена для всех записанных выше
сегментов. Например, для сегмента _LTEXT
задано имя
VxD_LOCKED_CODE SEG
, а для _RCODE
имя
VxD_REAL_INIT_SEG
. Мы будем использовать эти имена в своих
программах.
Обратимся теперь к трансляции виртуальных драйверов. Ниже записаны строки, в результате выполнения которых может быть получен виртуальный драйвер.
ml /coff /c /Сх /DMASM6 /DBLD_COFF /DIS_32 vxd1.asm link /vxd /def:vxd.def vxd1.obj
Константы MASM6
, BLD_COFF
, IS_32
используются операторами условной трансляции, которые заданы в файлах
vmm.inc
и vcond.inc
. Отметим, что если Вы используете
DEF
-файл в том виде, как мы его определили на Рис. 4.7.1, то при
компоновке будут появляться сообщения об отсутствии той или иной секции, что
смело можно проигнорировать.
Для того чтобы выполнить какие-либо действия, нам часто придется использовать
макроопределения из файла vmm.inc
. Познакомимся с некоторыми из
них.
Во-первых, конечно, это Declare_Virtual_Device
. Этот макрос
заполняет структуру DDB
, несколько облегчая нашу задачу. Вот его
полный формат (саму структуру макроса, довольно сложную, можно изучить в файле
vmm.inc
):
Declare_Virtual_Device Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData
Разберем параметры.Name
- имя виртуального драйвера. Должно
совпадать с именем, определенным в
DEF
-файле.MajorVer
, MinorVer
-
младшая и старшая части версии драйвера.CtrlProc
- имя
управляющей процедуры драйвера. Эта процедура принимает и обрабатывает
сообщения, приходящие на драйвер. Принято формировать имя процедуры из двух
частей: имя драйвера и добавка Control
. Например, если имя нашего
драйвера будет VXD1
, то имя процедуры следует взять
VXD1_Control
.DeviceID
- 16-битный уникальный
идентификатор виртуального драйвера. Необходимо указывать в том случае, когда
виртуальный драйвер будет предоставлять свой сервис другим драйверам. Кроме
того, идентификатор может понадобиться, если ваш драйвер собирается работать в
реальном режиме.InitOrder
- порядок загрузки драйвера. Это
всего лишь номер. Драйверы, имеющие меньший номер, загружаются в первую очередь.
Имеет смысл для статических драйверов.V86Proc
,
PMProc
- специфицируют адреса функций, которые будет экспортировать
драйвер для DOS- и обычных приложений. Если экспортирование не предполагается,
параметры следует опустить.Ref_Data
- ссылка на данные,
используемые супервизором ввода-вывода. Обычно опускается.
Макросы Begin_control_dispatch
и
End_control_dispatch
используются для определения управляющей
процедуры. Выглядит это следующим образом.
Begin control_dispatch VXD1 Control_Dispatch message, function End_control_dispatch VXD1
Макрос Control_Dispatch
определяет, какие сообщения какими
функциями будут обрабатываться. Например:
Begin_control_dispatch VXD1 Control_Dispatch INIT_COMPLETE, INIT End_control_dispatch VXD1
Итак, у нас все готово, чтобы собрать простейший драйвер, точнее его скелет, даже пока не понимая весь механизм его работы.
.386P include vmm.inc include vcond.inc DECLARE_VIRTUAL_DEVICE VXD1,1,0, VXD1_Control, \ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER Begin_control_dispatch VXD1 Control_Dispatch INIT_COMPLETE, INIT End_control_dispatch VXD1 VxD_LOCKED_CODE_SEG BeginProc INIT EndProc INIT VxD_LOCKED_CODE_ENDS end