Александр Тарво - Использование NuMega DriverStudio для написания WDM-драйверов
Рис. 5 — объект очереди, внедренный в объект драйвера.
Эти классы имеют методы, сходные с методами класса KDeviceQueue. Впрочем, ситуации, когда следует применять более одной очереди для буферизации запросов, встречаются не так уж и часто.
Обработка запросов на прерывание при помощи DriverWorksВ контексте данного руководства будем считать, что прерывание (Interrupt) – асинхронный аппаратный сигнал, который обычно возникает, когда периферийному устройству необходимы ресурсы процессора. "Асинхронный" означает то, что прерывание возникает в произвольные моменты времени (если вообще возникает). Прерывание заставляет процессор прервать выполнение программы, сохранить свое состояние, обработать поступивший запрос (вызывается процедура обработки прерывания, Interrupt Service Routine, ISR) и возобновить выполнение прерванной программы. При этом останавливаются все остальные процессы и потоки ОС вне зависимости от их приоритета.
Для того, чтобы удовлетворять разнообразным требованиям, возникающим при работе разнообразных устройств и программ на различных типах компьютеров, ОС предлагает концепцию уровня запроса на прерывание (Interrupt Request Level), IRQL. Всего существует 32 IRQL для данного процессора, пронумерованных от 0 до 31. При этом 0 — самый низкий приоритет, 31 — самый высокий.
31 Сбой работы шины 29 Сбой в цепи питания 28 Запрос от другого процессора (в многопроцессорной системе) Прерывания, доступные устройствам В/В 2 Выполнение DPC 1 Исключение защиты (page fault) 0 Passive levelТабл. 1 – уровни IRQL.
Для катастрофических событий ОС резервирует самые приоритетные прерывания (31–29). Для программных прерываний — прерывания с самым низким приоритетом (2–1). PassiveLevel — обычный режим работы драйвера. IRQL, предоставляемые для работы системных устройств, находятся где-то посредине нумерации уровней. О том, как эти прерывания сопрягаются с архитектурой компьютера, заботится HAL.
Естественно, в любой момент процессор может обрабатывать только один запрос на прерывание. Обработка поступившего прерывания прервется только в том случае, если поступит прерывание с более высоким приоритетом.
При проектировании процедуры обработки прерывания следует минимизировать время, которое будет затрачено на обработку прерывания. Иначе процессор будет чересчур долго обрабатывать прерывание и ни один процесс не сможет возобновить свою работу. Когда вызывается ISR первое, что она должна сделать сообщить оборудованию, что запрос на прерывание получен и обработан. После этого можно завершать обработку прерывания. Но как тогда обработать данные, поступившие от устройства, если мы сразу же завершим обработку прерывания? Для этого введен механизм вызова отложенных процедур (Deferred Procedure Call, DPC). Перед завершением работы ISR следует вызвать отложенную процедуру (DPC). DPC начнет выполнятся, как только процессор освободится от обработки прерываний. DriverWorks предоставляет класс KDeferredCall, в котором инкапсулируются данные и методы, необходимые для использования механизма DPC.
DriverWorks инкапсулирует все функции, необходимые для обработки прерываний, в классе KInterrupt. Экземпляр класса KInterrupt должен быть создан, как свойство в классе устройства. Пусть в нашем случае класс устройства называется MyDevice, объект класса KInterrupt – m_TheInterrupt. Далее в классе устройства описывается функция ISR:
BOOLEAN MyDevice::TheIsr(void);
Далее, в методе OnStartDevice следует добавить код для привязки ISR к устройству:
status = m_TheInterrupt.InitializeAndConnect(pAssignedResource,Isr,Context,0,FALSE);
где Context — значение без типа (void), передаваемое ISR.
Isr — адрес ISR, процедуры обработки прерываний.
Теперь осталось только добавить в конструктор следующий код:
VOID MyDevice::MyDevice(void) {
. . .
status = m_TheInterrupt.InitializeAndConnect(pAssignedResource, LinkTo(Isr), this, 0, FALSE );
. . .
}
Для отключения ISR следует вызвать метод Disconnect().
Естественно, данное описание не претендует быть полным описанием такой важной темы, как обработка прерываний и связанные с ней проблемы. Но в примере драйвера, описываемом ниже, отсутствует реакция на прерывания, а не упомянуть о них нельзя. Для более подробного обзора темы прерываний и DPC следует обратиться к документации DriverWorks или DDK.
Объекты для управления оборудованиемКак было упомянуто выше, объект устройства управляет работой устройства при помощи специальных объектов, управляющих работой оборудования – портами В/В, прерываниями, памятью, контроллерами ПДП. Драйвер создает эти объекты для представления физических параметров устройства.
Большинство периферийных устройств находятся на шинах компьютера. В современном компьютере есть несколько шин. Обычно процессор, внешняя кэш-память, и оперативная память находятся на высокоскоростной шине, архитектура которой специфична для данного типа процессора. Шина процессора соединена мостом со стандартной скоростной шиной, на которой находятся контроллеры дисплея, некоторые скоростные устройства. Архитектура этой шины может быть процессоро-независимой. Пример такой шины — PCI. Эта шина также может быть соединена мостом со вторичной локальной шиной, часто более медленной. На ней могут находиться контроллеры дисковых накопителей, сетевых адаптеров и т.п.
Периферийные устройства обычно имеют "на борту" регистры и диапазоны адресов памяти, при помощи которых реализуется интерфейс устройства с системой. Но добраться до них не так просто: процессор ведь физически использует другие механизмы для обращения к своим "родным" портам ввода-вывода и оперативной памяти. Для того, чтобы обратится к памяти и портам устройства, находящегося на локальной шине, процессор должен выполнить отображение (mapping) адресного пространства процессора и той шины, где находится наше устройство. В результате этой операции к участку памяти, физически находящийся в устройтсве, можно обращаться, как к участку оперативной памяти процессора. При таком обращении процессор переадресует запрос локальной шине. Но тут следует вспомнить об особенностях архитектуры Windows (да и практически любой современной ОС): ведь система поддерживает механизм виртуальной памяти! Пользовательские приложения теперь работают в своем адресном пространстве, а система, в том числе и драйвера, — в своем. Куда же будет отображена память устройства?
Ответ прост. Можно отобразить диапазон адресов устройства как на адресное пространство системы, так и на адресное пространство пользовательского процесса. Соответственно различаться будет и способ доступа к памяти устройства из приложения пользователя: в первом случае буфер с данными для записи или чтения будет передаваться драйверу из приложения, а в драйвере эти данные будут пересылаться устройству. Во втором случае приложение будет писать и читать данные в выделенный ему участок памяти, который находится в адресном пространстве процесса. Какой механизм выбрать — дело разработчика драйвера.
Объекты, представляющие адресное пространство периферийных устройств, представлены классами KPeripherialAdress, KIoRange, KMemoryRange, KIoregister, KMemoryRegister. KPeripherialAdress является базовым классом для большинства остальных классов управления диапазонами памяти и портов ввода-вывода. Сам класс KperipherialAdress в основном, не используется. Используются, в основном, следующие его подклассы:
• KIoRange — диапазон адресов ввода-вывода. Данный класс отображает диапазон адресов портов В/В из адресного пространства какой-либо из шин в адресное пространство процессора. При использовании класса KIoRange можно читать и записывать в порты 8, 16, и 32-битные значения.
• KIoRegister является альтернативным путем доступа к портам ввода-вывода. В виде экземпляра KIoRegister может быть пердставлен отдельный порт-ввода вывода в диапазоне адресов. Фактически, KIoRange — это несколько экземпляров класса KIoRegister, объединенных в массив. Создать экземпляр KioRegister можно, используя как стандартный конструктор, так и используя оператор [] класса KIoRange, например:
KIoRange m_range;
…
KIoRegister m_reg = m_range[6];
…
Применение KIoRegister упрощет процесс программирования и улучшает читабельность программы.
• KMemoryRange используется для отображения диапазона адресов памяти из адресного пространства шины в адресное пространство процессора (адресное пространство системы). После того, как память будет отображена, драйвер должен использовать методы доступа к памяти, позволяющие оперировать 8, 16 и 32– битными значениями.
• KMemoryRegister аналогичен KIoRegister, за исключением того, что в данном случае он представляет из себя отдельную ячейку памяти в адресном пространстве устройства.