39769

Уровни аппаратных привилегий в Windows NT 5

Реферат

Информатика, кибернетика и программирование

ДВВ представляет запросы от процессов пользовательского режима драйверным процедурам в форме пакета запроса на ввод вывод то есть пакета IRP. Пакет IRP является своего рода рабочим рецептом созданным ДВВ который передается в драйверные процедуры. Большая часть данной книги как раз посвящена правильной организации драйверного кода обрабатывающего IRP пакеты. Запросы сделанные к подсистеме ввода вывода формулируются передаются и отслеживаются с помощью четкого формата рабочего рецепта известного как IRP пакет I o Request Pcket пакет...

Русский

2013-10-08

294.5 KB

4 чел.

Уровни аппаратных привилегий в Windows NT 5

Для достижения устойчивости в работе системы, разработчики NT выбрали для построения ядра так называемую 'архитектуру клиент-сервер'. В данном случае, пользовательское приложение и является клиентом служб операционной системы.

Пользовательское приложение функционирует в специальном режиме (относительно аппаратного обеспечения), называемом 'user mode' — пользовательский режим. В пределах этого режима, код приложения ограничен выполнением "безвредных" инструкций. Например, через реализацию  маппинга (mapping, отображение) виртуальной памяти (страничное представление виртуальной памяти) пользовательский код лишается возможности доступа к виртуальной памяти, предоставленной другим приложениям (за исключением случаев обоюдного согласия, что реализуется специально предназначенными на тот случай методами). Инструкции аппаратного ввода/вывода также не могут быть выполнены кодом пользовательского режима. Целый класс инструкций центрального процессора (называемых привилегированными) запрещен в Windows NT для выполнения кодом пользовательского режима, как, например, команды процессора IN, OUT . Если вдруг приложению потребуется выполнить что-нибудь из числа таких запрещенных для нее действий, оно должно запросить соответствующую службу операционной системы.

Код самой операционной системы выполняется в так называемом 'kernel mode' — режиме ядра (режиме уровня ядра). Код режима ядра вправе выполнить любую процессорную инструкцию, не исключая инструкций ввода/вывода. Память, принадлежащая любому приложению, может быть доступна коду режима ядра, конечно, если страничная память приложения в данный момент не сброшена на жесткий диск.

Современные процессоры реализуют несколько форм привилегированного режима в отличие от непривилегированного. Код режима ядра выполняется в привилегированном контексте, в то время как пользовательский код выполняется в непривилегированной среде. Так как разные процессоры (и платформы на их основе) реализуют привилегированные режимы по-разному, то, для обеспечения переносимости, разработчики операционной системы ввели особые абстрактные элементы программирования, которые позволяют разграничивать пользовательский режим и режим ядра. Код операционной системы использует их для переключения привилегированного/непривилегированного контекста, следовательно, при перенесении операционной системы только лишь код этих дополнительных элементов необходимо "портировать" (переписывать конкретно под специфику новой аппаратной платформы). На платформе Intel пользовательский режим реализуется из набора инструкций Ring 3 (Кольца 3), в то время как режим ядра реализован с использованием Ring 0 (Кольца 0).

Драйверы уровня ядра (режима ядра) работают в привилегированном контексте. Соответственно, плохо написанный драйверный код может оказаться вредоносным для операционной системы.

Переносимость

В качестве способа решения задачи переносимости конструкторы NT выбрали многослойную архитектуру, как показано на рисунке 1.

Рисунок 1 – Слои операционной системы

Слой аппаратных абстракций (Hardware Abstraction Layer, HAL) изолирует процессорные и платформенные особенности от кода операционной системы. Его услугами Microsoft предлагает пользоваться и разработчику драйверного кода. Вполне возможно так написать драйвер, что для перенесения его на другую платформу потребуется разве что перекомпилировать его. Как можно это сделать, если изначально драйвер есть такая программная единица, которая жестко привязана и к своему устройству, и к конкретному процессору, и к конкретной платформе?! Просто драйвер должен обратиться к использованию средств уровня HAL для взаимодействия с аппаратными регистрами и аппаратной поддержкой шин. В отдельных случаях разработчик драйвера может опереться на код, предоставляемый Диспетчером ввода/вывода, для работы с совместно используемыми аппаратными ресурсами. Например, при DMA операциях (прямого доступа к памяти) используется такая абстракция программирования, как объект адаптера.

Расширяемость

На рисунке 1 обозначена и еще одна важная особенность представленной архитектуры — ядро отделено от слоя, который носит название "исполнительные компоненты" (Executive).

В данном случае, ядро несет ответственность за планировку активности программных потоков (threads). Поток является всего лишь "независимой тропинкой" в выполнении программного кода. Чтобы сохранить независимость от деятельности других потоков, для каждого из них необходимо сохранять уникальный потоковый контекст (thread context). Потоковый контекст состоит из состояния регистров процессора (включая также изолированный стек и счетчик инструкций, Program Counter), сохраненного ID (идентификатора потока, так называемого Thread ID или TID), значения приоритета, распределения памяти, связанной с потоком (Thread Local Storage), и другой информации, имеющей отношение к данному потоку.

Обязанностью планировщика потоков является определение, какой поток должен выполняться в данный момент. В среде с единственным процессором, в каждый момент времени только один поток получает в свое распоряжение процессор. В многопроцессорной конфигурации разные потоки могут выполняться на разных процессорах, реализуя настоящую параллельность выполнения кода. Планировщик в большинстве случаев выделяет потоку процессор на фиксированный временной интервал, известный под названием thread time quantum (потоковый временной квант). Предоставление процессора происходит, главным образом, на основе величины приоритета потока.

Так как основной задачей ядра является управление потоками, работа по управлению памятью, вопросами доступа (security) и действиями по вводу/выводу возлагается на другие компоненты операционной системы. Эти компоненты известны под собирательным названием 'Executive', Исполнительные Компоненты. Они сконструированы как модульное программное обеспечение (хотя, Диспетчер ввода/вывода сам является существенным исключением из этого правила).

Производительность

Все слои  выполняются в одном аппаратном режиме — режиме ядра. Следовательно, межслойные вызовы не используют ничего сложнее, чем инструкция процессора CALL. Средства же, предоставляемые уровнем HAL, в основном, представляет собой макроопределения, являющиеся inline-включаемым кодом.

Разработчики предприняли много усилий, направленных на то, чтобы заставить работать параллельно максимально возможное число программных потоков исполнительных компонентов. Вспомогательные процедуры редко блокируются или находятся в состоянии ожидания, что минимизирует время простоя процессора.

Вопросы повышение производительности в значительной степени касаются и разработчиков драйверов. В момент, когда пользовательское приложение или системный поток посылает запрос к обслуживаемому драйвером устройству (точнее сказать, объекту устройства), жизненно важно, чтобы драйверный код не блокировал выполнения запроса. Если же запрос не может быть обработан немедленно (например, устройство занято или медленно работает), запрос должен быть поставлен в очередь для последующей обработки. Диспетчер ввода/вывода предоставляет необходимые для этого процедуры.

Интерфейс системных служб 

System Service Interface. Данный компонент обеспечивает точки перехода из пользовательского режима в код режима ядра, что позволяет пользовательским приложениям (потокам этих приложений) безопасно осуществлять вызовы системных сервисов (процедур режима ядра). В зависимости от платформы, переход из пользовательского режима к коду режима ядра может быть и простой процессорной инструкцией, и достаточно сложным переключателем контекста Save или Restore.

Менеджер (диспетчер) объектов

Object Manager. Практически все услуги, предоставляемые операционной системой, оперируют с такой распространенной абстракцией, как объекты, хотя это и не стопроцентные объекты, по некоторым признакам, известным из объектно-ориентированного программирования. Например, программа, выполняемая в пользовательском режиме, которой необходимо синхронизировать несколько собственных потоков, может запросить у операционной системы объект синхронизации Event (событие), а на самом деле — просто особым образом обслуживаемую структуру данных. Система предоставляет Event в форме системного объекта, на который из программы пользовательского режима можно ссылаться только по дескриптору (handle). Файлы, процессы, потоки, события (Events), секции памяти (Memory Sections) и даже подразделы Системного Реестра (Registry Keys) поддерживаются операционной системой как системные объекты. Все объекты создаются и уничтожаются централизованно — Менеджером Объектов. Это позволяет получить унификацию (единообразие) доступа к объектам, контроля над их временем жизни и обеспечивать безопасность и права доступа к ним.

Менеджер конфигурирования

Configuration Manager. Менеджер конфигурирования Windows NT 5 конструирует модель всей доступной аппаратуры и всего инсталлированного программного обеспечения, которое имеется на компьютере. Для хранения образа этой модели используется база данных, хорошо известная под названием Системный Реестр. Драйверы устройств используют информацию Реестра для уточнения множества характеристик окружения, в котором им придется работать. С введением спецификации Plug and Play роль Системного Реестра для драйверов, реализованных по WDM модели, существенно снизилась.

Менеджер процессов

Process Manager. Предоставляет функции, начинающиеся с префикса Ps (например, PsGetVersion — получить версию операционной системы). Функции PsXxx могут выполняться на разных уровнях IQRL, что следует уточнять в документации DDK.

Процесс является средой, в которой существует (выполняется) поток. Каждый процесс определяет собственное адресное пространство и содержит элементы идентификации для определения прав доступа (security identity). Важно отметить, что в Windows процесс не выполняется; выполняется поток, который является единицей выполнения, в то время, как процесс является "фигурой" собственности. Процесс владеет одним или несколькими потоками.

Менеджер процессов Windows 2000/XP/2003 является исполнительным компонентом, который управляет созданием процесса и предоставляет ему окружение, в котором работают программные потоки. Менеджер Процессов в своей работе опирается, главным образом, на другие исполнительные компоненты (например, Менеджера Объектов и Менеджера виртуальной памяти), так что можно сказать, что он представляет верхний уровень абстрагирования над другими системными сервисами более низкого уровня.

