Драйверы - фильтры
Система ввода/вывода Windows NT является расширяемой.
Один из методов расширения возможностей системы ввода/вывода - разработка
и применение драйверов-фильтров.
Драйвер-фильтр - это промежуточный драйвер, перехватывающий запросы, предназначенные
некоторым программным модулям (например, драйверу файловой системы или
драйверу диска). Перехватив запрос, драйвер-фильтр имеет возможность либо
расширения, либо замещения функциональности, обеспечиваемой первоначальным
получателем запроса. Драйвер-фильтр может, либо использовать сервисы драйвера,
которому изначально предназначался запрос, либо использовать сервисы других
программных модулей уровня ядра для обеспечения дополнительной функциональности.
Драйверы-фильтры могут встраиваться в любое место в стеке драйверов, их
может быть несколько, в том числе расположенных подряд.
Примером использования драйвера-фильтра может послужить реализация прозрачного
зашифрования/расшифрования данных, хранимых на диске или в сети. Ни одна
из поставляемых с ОС Windows NT файловых систем (FASTFAT, NTFS, CDFS,
LAN Manager Redirector) не обеспечивает поддержки прозрачного зашифрования
данных перед записью их на диск (или отправкой в сеть) и расшифрования
данных перед предоставлением их авторизированному пользователю. Но для
того, чтобы выполнять шифрование, необязательно реализовать свой собственный
драйвер файловой системы или специальный драйвер диска. Достаточно разработать
драйвер-фильтр, располагающийся либо выше драйвера файловой системы (в
этом случае драйвер-фильтр сможет обработать запрос перед тем, как драйвер
файловой системы получит возможность его увидеть), либо ниже драйвера
файловой системы (этот случай позволяет драйверу-фильтру выполнить любые
требуемые операции после того, как драйвер файловой системы завершит свою
задачу, и перед тем, как запрос будет получен драйвером диска (или сетевым
драйвером)).
Драйвер-фильтр также может выполнять автоматическое обнаружение в реальном
времени сигнатур вирусов в файлах (в том числе и файлах, полученных из
сети). Фундаментальные шаги в разработке драйверов-фильтров:
Присоединение к нужному объекту-устройству. Диспетчер
ввода/вывода включает возможность присоединения к объекту-устройству
(1), созданному некоторым драйвером (1), объекта-устройства (2), созданного
другим драйвером(2). В результате такого присоединения пакеты IRP, направленные
драйверу (1), ассоциированному с объектом-устройством (1), будут перенаправляться
драйверу (2), ассоциированному с присоединенным объектом-устройством
(2). Этот «присоединенный» драйвер (2) и является драйвером-фильтром.
Каждый объект-устройство имеет поле, называемое AttachedDevice
и содержащее указатель на объект-устройство, созданное драйвером-фильтром,
который первым присоединил свое устройство. Если это поле содержит NULL,
значит нет присоединенных устройств. В процессе присоединения нового объекта-устройства,
процедура присоединения пройдет по связанному списку присоединенных устройств
до конца и выполнит присоединение нового объекта-устройства к последнему
объекту-устройству в этом списке. В результате, любой запрос на создание/открытие,
предназначающийся некоторому объекту-устройству, будет перенаправляться
последнему (в его списке присоединенных устройств) объекту-устройству.
То есть последнему драйверу-фильтру, ассоциированному с последним объектом-устройством.
Аналогично, при вызове функции, которая по имени объекта-устройства возвращает
его указатель, будет возвращен указатель на объект-устройство, являющийся
последним в списке присоединенных устройств.
Но важно заметить, что диспетчер ввода/вывода проходит до конца списка
присоединенных устройств не во всех случаях. Например, никакого перенаправления
не будет, если будет вызвана функция IoCallDriver(). Следовательно, чтобы
обращение к драйверу назначения не миновало драйвер-фильтр, этот драйвер-фильтр
должен присоединить свой объект-устройство к объекту-устройству драйвера-назначения,
прежде, чем вышележащий драйвер вызовет процедуру определения указателя
нижележащего объекта-устройства по его имени (это делается во время запроса
на открытие), а затем будет использовать этот указатель (в частности при
вызове функции IoCallDriver()).
Драйвер-фильтр может исследовать, модифицировать, завершать,
или передавать дальше полученный пакет запроса драйверу назначения.
Для того чтобы передать драйверу назначения полученный запрос, драйвер-фильтр
должен создать собственный пакет IRP.
Драйвер-фильтр должен реализовать завершающие процедуры
для завершения обработки IRP, после того, как IRP завершит драйвер назначения.
Отсоединение от объекта-устройства назначения.
Ниже рассмотрены основные принципы, которых необходимо
придерживаться при разработке драйвера-фильтра, а также недостатки, из-за
которых применение драйверов-фильтров становится нежелательным:
- 1. Разработчик драйвера-фильтра должен четко знать, как
работает драйвер, создавший устройство, к которому будет присоединяться
его драйвер. Драйвер-фильтр должен уметь обрабатывать все получаемые
им запросы, адресованные на самом деле первоначальному драйверу, к которому
он присоединился.
Несмотря на то, что все драйверы должны отвечать на
стандартное множество запросов, выдаваемых менеджером ввода/вывода, в
реальности, в случае разработки собственного драйвера-фильтра, нужно знать
очень тонкие взаимосвязи, которые проявляются в зависимости от того, на
каком уровне иерархии находится драйвер-фильтр. Возможны случаи, когда
драйвер одной файловой системы запрашивает сервисы у драйвера другой файловой
системы, или когда промежуточный драйвер запрашивает сервисы вышележащего
драйвера файловой системы.
Например, в случае фильтрации всех запросов, предназначенных некоторому
логическому тому, управляемому драйвером файловой системы (NTFS), необходимо
понимать всевозможные пути вовлечения драйвера NTFS в обработку запроса,
так как во всех этих случаях будут вовлекаться процедуры распределения
драйвера-фильтра. Процедуры обработки запроса на чтения/запись драйвера
файловой системы могут вовлекаться несколькими путями, например: из потока
режима ядра, вызвавшего системные сервисы ZwReadFile(), ZwWriteFile();
из потока пользовательского режима, вызвавшего системные сервисы NtReadFile(),
NtWriteFile(); из диспетчера памяти из-за обращения к отсутствующей странице
спроецированного файла; из диспетчера кэша в результате асинхронного сброса
буферов диспетчера кэша на диск; и так далее.
В некоторых ситуациях может быть приемлемым то, что драйвер - фильтр отправляет
запрос ввода/вывода на асинхронное выполнение, но в других ситуациях (например,
при обслуживании обращения к отсутствующей странице) драйвер-фильтр не
должен этого делать, иначе это приведет к взаимной блокировке или зависанию.
В случае, когда драйвер-фильтр привязывается к объекту-устройству нижнего
уровня (например, к устройству, представляющему физический диск), то возникают
другие проблемы, с которыми сталкивается подобный драйвер-фильтр. Например,
пакеты IRP, посланные драйверу диска, могут быть чаще всего ассоциированными
пакетами, созданными драйвером файловой системы, и поэтому драйвер-фильтр
не должен пытаться в свою очередь создавать ассоциированные пакеты. Обычно
от драйверов дисков ожидают, что они будут выполнять операции асинхронно,
поэтому драйвер-фильтр должен удовлетворять этим ожиданиям.
- 2. Драйвер-фильтр должен корректно работать с другими
возможными драйверами-фильтрами, присоединенными до или после него.
Никогда нельзя полагаться на то, что запрос, выпущенный драйвером-фильтром,
пойдет напрямую нужному драйверу, так как он может быть перехвачен другим
драйвером-фильтром.
- 3. Драйвер-фильтр должен знать, к чему он привязывается.
Например, может получиться так, что когда драйвер-фильтр попытается
привязаться к объекту-устройству, представляющему логический том файловой
системы, он в действительности привяжется к объекту-устройству, представляющему
физический диск, если том еще не был вмонтирован.
- 4. Драйвер-фильтр должен избегать поддержки ненужных
ссылок. Если по небрежности драйвер-фильтр поддерживает лишнюю ссылку
на объект-устройство назначения, то, возможно, что это будет препятствовать
всем дальнейшим запросам на открытие и выполнение операций над этим
устройством.
- 5. Драйвер-фильтр должен следить за контекстом потока,
в котором выполняется его процедура распределения. В первую очередь
это касается запросов ввода/ вывода к драйверам файловых систем, так
как эти запросы должны передаваться в контексте потока, инициировавшего
запрос, поэтому если драйвер-фильтр вызвал переключение контекста, например,
послав запрос на исполнение рабочему потоку, то дальнейшая обработка
этого запроса драйвером файловой системы приведет к непредсказуемым
последствиям.
На всех уровнях, которые будут обсуждаться в дальнейшем,
возможны реализация и применение драйверов-фильтров. |