Использование NuMega DriverStudio для написания WDM-драйверов - [7]
Если в данном случае у компьютера поменяется клавиатура, то необходимо установить только новый драйвер 2. Если клавиатура переключится на другой порт, то устройство 2 будет общаться не с устройством 3, а с каким-то другим устройством. В таком случае система становится более гибкой, легкой в проектировании, более надежной и простой в использовании. И пользователю, и приложениям становится абсолютно все равно, какой тип клавиатуры установлен на компьютере.
В нашем примере получается, что и устройство 1, и устройство 2 управляет оборудованием – клавиатурой. Но они делают это не напрямую, а посылая IRP устройству 3. Для того, чтобы наш объект устройства мог передавать IRP–пакеты другим объектам устройств, введен класс устройств нижнего уровня (KLowerDevice, KPnpLowerDevice). Естественно, для этого устройство должно знать, как управлять устройством нижнего уровня при помощи IRP.
Впрочем, подбная ситуация имеет место практически во всех современных ОС. Только в других системах это выражено менее ярко и не декларируется, как "официальная идеология".
Затрагивая тему управления аппаратурой, нельзя не упомянуть еще об одном способе управления устройствами. Иногда нет возможности использовать классы DriverWorks или функции DDK. Например, необходимо обратится непосредственно к портам ввода-вывода компьютера, в частности, к портам управления принтером. Напрямую сделать это из приложения пользователя, работающего под Win2000, невозможно. Все пользовательские программы работают в непривилегированном кольце защиты 3 и не могут выполнять ассемблерные команды типа inp / outp. Но драйвер работает в кольце защиты 0 и, фактически, может делать все что угодно. В этом случае следует переопределить методы класса устройства, например ReadFile(), WriteFile(), DeviceControl() – добавить туда ассемблерные вставки или код на С, выполняющий то, что нам необходимо сделать (чаще всего это обращение к портам ввода-вывода). Впрочем, любое обращение к портам ввода-вывода компьютера напрямую может оказаться опасным. Если программист допустит ошибку или неточность в манипуляциях с параллельным портом, то это, скорее всего, пройдет бесследно для системы. Но если он ошибется при обращении к портам управления таймером, винчестером или другими жизненно важными устройствами компьютера, то в лучшем случае система зависнет.
Сколько операций может параллельно выполнять наше физическое устройство? Естественно, это определяется самой природой этого устройства. Многие виды оборудования могут одновременно делать что-то одно. Например, параллельный порт не может передать два байта за один раз при всем нашем желании, ведь физически это один канал передачи. Но ведь IRP–пакеты могут приходить в любое время! Поэтому большинство объектов устройств должны содержать какой-либо механизм для буферизации и упорядочивания (serialization) запросов, т.к. зачастую только один запрос может быть обработан в единицу времени. Самым простым и в то же время эффективным методом такой буферизации является очередь.
Объекты, внедренные в объект устройства, представлены в классе KDeviceQueue. Его методы не только реализуют манипуляцию с очередью, но и решают более интеллектуальные задачи. Например, есть метод, смысл которого может быть описан таким образом: "Если устройство сейчас обрабатывает запрос и занято, то помести новый запрос в очередь, иначе немедленно начни его обработку". Подобные методы сильно облегчают задачу буферизации запросов для объекта устройства. Но возможна и другая ситуация: устройство может одновременно обрабатывать запросы разного вида. К примеру, наше устройство – это дуплексный канал связи. Оно одновременно может и принимать, и передавать информацию. Если мы будем использовать для буферизации всех одну очередь, то такой подход является неэффективным. Поэтому система позволяет объектам устройств создавать дополнительные объекты очередей. Они реализованы в классе KDriverManagedQueue.
Рис. 5 — объект очереди, внедренный в объект драйвера.
Эти классы имеют методы, сходные с методами класса KDeviceQueue. Впрочем, ситуации, когда следует применять более одной очереди для буферизации запросов, встречаются не так уж и часто.
В контексте данного руководства будем считать, что прерывание (Interrupt) – асинхронный аппаратный сигнал, который обычно возникает, когда периферийному устройству необходимы ресурсы процессора. "Асинхронный" означает то, что прерывание возникает в произвольные моменты времени (если вообще возникает). Прерывание заставляет процессор прервать выполнение программы, сохранить свое состояние, обработать поступивший запрос (вызывается процедура обработки прерывания, Interrupt Service Routine, ISR) и возобновить выполнение прерванной программы. При этом останавливаются все остальные процессы и потоки ОС вне зависимости от их приоритета.
Для того, чтобы удовлетворять разнообразным требованиям, возникающим при работе разнообразных устройств и программ на различных типах компьютеров, ОС предлагает концепцию уровня запроса на прерывание (Interrupt Request Level), IRQL. Всего существует 32 IRQL для данного процессора, пронумерованных от 0 до 31. При этом 0 — самый низкий приоритет, 31 — самый высокий.