Драйверы редко контактируют непосредственно с Менеджером процессов. Как правило, они опираются на другие системные сервисы для доступа к среде процесса (process environment). Например, драйвер хочет быть уверен, что буферная область памяти в области адресов, находящихся во владении процесса, удерживается в течение всего времени передачи данных. Системные процедуры с префиксом Mm (относящиеся к Менеджеру памяти) предоставляют драйверу средства для такого удержания.

Менеджер виртуальной памяти 

Virtual Memory Manager, VMM. В операционной системе Windows 2000/XP/2003 (впрочем, как и в Windows 9x) адресное пространство процесса является непрерывным и непрерывно адресуемым (flat). Размер адресуемого таким образом пространства в 32 разрядной версии Windows составляет 4 ГБ (2 в степени 32), что соответствует использованию 32 разрядного указателя. При этом только нижние 2 ГБ доступны для использования кодом пользовательского режима. Программный код и данные пользовательских программ должны размещаться в этой нижней половине адресного пространства. В случае, если пользовательские программы используют совместно динамически подключаемые библиотеки (DLL), то этот библиотечный код также должен размещаться в первых двух гигабайтах адресного пространства (эта схема претерпевает некоторые изменения — в сторону увеличения пользовательского адресного пространства до 3 ГБ — лишь в Enterprise Server при его соответствующей настройке).

Верхние 2 ГБ адресного пространства каждого процесса содержат код и данные, доступ к которым возможен только из программного кода, выполняющегося на уровне ядра. Верхние 2 ГБ используются кодом уровня ядра совместно от процесса к процессу. Если вы выполните распечатку адресов каких-нибудь объектов, переменных или процедур в окне DebugView или DebugPrint, то сразу же увидите, что код драйвера отображается на адресное пространство выше 2 ГБ (адреса превышают 0x80000000).

Менеджер памяти (VMM) осуществляет управление памятью от имени всей операционной системы. Для обычной программы пользовательского режима это означает выделение памяти и управление адресным пространством и физической памятью ниже границы 2 ГБ. В том, достаточно обычном, случае, когда процессу пользовательского режима не хватает физической памяти, VMM создает иллюзию наличия памяти путем виртуализации запроса. Необходимая память выделяется страницами (то есть блоками соответствующего размера, для Intel платформы — 4 КБ) на жестком диске (что называется — paging). По мере необходимости доступа к ней со стороны потоков процесса выделенные страницы перемещаются в физическую память. Таким образом, физическая память становится совместно используемым ресурсом всех процессов.

Менеджер Виртуальной Памяти выступает также и в роли ответственного за распределение памяти в том смысле, что управляет "кучей" (heap area) для программного кода уровня ядра. Для своих нужд драйверы через соответствующие вызовы (например, вызов ExAllocatePool, в конечном счете, обрабатываемый VMM) могут запросить выделение областей памяти в страничной (то есть организованной странично и допускающей сброс на жесткий диск) или нестраничной памяти.

Средства локальных процедурных вызовов

Local Procedure Call (LPC), локальный процедурный вызов, является механизмом вызовов между процессами на одном компьютере. Так как меж-"процессный" вызов (interprocess call) может проходить между разными адресными пространствами, то существуют и соответствующий Исполнительный компонент, который делает это действие возможным и эффективным. Как правило, драйверному коду не требуется осуществление вызова другого процесса и, соответственно, LPC средства.

Диспетчер (менеджер) ввода/вывода

Диспетчер ввода/вывода (I/O Manager), ДВВ, является исполнительным компонентом, который реализован в виде множества процедур уровня ядра. ДВВ представляет для процессов пользовательского режима единообразный подход к операциям ввода/вывода. Предлагаемая модель не делает различий, получает ли пользовательский процесс доступ к клавиатуре, коммуникационному порту или файлу на диске — способ обращения оформлен одинаково.

ДВВ представляет запросы от процессов пользовательского режима драйверным процедурам в форме пакета запроса на ввод/вывод, то есть пакета IRP. Пакет IRP является своего рода рабочим рецептом, созданным ДВВ, который передается в драйверные процедуры. Работа же драйвера состоит в том, чтобы должным образом этот запрос обработать. Большая часть данной книги как раз посвящена правильной организации драйверного кода, обрабатывающего IRP пакеты.

Фактически, Диспетчер ввода/вывода является интерфейсом между кодом пользовательского режима и драйверами устройств. Таким образом, он является первым и важнейшим системным компонентом, с которым взаимодействует драйвер.

При посредничестве Диспетчера ввода/вывода с драйвером могут общаются и компоненты уровня ядра, например, могут обращаться другие драйверы (вспомним, хотя бы, случай с программой Debug Print Monitor, глава 2).

Расширения базовой операционной системы

Исполнительные компоненты Windows 2000/XP/2003 определяют и представляют основные сервисы операционной системы. Однако эти сервисы никогда не предоставляются программам пользовательского режима непосредственно. Вместо этого разработчики из Microsoft определили несколько интерфейсов прикладного программирования (Application Programming Interfaces), при помощи которых код пользовательского режима может обращаться к абстракциям системных служб.

Эти интерфейсы формируют различные среды (environmental subsystems), в которых и обитают прикладные программы. В настоящее время в Windows NT 5 представлены:

  •  Win32 подсистема, являющаяся собственным (native-mode) API для 32-разрядных версий Windows. Bce остальные среды (environmental subsystems) используют эту подсистему для выполнения своей работы. Все новые приложения 32-разрядных Windows 2000/XP/2003 (а также и все перенесенные) полагаются на Win32 как на среду своего функционирования. Из-за важности (и достаточно интересной реализации) эта подсистема будет рассмотрена далее более детально. Следует, однако, отметить, что в 64-разрядной версии Windows (версии XP/Server 2003) она сама становится клиентом WOW64 (см. ниже).
  •  Virtual DOS Machine (VDM, виртуальная DOS машина) подсистема обеспечивает 16-разрядную MS DOS операционную среду для старых DOS приложений. Несмотря на уверения в совместимости, множество существующих DOS программ в этой среде не работают надлежащим образом. Происходит это по той причине, что Microsoft, проповедуя консервативный подход, предоставляет эмуляцию аппаратуры вместо возможности непосредственного обращения к ней. В результате, прямой доступ к аппаратуре приводит к ограничению со стороны операционной системы и, зачастую, отказу данного DOS приложения работать.
  •  Подсистема 'Windows on Windows' (WOW) поддерживает операционную среду для возможности работы старых 16-битных приложений Windows (например, Windows 3.x). В 64-разрядных клонах Windows XP/Server 2003 подсистема WOW 64 служит для запуска созданных ранее 32-разрядных приложений, перенесенных на новые аппаратные платформы.
  •  Подсистема POSIX обеспечивает выполнение Unix-приложений, которые удовлетворяют стандарту POSIX 1003.1. К сожалению, большинство перенесенных Unix-подобных систем приложений не работает должным образом в этой подсистеме. В данном случае большинство Unix-приложений переносятся под Windows путем переписывания под Win32 подсистему или они изначально создаются с использованием специальных программных пакетов, типа MainWin, Motif и OpenMotif.
  •  Подсистема OS/2 создает среду выполнения для 16-разрядных программ операционной системы OS/2 — по крайней мере, тех из них, которые не используют в своей работе сервисов такого компонента OS/2, как Presentation Manager (PM). На эту подсистему можно рассчитывать только в версии Windows для платформы Intel (x86).

Каждое приложение однозначно связано с одной средой выполнения. Приложения не могут осуществлять API вызовы к другим исполнительным средам. Кроме того, подсистема Win32 является основной в 32-разрядных версиях Windows NT 5.x. Другие подсистемы эмулируют соответствующие свойства реализуемых сред через средства и методы Win32. Соответственно, параметры выполнения программ в этих средах деградируют и существенно уступают аналогичным программам для Win32.

Подсистема Win32

В качестве основного для Windows 2000/XP/Server 2003 интерфейса API (32-разрядных версий), подсистема Win32 ответственна за:

  •  графический пользовательский интерфейс (Graphical User Interface, GUI), который наблюдает пользователь системы. Win32 отвечает за реализацию видимых окон, диалоговых элементов и элементов управления (кнопок, полос прокрутки и т.п.), то есть общий стиль оформления системы.
  •  Консольный ввод/вывод, включая клавиатуру, мышь и дисплей для всей операционной системы и других подсистем.
  •  Функционирование Win32 API, при помощи которого приложения и другие подсистемы взаимодействуют с исполнительными компонентами режима ядра.

Так как подсистема Win32 имеет особый статус среди остальных подсистем, а вследствие этого к ней предъявляются повышенные требования, то и реализация этой подсистемы существенно отличается. В частности, подсистема Win32 разделена на несколько компонентов, часть из которых работает пользовательском режиме, а другая в режиме ядра. Функции Win32 можно разделить на три категории:

  •  Функции, которые предоставляются пользователю для управления окнами, меню, диалогами и элементами контроля (кнопками, полосами прокрутки, переключателями, закладками и т.п.).
  •  Функции GDI, реализующие прорисовку изображений на физических устройствах, экране, принтере, графопостроителе.
  •  Функции, которые управляют неграфическими ресурсами, такими как процессы, программные потоки, файлы и объекты синхронизации. KERNEL-функции тесно связаны с системными службами исполнительных компонентов.

Со времен NT 4.0 большая часть функций первых двух категорий из приведенной выше классификации была реализована в режиме ядра. Пользовательские процессы, которые запрашивают услуги GUI, обращаются непосредственно к коду режима ядра при использовании System Service Interface (Интерфейса Системных Служб). Код, представляющий эти функции и работающие в режиме ядра, локализован в модуле WIN32K.SYS.

Функции третьей категории при обработке запросов от пользовательских процессов опираются на стандартный серверный процесс CSRSS.exe (Client-Server Runtime Subsystem), который и обращается собственно к коду исполнительных компонентов для завершения обработки этих обращений.

Другие существенные компоненты операционной системы

В дополнение к подсистемам, реализующим среду выполнения кодов DOS, Windows, POSIX и OS/2, имеется еще несколько ключевых системных компонентов, которые реализованы как процессы пользовательского режима. Среди них:

  •  Security Subsystem, подсистема безопасности, которая управляет локальной и удаленной безопасностью (защитой от неправомерного доступа) с использованием ряда процессов и динамических библиотек. Часть работы Active Directory протекает как раз среди этой логической подсистемы.
  •  Service Control Manager (SCM, функции которого использовались для запуска драйвера в Главе 3), Менеджер Управления Сервисами, управляет процессами-демонами (сервисами), и драйверами устройств.
  •  Процессы поддержки RPC вызовов (Remote Procedure Call, вызов удаленной процедуры), которые оказывают поддержку приложениям, распространяемым по сети. Прибегая к использованию вызовов удаленных процедур, приложения могут выполнять свою работу с использованием множества сетевых компьютеров.

Компоненты обслуживания операций ввода/вывода, работающие в режиме ядра

Цели разработки подсистемы ввода/вывода

Подсистема ввода/вывода вносит корректировки в список задач новых систем Windows, но особенно следует отметить:

  •  Конфигурируемость и в терминах аппаратуры, и в терминах программного обеспечения. Для драйверов Windows это означает полную поддержку PnP спецификации для шин и устройств.
  •  Приоритетность и прерываемость. Код, написанный для обслуживания ввода/вывода, никогда не должен блокироваться и должен содержать безопасные программные потоки.
  •  Безопасность при использовании на многопроцессорных платформах. Один и тот же драйверный код должен безошибочно работать и на однопроцессорных и на многопроцессорных компьютерах.
  •  Объектная ориентированность. Услуги, предоставляемые кодом, должны "формулироваться" в терминах вполне определенных структур данных, которые служат выполнению разрешенных операций.
  •  Пакетное управление. Запросы, сделанные к подсистеме ввода/вывода формулируются, передаются и отслеживаются с помощью четкого формата "рабочего рецепта", известного как IRP пакет (I/o Request Packet, пакет запроса на ввод/вывод).
  •  Поддержка асинхронного ввода/вывода. Подсистема ввода/ввода должна позволять коду программы, по обращению которой создан запрос IRP, выполняться параллельно тому программному коду, который осуществляет обработку запроса. Также должен существовать механизм оповещения инициатора вызова о полном завершении обработки запроса.

Помимо уже указанных задач, существует и насущная необходимость обеспечения повторного использования кода (так называемой реентерабельности, повторной входимости в код). Это означает необходимость не только сложного структурирования собственно кода, отвечающего за операции ввода/вывода в одном устройстве, но и другого взгляда на взаимодействие слоев всей подсистемы ввода/вывода. Например, код, отвечающий за управление шиной, должен быть реализован автономно от кода, отвечающего за конкретное устройство, подключенное к шине. Выполнение этого требования позволяет повторно использовать код шинного драйвера программными блоками (например, драйверами), отвечающими за другие устройства на этой шине, которые, кстати сказать, могут подключаться, и удаляться, и, весьма возможно, пока еще и вовсе не изготовлены.

Типы драйверов Windows NT5

В былые времена разработчик драйвера мог изучить новую аппаратуру, интерфейс общения операционной системы и драйверов, окинуть все это критическим взором, сосредоточиться и создать драйвер. Хорошо это или плохо, но пора монолитных драйверов практически прошла. Сегодня разработчик драйверов должен изучить архитектуру аппаратных шин, архитектуру многослойной подсистемы ввода/вывода для того лишь, чтобы определить мотивы выбора конструкции драйвера. Определение, какого типа драйвер следует написать, уже само по себе важное решение. Выбор, следует ли заново реализовать или можно повторно использовать какой-нибудь уже имеющийся в подсистеме ввода/вывода слой, является еще одним важным решением.

Если взять самый крупный план, то можно разделить драйверы, используемые Windows NT 5, на две группы: драйверы пользовательского режима и драйверы режима ядра. Первые, как подразумевает их название, являются системным программным кодом, функционирующим в пользовательском режиме. В качестве примера можно назвать драйверы-симуляторы (виртуализаторы) для воображаемой аппаратуры или новых исполнительных подсистем (WOW, POSIX, OS/2). Так как Windows 2000 не допускает непосредственной работы с аппаратурой для кода пользовательского режима, то такие драйверы должны полагаться в этой части на драйверы, работающие в режиме ядра. Предположим, в примере главы 3 (драйвер Example.sys) была бы создана DLL, которая работала бы в пользовательском режиме и общалась бы с драйвером на манер приложения ExampleTest. В свою очередь, если бы пользовательские приложения обращались бы к ней (а не к помощи функций CreateFile, DeviceIoControl и т.п.) в те моменты, когда желали бы обратиться к драйверу, то тогда такая DLL и была бы типичным драйвером пользовательского режима.

Драйверы режима ядра (kernel-mode drivers) целиком состоят из кода системного уровня, выполняющегося в режиме ядра. Поскольку коду режима ядра разрешено работать непосредственно с аппаратурой (мы это видели в коде примера главы 3 при обработке IOCTL запроса IOCTL_TOUCH_PORT_378H), такие драйверы имеют прямой доступ к управлению устройствами, содержащимися в компьютере или подключенными к компьютеру. Разумеется, ничто не может помешать такому драйверу представлять вымышленную аппаратуру — воля разработчика, где этим заниматься — в пользовательском режиме или режиме ядра.

Ограничившись категорией драйверов режима ядра, чему и посвящена данная книга, перемещаемся как раз в код режима ядра. На этом уровне можно выполнить деление драйверов еще на две категории: наследованные (legacy, доставшиеся как наследство от Windows NT 3.5, 4) и WDM драйверы. Пример драйвера Example.sys, рассмотренный в предыдущей главе, как раз и является примером legacy драйвера. Он использует функции заголовочного файла ntddk.h, скомпилирован без директивы DRIVERTYPE=WDM, не зарегистрировал ни одной процедуры типового WDM драйвера, не выполнил подключение объекта своего устройства к родительскому объекту и не реализует свои запросы путем обращения к стеку устройств. Драйверы типа legacy (если не говорить о задачах проникновения в режим ядра с задачами чистого программирования или исследования) предназначены для работы с теми устройствами, которые не поддерживают PnP спецификацию, поскольку только разработчик драйвера знает, как различить его присутствие в системе, не говоря уже о приемах работы с ним.

К счастью, практически все знания, касающиеся наследуемых драйверов NT, полностью применимы к модели WDM, по которой можно построить драйверы, работающие в Windows 2000/XP/Server 2003 (и Windows 98, Ме). Правда, как мы видели на примере Example.sys, и некоторые экземпляры драйверов типа legacy ("в-стиле-NT") могут работать во всех перечисленных ОС.

Способность драйверов WDM работать по PnP спецификации включает в себя: участие в управлении энергоснабжением системы, автоматическое конфигурирование устройства и возможность его "горячего" подключения. Корректно написанный WDM драйвер может быть использован и под Windows NT 5.x и под Windows 98/Ме, хотя Microsoft не гарантирует бинарной совместимости (простого переноса .sys файла в другую ОС, как это получилось с Example.sys). B большинстве случаев все еще необходима перекомпиляция под Windows 98 DDK.

Наследуемые и WDM драйверы можно разделить также и на другие три категории: высокоуровневые, средне и низкоуровневые драйверы. Как подразумевает эта классификация, высокоуровневые драйверы зависят от драйверов среднего и низкого уровня в выполнении своих задач. Драйверы среднего уровня (intermediate, промежуточные), соответственно, в своей работе зависят от функционирования драйверов низкого уровня (low-level drivers).

К высокоуровневым драйверам, например, относятся драйверы файловых систем (file system drivers, FSDs). Такие драйверы предоставляют инициаторам запросов нефизическую абстракцию получателя, и уже эти запросы транслируется в специфические запросы к лежащим ниже драйверам. Необходимость в создании высокоуровневых драйверов возникает тогда, когда основные услуги аппаратуры уже реализованы драйверами нижнего уровня, и требуется только создать новую фигуру абстрагирования, которая необходима для предъявления инициатору запросов (клиенту драйвера).

Фирма Microsoft поставляет комплект программного обеспечения Installable File System (IFS) Kit, который распространяется отдельно от MSDN или других продуктов. Пакет IFS Kit требует наличия пакета DDK (и некоторых других средств) для того, чтобы заняться разработкой файловой системы всерьез. При этом существуют многочисленные ограничения на то, какие типы файловых систем могут быть получены при помощи этого пакета (IFS Kit). Дополнительную информацию по пакету IFS Kit можно получить на интернет-сайте Microsoft.

Драйверы среднего уровня могут быть проиллюстрированы такими примерами, как драйверы зеркальных дисков, классовые драйверы (class drivers), мини-драйверы (mini drivers) и фильтр-драйверы (filter drivers). Эти драйверы позиционируют себя между высокоуровневыми абстракциям высокоуровневых драйверов и средствами физической поддержки на нижних уровнях. Например, драйвер зеркальных дисков получает запрос от высокоуровневого драйвера файловой системы (FSD), транслирует этот запрос в два запроса к двум разным дисковым драйверам более низкого уровня. При этом нет никакой необходимости в том, чтобы кто-нибудь из драйверов верхнего или нижнего уровней был в курсе, как на самом деле произошла "зеркализация" и была ли она вообще.

Драйверы класса являются возможностью повторного использования кода в пределах драйверной модели. Так как много драйверов определенного типа могут иметь много общего, то программный код, описывающий общие места, может быть помещен в общий для данного типа классовый драйвер, отдельно от специфичного для обслуживания конкретных устройств кода. Например, драйверы HID (human interface device, устройства ввода по шине USB) используют такие сходства. Драйверы специфических HID устройств могли бы быть, в такой ситуации, реализованы как мини-драйверы, взаимодействующие с классовыми драйверами. Мини-драйверы отличаются тем, что они, как правило, общаются только с другими драйверами верхнего уровня (представляющими для них оболочку), не выходя на "прямой контакт" с Диспетчером ввода/вывода. Мини-драйвер и его клиент наверху имеют заранее обусловленный протокол общения, и обычно мини-драйвер экспортирует набор функций своего интерфейса по запросу драйвера-оболочки, после чего возможно общение между драйверами минуя Диспетчер ввода/вывода, что существенно ускоряет работу.

Фильтр-драйверы (filter drivers) являются драйверами среднего уровня, которые позиционирует себя во время загрузки над или под интересующим их драйвером и перехватывают запросы, идущие к нему или от него. Фильтр-драйверы, как правило, предназначены для модификации запроса к существующему драйверу или для реализации некоей дополнительной функции, изначально не заложенной в существующем драйвере (в простейшем случае это может быть подсчет пропущенных IRP пакетов). В операционной системе фильтр-драйверы с большой долей вероятности можно опознать по отсутствию имен у объектов устройств, созданных такими драйверами (при использовании программы DeviceTree).

Наконец, документация DDK упорно внедряет такую категорию (по отношению к WDM драйверам) как функциональные драйверы (functional drivers), причем эти драйверы могут быть либо классовыми, либо мини-драйверами. Что имеет в виду документация DDK, когда вводит термин "функциональный"? Дело в том, что такие драйверы всегда работают как интерфейс между абстрактным запросом ввода/вывода и кодом низкоуровневого физического драйвера, "физического" — в том смысле, что он связан непосредственно с устройством и в его функционировании хорошо просматриваются особенности этого устройства. Характерна в данном случае следующая деталь. Когда типовой WDM драйвер нормального PnP устройства собирается подключить себя к стеку устройств (это должно происходить в процедуре AddDevice), он выполняет подключение функционального объекта устройства (FDO), созданного им самим, к физическому объекту устройства (PDO), предоставленному ему родительским драйвером. Как правило, родительским является драйвер шины, который первоначально обнаружил подключение данного устройства, инициировав затем обращение к рассматриваемому WDM драйверу устройства. Вот здесь и проявляется функциональность последнего: он получает общие функциональные запросы, а превращает их в низкоуровневые (например, в URB запросы для устройств USB), понятные шинному драйверу и устройству, олицетворяя при этом для своего клиента функции устройства, а не его конструкцию и внутреннюю логику.

Специальные драйверные архитектуры

Microsoft предлагает специфические драйверные архитектуры для нескольких типов или классов устройств, а именно:

  •  Видеодрайверы.
  •  Драйверы принтеров.
  •  Драйверы устройств мультимедиа.
  •  Сетевые драйверы.

1Драйвер "в-стиле-NT"

Все драйверы с точки зрения степени привилегированности их кода делятся на драйверы, функционирующие в пользовательском режиме и функционирующие в режиме ядра.

Первые из них, драйверы пользовательского режима, представляют собой обычный программный код, как правило, оформленный в хорошо всем знакомые динамически загружаемые библиотеки (DLL). Эти драйверы стеснены в обращении к системным ресурсам и опираются в своей работе на модули режима ядра, с которыми они тесно сотрудничают. Так устроен пакет проектирование драйверов WinDriver от фирмы Jungo Ltd, в котором клиентские приложения через функции пользовательского режима (библиотеку функций WinDriver UserMode Library, являющуюся, по сути, драйвером пользовательского режима) общаются с кодом режима ядра (модулем WinDriver Kernel).

Процедура DriverEntry и предварительные объявления

Все приведенные ниже отрывки кода следует последовательно поместить в один файл (обычно, файл, содержащий описание DriverEntry, разработчики называют Init.c). Редактирование, разумеется, удобнее всего выполнять в каком-нибудь редакторе интегрированной среды. Рекомендуется использовать редактор из среды Visual Studio, поскольку в нем производится динамический контроль синтаксиса и типов данных языка С.

// init.cpp: Инициализация драйвера

// DriverEntry           Главная точка входа в драйвер

// UnloadRoutine          Процедура выгрузки драйвера

// DeviceControlRoutine   Обработчик DeviceIoControl IRP пакетов

 

#include "Driver.h"

// Предварительные объявления функций:

NTSTATUS DeviceControlRoutine( IN PDEVICE_OBJECT fdo, IN PIRP Irp );

VOID     UnloadRoutine(IN PDRIVER_OBJECT DriverObject);

NTSTATUS ReadWrite_IRPhandler( IN PDEVICE_OBJECT fdo, IN PIRP Irp );

NTSTATUS Create_File_IRPprocessing(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Close_HandleIRPprocessing(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

// Хотя и нехорошо делать глобальные переменные в драйвере...

KSPIN_LOCK MySpinLock;

#pragma code_seg("INIT") // начало секции INIT

 

// (Файл init.cpp)

// DriverEntry - инициализация драйвера и необходимых объектов

// Аргументы:  указатель на объект драйвера

//             раздел реестра (driver service key) в UNICODE

// Возвращает: STATUS_Xxx

extern "C"

NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,

                     IN PUNICODE_STRING RegistryPath  )

{

NTSTATUS status = STATUS_SUCCESS;

PDEVICE_OBJECT  fdo;

UNICODE_STRING  devName;

#if DBG

DbgPrint("=Example= In DriverEntry.");

DbgPrint("=Example= RegistryPath = %ws.", RegistryPath->Buffer);

 #endif

// Экспорт точек входа в драйвер (AddDevice объявлять не будем)

 // DriverObject->DriverExtension->AddDevice= OurAddDeviceRoutine;

DriverObject->DriverUnload = UnloadRoutine;

DriverObject->MajorFunction[IRP_MJ_CREATE]= Create_File_IRPprocessing;

DriverObject->MajorFunction[IRP_MJ_CLOSE] = Close_HandleIRPprocessing;

DriverObject->MajorFunction[IRP_MJ_READ]  = ReadWrite_IRPhandler;

DriverObject->MajorFunction[IRP_MJ_WRITE] = ReadWrite_IRPhandler;

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]= DeviceControlRoutine;

 //========================================================

// Действия по созданию символьной ссылки

// (их нужно было бы делать в OurAddDeviceRoutine, но у нас

// очень простой драйвер и эта процедура отсутствует):

 RtlInitUnicodeString( &devName, L"\\Device\\EXAMPLE" );

// Создаем наш Functional Device Object (FDO) и получаем

 // указатель на созданный FDO в нашей переменной fdo.

// (В WDM драйвере эту работу также следовало бы выполнять

// в процедуре OurAddDeviceRoutine.) При создании FDO

// будет выделено место и под структуру расширения устройства

// EXAMPLE_DEVICE_EXTENSION (для этого мы передаем в вызов

// ее размер, вычисляемый оператором sizeof):

 status = IoCreateDevice(DriverObject,

                           sizeof(EXAMPLE_DEVICE_EXTENSION),

                           &devName, // может быть и NULL

                           FILE_DEVICE_UNKNOWN,

                           0,

                           FALSE, // без эксклюзивного доступа

                           &fdo);

 if(!NT_SUCCESS(status)) return status;

 // Получаем указатель на область, предназначенную под

 // структуру расширение устройства

PEXAMPLE_DEVICE_EXTENSION dx = (PEXAMPLE_DEVICE_EXTENSION)fdo->DeviceExtension;

 dx->fdo = fdo;  // Сохраняем обратный указатель

// Применяя прием условной компиляции, вводим функцию DbgPrint,

// сообщения которой мы сможем увидеть в окне DebugView, если

// выполним сборку нашего драйвера как checked (отладочную)

 // версию:

#if DBG

DbgPrint("=Example= FDO %X, DevExt=%X.",fdo,dx);

 #endif

//=======================================

// Действия по созданию символьной ссылки

// (их нужно было бы делать в OurAddDeviceRoutine, но у нас

// очень простой драйвер):

UNICODE_STRING symLinkName;   // Сформировать символьное имя:

 // #define   SYM_LINK_NAME   L"\\??\\Example"

 // Такого типа символьные ссылки ^^ проходят только в NT.

// (То есть, если перенести бинарный файл драйвера в

// Windows 98, то пользовательские приложения заведомо

// не смогут открыть файл по такой символьной ссылке.)

// Для того, чтобы ссылка работала в и Windows 98 и в NT,

// необходимо поступать следующим образом:

#define SYM_LINK_NAME L"\\DosDevices\\Example"

 RtlInitUnicodeString( &symLinkName, SYM_LINK_NAME );

dx->ustrSymLinkName = symLinkName;

 

// Создаем символьную ссылку

status = IoCreateSymbolicLink( &symLinkName, &devName );

if (!NT_SUCCESS(status))

{ // при неудаче √ удалить Device Object и вернуть управление

 IoDeleteDevice( fdo );

 return status;

       } // Теперь можно вызывать CreateFile("\\\\.\\Example",...);

         // в пользовательских приложениях

       // Объект спин-блокировки, который будем использовать для

       // разнесения во времени выполнения кода обработчика

       // IOCTL запросов. Инициализируем его:

       KeInitializeSpinLock(&MySpinLock);

       // Снова используем условную компиляцию, чтобы выделить код,

       // компилируемый в отладочной версии и не компилируемый в

       // версии free (релизной):

       #if DBG

       DbgPrint("=Example= DriverEntry successfully completed.");

       #endif

       return status;

}

#pragma code_seg() // end INIT section

Функция CompleteIrp

Вспомогательная функция CompleteIrp реализует действия по завершению обработки IRP пакета с кодом завершения status. Данная функция предназначена для внутренних нужд драйвера и нигде не регистрируется. Параметр info, если он не равен нулю, чаще всего содержит число байт, переданных клиенту (полученных от клиента) драйвера.

// (Файл init.cpp)

// CompleteIrp: Устанавливает IoStatus и завершает обработку IRP

// Первый аргумент - указатель на объект нашего FDO.

//

NTSTATUS CompleteIrp( PIRP Irp, NTSTATUS status, ULONG info)

{

Irp->IoStatus.Status = status;

Irp->IoStatus.Information = info;

IoCompleteRequest(Irp,IO_NO_INCREMENT);

 return status;

}

Рабочая процедура обработки запросов read/write

Процедура ReadWrite_IRPhandler предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодами IRP_MJ_READ/IRP_MJ_WRITE по результатам обращения к драйверу из пользовательских приложений с вызовами read/write или из кода режима ядра с вызовами ZwReadFile или ZwWriteFile. В данном примере наша функция обработки запросов чтения/записи ничего полезного не делает, и ее регистрация выполнена только для демонстрации, как это могло бы быть в более "развитом" драйвере.

// (Файл init.cpp)

// ReadWrite_IRPhandler: Берет на себя обработку запросов

// чтения/записи и завершает обработку IRP вызовом CompleteIrp

// с числом переданных/полученных байт (BytesTxd) равным нулю.

// Аргументы:

// Указатель на объект нашего FDO

// Указатель на структуру IRP, поступившего от Диспетчера ввода/вывода

NTSTATUS ReadWrite_IRPhandler( IN PDEVICE_OBJECT fdo, IN PIRP Irp )

{

ULONG BytesTxd = 0;

NTSTATUS status = STATUS_SUCCESS; //Завершение с кодом status

 // Задаем печать отладочных сообщений √ если сборка отладочная

 #if DBG

DbgPrint("-Example- in ReadWrite_IRPhandler.");

#endif

return CompleteIrp(Irp,status,BytesTxd);

}

Рабочая процедура обработки запросов открытия драйвера

Процедура Create_File_IRPprocessing предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодами IRP_MJ_CREATE по результатам обращения к драйверу из пользовательских приложений с вызовами CreateFile или из кода режима ядра с вызовами ZwCreateFile. В нашем примере эта функция не выполняет никаких особых действий (хотя можно было бы завести счетчик открытых дескрипторов и т.п.), однако без регистрации данной процедуры система просто не позволила бы клиенту "открыть" драйвер для работы с ним (хотя сам драйвер мог бы успешно загружаться и стартовать).

//

// (Файл init.cpp)

// Create_File_IRPprocessing: Берет на себя обработку запросов с

// кодом IRP_MJ_CREATE.

// Аргументы:

// Указатель на объект нашего FDO

// Указатель на структуру IRP, поступившего от Диспетчера ВВ

//

NTSTATUS Create_File_IRPprocessing(IN PDEVICE_OBJECT fdo,IN PIRP Irp)

{

PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 // Задаем печать отладочных сообщений - если сборка отладочная

 #if DBG

DbgPrint("-Example- Create File is %ws",

 &(IrpStack->FileObject->FileName.Buffer));

#endif

return CompleteIrp(Irp,STATUS_SUCCESS,0); // Успешное завершение

}

Рабочая процедура обработки запросов закрытия драйвера

Процедура Close_File_IRPprocessing предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодом IRP_MJ_CLOSE по результатам обращения к драйверу из пользовательских приложений с вызовами CloseHandle или из кода режима ядра с вызовами ZwClose. В нашем примере эта функция не выполняет никаких особых действий, однако, выполнив регистрацию процедуры открытия файла, мы теперь просто обязаны зарегистрировать процедуру завершения работы клиента с открытым дескриптором. Заметим, что если клиент пользовательского режима забывает закрыть полученный при открытии доступа к драйверу дескриптор, то за него эти запросы выполняет операционная система (впрочем, как и в отношении всех открытых приложениями файлов, когда приложения завершаются без явного закрытия открытых файлов).

// (Файл init.cpp)

// Close_File_IRPprocessing: Берет на себя обработку запросов с

// кодом IRP_MJ_CLOSE.

// Аргументы:

// Указатель на объект нашего FDO

// Указатель на структуру IRP, поступившего от Диспетчера ввода/вывода

NTSTATUS Close_HandleIRPprocessing(IN PDEVICE_OBJECT fdo,IN PIRP Irp)

{

#if DBG

// Задаем печать отладочных сообщений - если сборка отладочная

 DbgPrint("-Example- In Close handler.");

#endif

return CompleteIrp(Irp,STATUS_SUCCESS,0);// Успешное завершение

}

Рабочая процедура обработки IOCTL запросов

Процедура DeviceControlRoutine предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодом IRP_MJ_DEVICE_CONTROL по результатам обращения к драйверу из пользовательских приложений с вызовами DeviceIoControl.

В нашем примере это самая важная функция. Она реализует обработку пяти IOCTL запросов:

  •  IOCTL_PRINT_DEBUG_MESS — выводим отладочное сообщение в окно DebugView.
  •  IOCTL_CHANGE_IRQL — проводим эксперимент, насколько высоко можно искусственно поднять уровень IRQL в коде драйвера.
  •  IOCTL_MAKE_SYSTEM_CRASH — проводим эксперимент по "обрушению" операционной системы и пытаемся его предотвратить.
  •  IOCTL_TOUCH_PORT_378H — проводим эксперимент по обращению к аппаратным ресурсам системы.
  •  IOCTL_SEND_BYTE_TO_USER — отправляем байт данных в пользовательское приложение.

Эти IOCTL коды являются пользовательскими — они определены с помощью макроса CTL_CODE в файле Driver.h, который является частью данного проекта, и речь о котором пойдет ниже.

// (Файл init.cpp)

// DeviceControlRoutine: обработчик IRP_MJ_DEVICE_CONTROL запросов

// Аргументы:

// Указатель на объект нашего FDO

// Указатель на структуру IRP, поступившего от Диспетчера ВВ

// Возвращает:  STATUS_XXX

// #define SMALL_VERSION

// В том случае, если не закомментировать верхнюю строчку √ будет

// выполнена компиляция версии, в которой будет обрабатываться только

// один тип IOCTL запросов -- IOCTL_MAKE_SYSTEM_CRASH

NTSTATUS DeviceControlRoutine( IN PDEVICE_OBJECT fdo, IN PIRP Irp )

{

NTSTATUS status = STATUS_SUCCESS;

ULONG BytesTxd =0; // Число переданных/полученных байт (пока 0)

PIO_STACK_LOCATION IrpStack=IoGetCurrentIrpStackLocation(Irp);

 // Получаем указатель на расширение устройства

PEXAMPLE_DEVICE_EXTENSION dx =

    (PEXAMPLE_DEVICE_EXTENSION)fdo->DeviceExtension;

 //-------------------------------

// Выделяем из IRP собственно значение IOCTL кода, по поводу

// которого случился вызов:

ULONG ControlCode =

 IrpStack->Parameters.DeviceIoControl.IoControlCode;

ULONG method = ControlCode & 0x03;

 // Получаем текущее значение уровня IRQL √ приоритета,

// на котором выполняется поток (вообще говоря, целое число):

 KIRQL irql,

currentIrql = KeGetCurrentIrql();

#if DBG

DbgPrint("-Example- In DeviceControlRoutine (fdo= %X)\n",fdo);

DbgPrint("-Example- DeviceIoControl: IOCTL %x.", ControlCode );

if(currentIrql==PASSIVE_LEVEL)

 DbgPrint("-Example- PASSIVE_LEVEL (val=%d)",currentIrql);

 #endif

// Запрашиваем владение объектом спин-блокировки. В данном

// примере не выполняется никаких критичных действий, но,

// вообще говоря, этот прием может быть полезен и даже

// незаменим, если в приведенном ниже коде должны будут

// выполнены манипуляции, которые можно делать только

// эксклюзивно. Пока потоку выделен объект спин-блокировки √

// никакой другой поток не сможет войти в оператор switch:

KeAcquireSpinLock(&MySpinLock,&irql);

// Диспетчеризация по IOCTL кодам:

 switch( ControlCode) {

 #ifndef SMALL_VERSION

case IOCTL_PRINT_DEBUG_MESS:

 {     // Только вводим сообщение и только в отладочной версии

 #if DBG

 DbgPrint("-Example- IOCTL_PRINT_DEBUG_MESS.");

 #endif

 break;

}

case IOCTL_CHANGE_IRQL:

 {

 #if DBG

 // Эксперименты по искусственному повышению

 // IRQL √ только в отладочной версии!

 DbgPrint("-Example- IOCTL_CHANGE_IRQL.");

 KIRQL dl = DISPATCH_LEVEL, // только для распечатки (2)

 oldIrql,

 newIrql=25; // Новый уровень IRQL (например, 25)

 // Устанавливаем newIrql, сохраняя текущий в oldIrql:

 KeRaiseIrql(newIrql,&oldIrql);

 newIrql=KeGetCurrentIrql(); // Что реально получили?

 DbgPrint("-Example- DISPATCH_LEVEL value =%d",dl);

 DbgPrint("-Example- IRQLs are old=%d new=%d",

     oldIrql,newIrql);

 KeLowerIrql(oldIrql); // Возвращаем старое значение

 #endif

 break;

}

#endif // SMALL_VERSION

case IOCTL_MAKE_SYSTEM_CRASH:

{

 int errDetected=0;

 char x = (char)0xFF;

 #if DBG  // Вообще говоря, под NT мы этого уже не увидим:

 DbgPrint("-Example- IOCTL_MAKE_SYSTEM_CRASH.");

 #endif

 // Вызываем системный сбой обращением по нулевому адресу

 __try {

 x = *(char*)0x0L; // ошибочная ситуация

  //^^^^^^^^^^^^ здесь случится сбой NT, но не Win98

 }

 __except(EXCEPTION_EXECUTE_HANDLER)

 {   // Перехват исключения не работает!

  // Эта занимательная ситуация объяснена в 10.2.6,

  // при рассмотрении объектов спин-блокировок.

  errDetected=1;

 };

 #if DBG

 DbgPrint("-Example- Value of x is %X.",x);

 if(errDetected)

  DbgPrint("-Example- Except detected in Example driver.");

 #endif

 break;

}

#ifndef SMALL_VERSION

case IOCTL_TOUCH_PORT_378H:

{

 unsigned short ECRegister = 0x378+0x402;

 #if DBG

 DbgPrint("-Example- IOCTL_TOUCH_PORT_378H.");

 #endif

 // Пробуем программно перевести параллельный порт 378,

 // сконфигурированный средствами BIOS как ECP+EPP, в

 // режим EPP.

 _asm  {

  mov dx,ECRegister ;

  xor al,al    ;

  out dx,al    ;    Установить EPP mode 000

  mov al,095h  ;    Биты 7:5 = 100

  out dx,al    ;    Установить EPP mode 100

 }

 // Подобные действия в приложении пользовательского

 // режима под NT  обязательно привело бы к аварийной

 // выгрузке приложения с сообщением об ошибке!

 // Практически эти пять строк демонстрируют, что можно

 // работать с LPT портом под Windows NT !

 break;

}

case IOCTL_SEND_BYTE_TO_USER:

 {

 // Размер данных, поступивших от пользователя:

 ULONG InputLength = //только лишь для примера

  IrpStack->Parameters.DeviceIoControl.InputBufferLength;

 // Размер буфера для данных, ожидаемых пользователем

 ULONG OutputLength =

 IrpStack->Parameters.DeviceIoControl.OutputBufferLength;

 #if DBG

 DbgPrint("-Example- Buffer outlength %d",OutputLength);

 #endif

 if( OutputLength<1 )

 {// Если не предоставлен буфер √ завершить IRP с ошибкой

  status = STATUS_INVALID_PARAMETER;

  break;

 }

 UCHAR *buff; // unsigned char, привыкаем к новой нотации

 if(method==METHOD_BUFFERED)

 {

  buff = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;

  #if DBG

  DbgPrint("-Example- Method : BUFFERED.");

  #endif

 }

 else

  if (method==METHOD_NEITHER)

  {

   buff=(unsigned char*)Irp->UserBuffer;

   #if DBG

   DbgPrint("-Example- Method : NEITHER.");

   #endif

  }

  else

  {

   #if DBG

   DbgPrint("-Example- Method : unsupported.");

   #endif

   status = STATUS_INVALID_DEVICE_REQUEST;

   break;

  }

 #if DBG

 DbgPrint("-Example- Buffer address is %08X",buff);

 #endif

 *buff=33;     // Любимое число Штирлица

 BytesTxd = 1; // Передали 1 байт

 break;

}

#endif // SMALL_VERSION

// Ошибочный запрос (код IOCTL, который не обрабатывается):

 default: status = STATUS_INVALID_DEVICE_REQUEST;

}

// Освобождение спин-блокировки

KeReleaseSpinLock(&MySpinLock,irql);

#if DBG

DbgPrint("-Example- DeviceIoControl: %d bytes written.", (int)BytesTxd);

#endif

return CompleteIrp(Irp,status,BytesTxd); // Завершение IRP

}

Рабочая процедура выгрузки драйвера

Процедура UnloadRoutine выполняет завершающую работу перед тем как драйвер растворится в небытии.

При следовании WDM модели, драйвер должен был бы зарегистрировать обработчик PnP запросов (то есть IRP_MJ_PNP) и перед вызовом UnloadRoutine получал бы IRP пакеты с кодом IRP_MJ_PNP и суб-кодом IRP_MN_STOP_DEVICE (например, когда пользователь решил отключить устройство, воспользовавшись окном Диспетчера Устройств в Настройках системы). В этом обработчике и следует выполнять действия, предшествующие удалению WDM драйвера. 

//

// (Файл init.cpp)

// UnloadRoutine: Выгружает драйвер, освобождая оставшиеся объекты

// Вызывается системой, когда необходимо выгрузить драйвер.

// Как и процедура AddDevice, регистрируется иначе чем

// все остальные рабочие процедуры и не получает никаких IRP.

// Arguments:  указатель на объект драйвера

//

#pragma code_seg("PAGE")

// Допускает размещение в странично организованной памяти

//

VOID UnloadRoutine(IN PDRIVER_OBJECT pDriverObject)

{

PDEVICE_OBJECT pNextDevObj;

int i;

 // Задаем печать отладочных сообщений √ если сборка отладочная

 #if DBG

DbgPrint("-Example- In Unload Routine.");

 #endif

//==========================================================

// Нижеприведенные  операции в полномасштабном WDM драйвере

// следовало бы поместить в обработчике IRP_MJ_PNP запросов

// с субкодом IRP_MN_REMOVE_DEVICE, но в силу простоты

// драйвера, сделаем это здесь.

// Проходим по всем объектам устройств, контролируемым

 // драйвером

pNextDevObj = pDriverObject->DeviceObject;

for(i=0; pNextDevObj!=NULL; i++)

{

 PEXAMPLE_DEVICE_EXTENSION dx =

   (PEXAMPLE_DEVICE_EXTENSION)pNextDevObj->DeviceExtension;

 // Удаляем символьную ссылку и уничтожаем FDO:

 UNICODE_STRING *pLinkName = & (dx->ustrSymLinkName);

 // !!! сохраняем указатель:

 pNextDevObj = pNextDevObj->NextDevice;

 #if DBG

 DbgPrint("-Example- Deleted device (%d) : pointer to FDO = %X.",

      i,dx->fdo);

 DbgPrint("-Example- Deleted symlink = %ws.", pLinkName->Buffer);

 #endif

 IoDeleteSymbolicLink(pLinkName);

 IoDeleteDevice( dx->fdo);

}

}

#pragma code_seg() // end PAGE section

Заголовочный файл Driver.h

Ниже приводится полный текст файла Driver.h, содержащий объявления, необходимые для компиляции драйвера Example.sys.

#ifndef _DRIVER_H_04802_BASHBD_1UIWQ1_8239_1NJKDH832_901_

#define _DRIVER_H_04802_BASHBD_1UIWQ1_8239_1NJKDH832_901_

// Выше приведены две строки (в конце файла имеется еще #endif),

// которые в больших проектах запрещают повторные  проходы по тексту,

// который находится внутри h-файла (что весьма удобно для повышения

// скорости компиляции).

// (Файл Driver.h)

#ifdef __cplusplus

extern "C"

{

#endif

#include "ntddk.h"

//#include "wdm.h"

// ^^^^^^^^^^^^^^ если выбрать эту строку и закомментировать

// предыдущую, то компиляция  в среде DDK (при помощи утилиты Build)

// также пройдет успешно, однако драйвер Example не станет от этого

// настоящим WDM драйвером.

#ifdef __cplusplus

}

#endif

// Определяем структуру расширения устройства. Включим в нее

// указатель на FDO (для удобства последующей работы UnloadRoutine) и

// имя символьной ссылки в формате UNOCODE_STRING.

typedef struct _EXAMPLE_DEVICE_EXTENSION

{

PDEVICE_OBJECT fdo;

UNICODE_STRING ustrSymLinkName; // L"\\DosDevices\\Example"

} EXAMPLE_DEVICE_EXTENSION, *PEXAMPLE_DEVICE_EXTENSION;

// Определяем собственные коды IOCTL, с которыми можно будет

// обращаться к драйверу при помощи вызова DeviceIoControl.

// Определение макроса CTL_CODE можно найти в файле DDK Winioctl.h.

// Там же можно найти и численные значения, скрывающиеся под именами

// METHOD_BUFFERED и METHOD_NEITHER.

// Внимание! Текст приведенный ниже должен войти в файл Ioctl.h,

// который будет необходим для компиляции тестового приложения.

// (Разумеется, за исключением последней строки с "#endif".)

#define IOCTL_PRINT_DEBUG_MESS CTL_CODE( \

FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_CHANGE_IRQL CTL_CODE(\

FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_MAKE_SYSTEM_CRASH CTL_CODE( \

FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_TOUCH_PORT_378H CTL_CODE( \

FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_SEND_BYTE_TO_USER CTL_CODE( \

FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)

// Вариант :

//#define IOCTL_SEND_BYTE_TO_USER CTL_CODE( \

//    FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS)

#endif

Третий параметр CTL_CODE называется Function и при составлении собственных (пользовательских) IOCTL кодов его значение не должно быть менее 0x800. Пересечение пользовательских IOCTL кодов со значениями IOCTL кодов других драйверов не имеет никакого значения, поскольку они действуют только в пределах конкретного драйвера.

Пакеты IRР

Практически весь процесс ввода/вывода, имеющий место в Windows, является пакетно-управляемым. Отдельная транзакция ввода/вывода описывается рабочим рецептом, предписывающим драйверу, что делать. При помощи IRP прослеживается также обработка запроса в подсистеме ввода/вывода. Этот рабочий рецепт имеет форму структуры данных, называемой I/o Request Packet (IRP) — пакет запроса на ввод/вывод.

При каждом запросе из программного кода клиента драйвера на выполнение операции ввода/вывода, включая IOCTL запросы (управляющие воздействия на аппаратуру), Диспетчер ввода/вывода выделяет под IRP область нестраничной памяти. Определив по дескриптору открытого файла, к какому драйверу и объекту устройства адресовано обращение, и по запрошенному коду операции ввода/вывода (IRP_MJ_Xxx), Диспетчер передает сформированный пакет IRP в соответствующую рабочую (см.ниже) процедуру драйвера. (Следует отметить, что для доступа из программного кода клиента применяется "файловая абстракция" процесса взаимодействия с драйвером — открытие, чтение, запись, дескрипторы и т.п.)

Пакеты IRP являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых I/O stack location — ячейкой стека ввода/вывода.

Структура пакета IRP

Заголовок IRP

Ниже перечислены поля заголовка, к которым можно обращаться из программного кода драйвера. К другим полям заголовка обращаться не рекомендуется.

Таблица Заголовок пакета IRP 

Поля

Описание

IO_STATUS_BLOCK IoStatus

Код состояния (статус) запроса

PVOID AssociatedIrp.SystemBuffer

Указатель на системный буфер для случая, если устройство поддерживает буферизированный ввод/вывод

PMDL MdlAddress

Указатель на MDL список в случае, если устройство поддерживает прямой ввод/вывод

PVOID UserBuffer

Адрес пользовательского буфера для ввода/вывода

BOOLEAN Cancel

Индикатор того, что пакет IRP должен быть аннулирован

Фрагмент структуры IRP под названием IoStatus фиксирует окончательное состояние данной операции ввода/вывода. Когда драйвер готов завершить обработку пакета IRP, он устанавливает в поле IoStatus.Status значение STATUS_XXX. В поле IoStatus.Information этого блока записывается 0 (если произошла ошибка) или другое определенное операцией ввода/вывода значение, чаще всего — количество переданных/полученных байт данных (которое может быть и равно нулю). Вопросы адресации и доступа к буферам данных, описываемых пакетом IRP, будут рассмотрены позже.

Ячейки стека ввода/вывода

Основное назначение ячеек стека ввода/вывода (I/O stack location) состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/вывод (последние могут претерпевать изменения при путешествии пакета по стеку драйверов). Ниже приводятся поля ячеек стека ввода/вывода, к которым драйвер может обращаться непосредственно по указателю (чего не рекомендуется делать для остальных полей).

Таблица 8.6. Некоторые элементы ячейки стека ввода/вывода

IO_STACK_LOCATION, *PIO_STACK_LOCATION

Поля

Описание

UCHAR MajorFunction

Код IRP_MJ_XXX, описывающий назначение операции

UCHAR MinorFunction

Суб-код операции

PDEVICE_OBJECT
DeviceObject

Указатель на объект устройства, которому был адресован данный запрос IRP

PFILE_OBJECT FileObject

Файловый объект для данного запроса, если он задан

union Parameters (трактовка определяется значением MajorFunction):

struct Read

Параметры для IRP типа IRP_MJ_READ:
• ULONG Length
• ULONG Key
• LARGE_INTEGER ByteOffset

struct Write

Параметры для IRP типа IRP_MJ_WRITE:
• ULONG Length
• ULONG Key
• LARGE_INTEGER ByteOffset

struct DeviceIoControl

Параметры для IRP типа IRP_MJ_DEVICE_CONTROL:
• ULONG OutputBufferLenght
• ULONG InputBufferLenght
• ULONG IoControlCode
• PVOID Type3InputBuffer

Для запроса, который адресован драйверу самого нижнего уровня, соответствующий IRP пакет имеет только одну ячейку стека. Для запроса, который послан драйверу верхнего уровня, Диспетчер ввода/вывода создает пакет IRP с несколькими стековыми ячейками — по одной на каждый драйверный слой. Другими словами, размер стека в пакете IRP численно равен количеству драйверных слоев, участвующих в обработке запроса на данную операцию. Любому драйверу в иерархии разрешен доступ только к его собственной — "числящейся" за ним — ячейке стека пакета IRP (хотя никто не контролирует и иные "противоправные" действия).

В случае если текущий драйвер решит обратиться к драйверу более низкого уровня с новым пакетом IRP собственного производства, то он должен гарантировать, что в новом IRP пакете имеется достаточное количество стековых ячеек.

Когда драйвер передает IRP пакет нижнему драйверному уровню, Диспетчер ввода/вывода автоматически изменяет указатель стека ввода/вывода IRP пакета таким образом, что он указывает на стековую ячейку, предназначенную для драйвера очередного нижнего уровня. Когда обработка пакета драйвером нижнего уровня завершена, и он "отпускает" пакет IRP, указатель стека снова возвращается в исходное положение и указывает на ячейку стека для лежащего выше драйвера. Разумеется, для получения указателя на текущую ячейку стека существует специальный системный вызов IoGetCurrentStackLocation.

Процедура DriverEntry

Процедура DriverEntry присутствует в любом драйвере и имеет данное стандартное имя. Драйверы "в-стиле-NT" выполняют в DriverEntry большую работу, нежели WDM драйвера — последние откладывают часть работы по инициализации устройства до момента обнаружения устройства системой, когда будет вызвана процедура AddDevice.

Таблица Параметры вызова функции DriverEntry

NTSTATUS DriverEntry

IRQL == PASSIVE_LEVEL

Параметры

Описание

IN PDRIVER_OBJECT pDriverObject

Адрес объекта драйвера

IN PUNICODE_STRING pRegistryPath

Путь в регистре к подразделу драйвера

Возвращаемое значение

• STATUS_SUCCESS
• STATUS_XXX —
код ошибки 

Получив от Диспетчера ввода/вывода указатель на структуру DRIVER_OBJECT (см. заголовочные файлы DDK ntddk.h или wdm.h), драйвер должен заполнить в ней определенные поля, а именно:

  •  Поле pDriverObject->DriverUnload — для регистрации собственной функции Unload, которая вызывается перед выгрузкой драйвера.
  •  Поле pDriverObject->DriverStartIo — для регистрации собственной функции StartIo, которая необходима для организации обработки очереди необработанных запросов System Queuing.
  •  Поле pDriverObject->DriverExtension->AddDevice — в структуре расширения объекта драйвера DRIVER_EXTENSION (см. ntddk.h или wdm.h), в котором WDM драйвер регистрирует собственную процедуру AddDevice.
  •  В массиве pDriverObject->MajorFunction[IRP_MJ_Xxx] драйвер регистрирует точки входа в собственные рабочие процедуры.

Регистрация рабочих процедур происходит обычно в виде:

DriverObject->MajorFunction[IRP_MJ_READ]= ReadWrite_IRPhandler;

DriverObject->MajorFunction[IRP_MJ_WRITE]=ReadWrite_IRPhandler;

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=

 DeviceControlRoutine;

. . . . . . . . .

Однако для фильтр-драйверов модели WDM, которым интересен только определенный тип запросов, возможен следующий тип регистрации:

int i;

for( i=0; i<IRP_MJ_MAXIMUM_FUNCTION; i++)

{

DriverObject->MajorFunction[i]= myPassIrpDown;

}

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=

 DeviceControlRoutine;  

Здесь все запросы будут приводить к вызову функции myPassIrpDown, которая занимается только лишь переадресацией запросов нижним драйверам в стеке WDM драйверов. Исключение составит функция-обработчик IOCTL запросов, которые будут поступать в функцию DeviceControlRoutine фильтр-драйвера.

Помимо регистрации функций, процедура DriverEntry драйвера "в-стиле-NT" может выполнять следующую работу:

  1.  DriverEntry определяет аппаратное обеспечение, которое драйвер будет контролировать. Это аппаратное обеспечение выделяется драйверу, то есть помечается как находящееся под управлением данного драйвера.
  2.  Если драйвер управляет многокомпонентным (multiunit) или многофункциональным контроллером, используется IoCreateController для создания объекта контроллера, после чего инициализируется структура расширения контроллера.
  3.  Выполняет вызов IoCreateDevice для создания объекта устройства для каждого физического или логического устройства под управлением данного драйвера, в процессе которого инициализируется структура расширения устройства для каждого созданного объекта устройства. Рекомендуется сразу же после этого вызова явно установить флаги (поле Flags в объекте устройства), описывающие способ буферизации, используемый данным устройством.
  4.  Созданные устройства затем делаются видимыми для приложений пользовательского режима путем выполнения вызова IoCreateSymbolicLink.
  5.  Устройство подключается к объекту прерываний. В случае, если ISR процедура требуют использования объекта DPC (отложенного процедурного вызова), то он создается и инициализируется на этом этапе.
  6.  Шаги с 3 по 5 повторяются для каждого физического или логического устройства, работающего под управлением данного драйвера.
  7.  В случае успешного завершения, функция DriverEntry должна возвратить Диспетчеру ввода/вывода значение STATUS_SUCCESS.

В случае, если процедура DriverEntry обнаружила, что некоторые действия, которые она должна выполнить как часть подготовки к запуску драйвера, не могут быть выполнены сейчас, но в будущем (после загрузки некоторых других драйверов, например) это станет возможным, то DriverEntry должна зарегистрировать собственную процедуру, которая завершит инициализацию чуть позже. Регистрация такой процедуры выполняется системным вызовом IoRegisterDriverReinitialization (см. таблицу 8.2).

Таблица Параметры системного вызова IoRegisterDriverReinitialization 

VOID IoRegisterDriverReinitialization

IRQL == PASSIVE_LEVEL

Параметры

Регистрирует функцию драйвера для отложенной инициализации

IN PDRIVER_OBJECT pDriverObject

Указатель на объект драйвера

IN PDRIVER_REINITIALIZE DriverReinitializationRoutine

Указатель на процедуру реинициализации, предоставляемую драйвером (см. таблицу 8.3 ниже).

IN PVOID Context

Контекстный указатель, который получит регистрируемая функция при вызове

Возвращаемое значение

void

Таблица Описание параметров вызова myReinitializeFunction

VOID myReinitializeFunction

IRQL == PASSIVE_LEVEL

Параметры

Функция драйвера, регистрируемая для выполнения отложенной инициализации

IN PDRIVER_OBJECT pDriverObject

Указатель на объект драйвера

IN PVOID Context

Контекстный блок, указанный при регистрации

IN ULONG

Количество вызовов процедуры ре-инициализации (отсчет от 0)

Возвращаемое значение

void

Процедура AddDevice

Процедура AddDevice, выполняющая в WDM драйверах работу по подготовке устройства к работе.

Процедура Unload

Как правило, однажды загруженный драйвер остается в системе до перезагрузки. Для того чтобы сделать драйвер выгружаемым, необходимо написать и зарегистрировать процедуру выгрузки Unload. Диспетчер ввода/вывода затем производит вызов этой процедуры в момент ручной либо автоматической выгрузки драйвера — как раз перед удалением драйвера из памяти.

Таблица  Описание прототипа функции Unload 

VOID Unload

IRQL == PASSIVE_LEVEL

Параметры

Выполняет завершающие действия

IN PDRIVER_OBJECT pDriverObject

Указатель на объект драйвера

Возвращаемое значение

void

Хотя действия процедуры Unload могут меняться от драйвера к драйверу, общими являются следующие шаги, характерные более для драйверов "в-стиле-NT".

  1.  Для некоторых типов аппаратуры необходимо сохранить ее состояние в Системном Реестре. При последующей загрузке драйвера эти данные могут быть использованы в процедуре DriverEntry. Скажем, драйвер принтера может сохранить последнее значение разрешения печати.
  2.  Если прерывания разрешены для обслуживаемого устройства, то процедура выгрузки должна запретить их и произвести отключение от объекта прерываний. Ситуация, когда устройство будет порождать запросы на прерывание в то время, как объект прерываний не существует, неминуемо приведет к краху системы.
  3.  Символьная ссылка должна быть удалена из пространства имен, видимого пользовательскими приложениями. Это выполняется при помощи вызова IoDeleteSymbolicLink.
  4.  Объект устройства должен быть удален вызовом IoDeleteDevice.
  5.  В случае, если драйвер управляет многокомпонентным (multiunit) контроллером, необходимо повторить шаги 3 и 4 для каждого устройства, подключенного к контроллеру, а затем необходимо удалить сам объект контроллера при помощи вызова IoDeleteController.
  6.  Следует выполнить освобождение памяти, выделенной драйверу, во всех типах оперативной памяти.

Драйверы WDM модели выполняют практически все из описанных выше действий в обработчике IRP_MJ_PNP запросов с субкодом IRP_MN_REMOVE (то есть посвященном удалению устройства из системы).

Поскольку процедура Unload выполняется на уровне PASSIVE_LEVEL IRQL, то это означает возможность безопасного доступа к ресурсам страничной памяти.

Адресация и доступ к данным в IRP пакетах чтения/записи

Сразу же после создания объекта устройства (будет ли это сделано в процедуре DriverEntry, как это было указано выше для драйвера "в-стиле-NT", или в процедуре AddDevice для WDM драйверов) рекомендуется явно описать способ, каким новый объект устройства готов воспринимать поля пакета IRP, описывающие адреса областей памяти, через которые будет происходить обмен между драйвером и его клиентами. Например:

PDEVICE_OBJECT pNewDeviceObject;

IoCreateDevice(. . . , &pNewDeviceObject);

pNewDeviceObject->Flags |= DO_BUFFERED_IO;

либо:

pNewDeviceObject->Flags |= DO_DIRECT_IO;

По умолчанию подразумевается 'pNewDeviceObject->Flags |= 0' — метод NEITHER (ни один из первых двух).

В том случае, если новый объект устройства ориентирован на работу с нижним драйвером в стеке драйверов, следует скопировать эти флаги из объекта устройства, к которому подключен данный (одним из вызовов IoAttachXxx, см. главу 9), например:

pNewDeviceObject->Flags |=

(pUnderlyingDevObject->Flags) & (DO_BUFFERED_IO | DO_DIRECT_IO);

По традиции, главными считаются запросы в форме пакетов IRP с основным кодом IRP_MJ_READ (в результате вызова ReadFile) либо IRP_MJ_WRITE (в результате вызова WriteFile).

Диспетчер ввода/вывода, если он замечает в описании устройства установленный флаг DO_DIRECT_IO, непременно проверяет возможность доступа к буферу, который клиент драйвера указывает в своем запросе как буфер с данными (WRITE) или для данных (READ), подготавливает MDL список для него и фиксирует страницы в оперативной памяти. Адрес подготовленного таким образом MDL списка вносится в поле IRP пакета под названием MdlAddress. Если попробовать получить виртуальный адрес от данного MDL списка вызовом MmGetMdlVirtualAddress, то получится именно виртуальный адрес (пользовательского адресного пространства), который предоставило пользовательское приложение в качестве адреса буфера с выводимыми данными. Адрес в терминах системного адресного пространства для той же области данных можно получить, если вызвать MmGetSystemAddressForMdl. Когда обработка запроса ввода/вывода полностью завершена, клиентский буфер де-блокируется и удаляется из схемы распределения системной области памяти.

В том случае, если объект устройства, которому адресован IRР пакет, описан флагом DO_BUFFERED_IO, то драйвер должен взять адрес буферной области из поля IRP пакета AssociatedIrp.SystemBuffer. Данный адрес будет адресом в системном адресном пространстве. Диспетчер ввода/вывода выделяет этот буфер в нестраничной памяти после проверки на доступность предоставленного клиентом буфера. При запросе ввода данных (READ-запрос) Диспетчер ввода/вывода по окончании операции переносит данные из системного буфера в клиентский, а адрес клиентского буфера запоминается в поле IRP пакета UserBuffer. При запросе вывода данных (WRITE-запрос) в системный буфер переносятся данные из клиентского буфера (поле UserBuffer равно NULL). B обоих описанных выше случаях, предоставленные в IRP пакете адреса AssociatedIrp.SystemBuffer можно использовать в произвольном контексте в потоках режима ядра.

В том случае, если устройство не объявило признаков DO_DIRECT_IO или DO_BUFFERED_IO, то Диспетчер ввода/вывода не делает никаких особых действий, а просто помещает адрес буферной области, переданной ему инициатором запроса в поле IRP пакета UserBuffer. Оба поля Associate.SystemBuffer и MdlAddress устанавливаются равными NULL.

В последнем случае помещенный в пoлe UserBuffer виртуальный адрес является "нехорошим" адресом. В большинстве случаев инициатором обращения к драйверу является программный поток пользовательского режима. Соответственно, предоставленный им виртуальный адрес требует для своей корректной интерпретации контекст соответствующего пользовательского потока. Что, разумеется, исключает возможность использования такого адреса в произвольных контекстах режима ядра без предварительной подготовки (подготовки MDL списка и т.п.). Поэтому метод NEITHER можно рекомендовать только для драйверов самых верхних драйверных слоев.

Длина буфера во всех случаях передается в полях Parameters.Write.Length (при WRITE-запросах) или Parameters.Read.Length (при READ-запросах).

PAGE  9


 

А также другие работы, которые могут Вас заинтересовать

44018. WEB – орієнтована інформаційна система «Math» 1.47 MB
  До цих джерел шуму відносяться також залізничні вузли і станції великі автовокзали і автогосподарства мотелі і кемпінги трейлерні парки промислові об'єкти і великі бази будівельної індустрії енергетичні установки. Це і проблема могильників і проблема саркофагу який поступово руйнується проблема населення яке все ще проживає в тридцяти кілометровій зоні. Арк. Підпис Дата Арк.
44019. Место ангиографической аппаратуры в решении задач улучшения диагностической помощи населению и принцип получения ангиограмм 1.24 MB
  Требования к техническим средствам ангиографического комплекса и принцип комплектования аппаратуры. Питающее устройство ангиографического аппарата. Штативные устройства ангиографического комплекса. Назначение и устройство стола координат ангиографического комплекса.
44020. СПЕЦИФІКА АНГЛІЙСЬКОЇ ІНТЕРНЕТ-РЕКЛАМИ, ЇЇ КОГНІТИВНИЙ АСПЕКТ 2.14 MB
  Англійська Інтернет-реклама – є новим видом розповсюдження інформації. Сучасні англійські інформаційні та телекомунікаційні рекламні технології істотно змінюють не тільки спосіб виробництва продуктів і послуг, але й організацію, форми проведення дозвілля, реалізацію людиною своїх громадянських прав методи і форми виховання освіти. Англійська Інтернет-реклама має вирішальний вплив на соціальну структуру суспільства, економіку, політику та розвиток різних суспільних інститутів.
44021. Емпіричне дослідження впливу потреб на професійний вибір підлітків 545.5 KB
  Підходи до визначення поняття потреби. потреба є психічним явищем відображення об’єктивної нужди у чомусь організму біологічні потреби та особистості соціальні потреби. Поява нової потреби заставляє дитину проявляти для досягнення цього стану активність в ході якої і відбувається розвиток. При задоволенні потреби людина досягає стану спокою.
44022. Контактная разность потенциалов 99 KB
  Наиболее важно понятие контактной разности потенциалов для твёрдых проводников металлов и полупроводников. В конечном счёте достигается равновесие при котором потоки электронов в обоих направлениях становятся одинаковыми и между проводниками устанавливается контактная разность потенциалов Значение контактной разности потенциалов равно разности работ выхода отнесённой к заряду электрона. Если составить электрическую цепь из нескольких проводников то контактная разность потенциалов между крайними проводниками определяется только их...
44023. Организация и проведение маркетинговых исследований на базе сети магазинов «Л'Этуаль» 2.14 MB
  Проанализировать процесс организации и проведения маркетинговых исследований на базе сети магазинов «Л'Этуаль», выявить основные проблемы, возникающие при их реализации, и разработать пути совершенствования существующих бизнес процессов с целью усовершенствования качества услуг, предоставляемых предприятием.
44024. Создание и публикация веб-узла учебного заведения 990.5 KB
  Принципы функционирования дистанционного обучения. Технология обучения в системе дистанционного образования ДО. Информационные службы Интернета. Очень ценно что доступ к Internet–учебнику возможен с любой машины подключенной к сети Internet что позволяет при наличии интереса со стороны пользователей попробовать освоить какой либо курс дистанционного обучения. Дистанционное обучение ДО является формой получения образования наряду с очной и заочной при которой в образовательном процессе используются лучшие традиционные и...
44025. Организация расчетно-кассового обслуживания населения филиалом 614 ОАО «АСБ Беларусбанк» 1006 KB
  Первые попытки теоретически осмыслить природу денег были сделаны еще выдающимися мыслителями древности Ксенофонтом Платоном и в частности Аристотелем которого по праву считают родоначальником экономической науки в том числе науки о деньгах. Во многом сложность денег объясняется их необычным свойством быть воплощением способности товаров подвергаться обмену различным образом проявляющейся в разные исторические эпохи. Такой эквивалентной формой становится денежный товар функционирующий в...
44026. Учет, анализ денежных средств и управление денежными потоками ООО «транстехсервис» 777.5 KB
  Хозяйственные связи - необходимое условие деятельности предприятий, так как они обеспечивают бесперебойность снабжения, непрерывность процесса производства и своевременность отгрузки и реализации продукции. Оформляются и закрепляются хозяйственные связи договорами, согласно которым одно предприятие выступает поставщиком товарно-материальных ценностей, работ или услуг, а другое - их покупателем, потребителем, а значит, и плательщиком.