Системное программное обеспечение Лекция № 10 «Драйверы устройств в MS Windows» Структура Windows NT Внутренний мир Windows NT (здесь и далее под Windows NT имеется в виду вся линейка - NT4, 2000, XP, 2003 и т.д.) разделен на две части с четко обозначенными границами, как в плане адресного пространства, так и в плане прав и обязанностей кода в этом адресном пространстве выполняющегося. С разделением адресного пространства все на удивление просто. Все четыре, доступных в 32-х разрядной архитектуре, гигабайта виртуальной памяти разделены на две равные части. Нижняя половина отдана процессам пользовательского режима, верхняя принадлежит ядру. С разделением прав и обязанностей немного сложнее. Структура Windows NT К пользовательским относятся следующие процессы: • Процессы поддержки системы (System Support Processes) например, процесс входа в систему Winlogon (реализован в \%SystemRoot%\System32\Winlogon.exe); • Процессы сервисов (Service Processes) - например, спулер печати (реализован в \%SystemRoot%\System32\spoolsv.exe); • Пользовательские приложения (User Applications) - бывают пяти типов: Win32, Windows 3.1, MS-DOS, POSIX (обеспечивает совместимость UNIX-программ) и OS/2; • Подсистемы окружения (Environment Subsystems) - поддерживается три подсистемы окружения: Win32 (реализована в \%SystemRoot%\System32\Csrss.exe), \%SystemRoot%\System32\Psxss.exe), POSIX OS/2 (реализована (реализована \%SystemRoot%\System32\os2ss.exe). Начиная с Windows XP поддержка POSIX и OS/2 прекращена. в в Структура Windows NT Ядро состоит из следующих компонентов: • Исполнительная система (Executive) - управление памятью, процессами и потоками и др.; • Ядро (Kernel) прерываний и планирование исключений потоков, и др. диспетчеризация (реализовано в \%SystemRoot%\System32\Ntoskrnl.exe); • Драйверы устройств (Device Drivers) - драйверы аппаратных устройств, сетевые драйверы, драйверы файловых систем; • Уровень абстрагирования от оборудования (Hardware Abstraction Layer, HAL) - изолирует три вышеперечисленных компонента от различий между аппаратными архитектурами (реализован в \%SystemRoot%\System32\Hal.dll); Структура Windows NT • Подсистема поддержки окон и графики (Windowing And Graphics System) - функции графического пользовательского интерфейса (Graphic User Interface, \%SystemRoot%\System32\Win32k.sys). GUI) (реализована в Структура Windows NT Хотя процессоры семейства Intel x86 поддерживают четыре уровня привилегий (называемых кольцами защиты), в Windows используются только два: • 0-ой для режима ядра • 3-ий для режима пользователя. Это связано с поддержкой других RISC (Reduced Instruction Set Computing — вычисления с сокращённым набором команд) процессоров (alpha, mips – RISC процессоры фирм DEC и MIPS Technologies), в которых реализовано только два уровня привилегий. Windows NT4.0 поддерживала эти архитектуры, но начиная с Windows 2000 осталась поддержка только x86. Структура Windows NT Процессы пользовательского режима: • имеют свои защищенные адресные пространства, • потоки этих процессов выполняются в непривилегированном режиме процессора (называемом пользовательским или третьим кольцом защиты), • не могут выполнять привилегированные команды процессора, • имеют ограниченный и опосредованный доступ к системным данным и к системному адресному пространству, • не имеют прямого доступа к оборудованию. Процессы пользовательского режима рассматриваются как потенциально опасные с точки зрения стабильности системы. Их права ограничиваются. И всяческие попытки выйти за пределы этих ограничений жестко пресекаются. Структура Windows NT Правда, в процессе своей работы, потоки этих процессов, вызывая системные сервисы, переходят в режим ядра, но в этом случае полностью теряют контроль над своим выполнением до возвращения обратно в режим пользователя. Компоненты ядра: • разделяют единое адресное пространство (верхние два гигабайта), • выполняются в привилегированном режиме процессора (называемом режимом ядра или нулевым кольцом защиты), • могут выполнять все, в том числе и привилегированные команды процессора, • имеют неограниченный и прямой доступ к системным данным и коду, • имеют прямой, или через HAL, доступ к оборудованию. Структура Windows NT Код ядра (собственно это и есть сама система) рассматривается как полностью доверяемый (т.е. этому коду доверено делать загруженным в все, что системное он пожелает). адресное Поэтому, пространство, будучи драйвер становится частью системы и на него не накладываются какие-либо ограничения. Таким образом пользовательские приложения отделены от собственно операционной системы. Если поставить целью написать сколь-нибудь серьезное приложение, для работы которого необходим доступ к внутренним функциям или структурам данных системы, то возникнет множество ограничений, преодолеть которые можно только разместив свой код в системном адресном пространстве. Драйверы устройств в Windows NT Из документированных возможностей существует только один способ это сделать - установить драйвер устройства. Способ этот относительно прост, надежен, а главное, полностью обеспечен поддержкой со стороны самой операционной системы. С момента своего появления до сегодняшнего дня концепция драйвера беспрерывно эволюционировала и процесс этот до сих пор не закончился. Один из моментов эволюции концепции драйвера — это эволюция драйвера как легко заменяемой части операционной системы. Как отдельный и довольно независимый модуль драйвер сформировался не сразу. Да и сейчас многие драйверы практически неотделимы от операционной системы. Драйверы устройств в Windows NT Во многих случаях это приводит к необходимости: • переустановки системы (DOS, Windows) • пересборки ее ядра (в UNIX-системах). Такое же различие есть и между ветками операционной системы Windows: • Windows 9х • Windows NT. В первом происходит случае практически процесс всегда работы как с с драйверами отдельными "кирпичиками«. Во втором дела обстоят намного хуже: множество (если не большинство) драйверов "вшито" в ядро. Драйверы устройств в Windows NT Список основных общих моментов в концепциях драйверов в Windows- и DOS-системах выглядит так: • способ работы с драйверами как файлами (функции, используемые при взаимодействии с файлами, практически идентичны таковым при взаимодействии с драйверами (имеется в виду лексически): open, close, read и т. д.); • драйвер, как легко заменяемая часть ОС; • идентичность механизма IOCTL (Input/Output Control Code, код управления вводом/выводом) — запросов. Драйверы устройств в Windows NT Windows NT поддерживает множество типов драйверов устройств: драйверы пользовательского режима (User-Mode Drivers): драйверы виртуальных устройств (Virtual Device Drivers, VDD) — используются для поддержки программ MS-DOS; драйверы принтеров (Printer Drivers); драйверы режима ядра (Kernel-Mode Drivers): драйверы файловой системы (File System Drivers) — осуществляют ввод/вывод на локальные и сетевые диски; унаследованные драйверы (Legacy Drivers) — написаны для предыдущих версий Windows NT; драйверы видеоадаптеров графических операций; (Video Drivers) — для Драйверы устройств в Windows NT драйверы потоковых устройств (Streaming Drivers) — осуществляют ввод/вывод потокового видео и звука; WDM-драйверы (Windows Driver Model) — поддерживают технологию Plug and Play и управления электропитанием. Модель пришла на смену предыдущей среде разработки драйверов VxD (virtual device driver) для Windows 9x. Далее стоит отметить, что драйверы бывают одно- и многоуровневыми. Если драйвер является многоуровневым, то обработка запросов ввода/вывода распределяется между несколькими драйверами, каждый из которых выполняет свою часть работы. Между этими драйверами можно "поставить" любое количество фильтр-драйверов (filter-drivers). Драйверы устройств в Windows NT При обработке запроса данные идут от вышестоящих драйверов (higher-level driver) к нижестоящим (lower-level driver), а при возврате результатов — наоборот. Ну и, понятно, одноуровневый (monolithic) драйвер просто является противоположностью многоуровневому. Для технологии Plug and Play существуют три уровня-типа драйверов: • шинные драйверы; • фильтр-драйверы; • функциональные драйверы. На низшей ступени находится шинный драйвер, выше него — функциональный драйвер. Между и над ними находится определенное количество фильтр-драйверов. Драйверы устройств в Windows NT Поскольку, в большинстве случаев, имеется всего один процессор, а приложений, которые нужно выполнять много, то естественно, что для создания иллюзии одновременного их выполнения надо последовательно подключать эти приложения к процессору, причем очень быстро. Эта процедура называется переключением контекста потока (thread context switching). Так как переключение контекста операция не самая быстрая, то драйверы, по соображениям лучшей производительности, как правило, не создают своих потоков. Но код драйвера все же нужно выполнять. Поэтому, для экономии времени на переключение контекстов, драйверы выполняются в режиме ядра в одном из трех контекстов: • в контексте пользовательского потока, инициировавшего запрос ввода-вывода; Драйверы устройств в Windows NT • в контексте системного потока режима ядра (эти потоки принадлежат процессу System); • как результат прерывания (а значит, в контексте потока, который был текущим на момент прерывания). Windows NT использует схему приоритетов прерываний, известную под названием уровни запросов прерываний (interrupt request levels, IRQL). Всего существует 32 уровня, с 0 (passive), имеющего самый низкий приоритет, по 31 (high), имеющего самый высокий. Причем, прерывания с IRQL=0 по IRQL=2 являются программными, а прерывания с IRQL=3 по IRQL=31 являются аппаратными. Прерывание с уровнем IRQL=0, строго говоря, прерыванием не является, т.к. оно не может прервать работу никакого кода На этом IRQL выполняются потоки пользовательского режима. Драйверы устройств в Windows NT Одновременно с операционной системой Windows Vista компания Microsoft выпустила и новую драйверную модель WDF (Windows Driver Foundation). В разработку этой модели положены следующие принципы, многие из которых уже реализованы: • Новая драйверная модель должна быть простой и гибкой. Простой — для облегчения процесса написания драйверов, гибкой — для быстрой "адаптации" к новым возможностям системы. • Драйверная модель не должна зависеть от основных компонентов ОС (их изменение, добавление и т. д. не должно "тянуть" за собой проблемы и/или вынужденные изменения при разработке драйверов). Драйверы устройств в Windows NT • Драйверная модель должна поддерживать версионность, т. е. один исполняемый файл драйвера должен работать на разных версиях ОС. • Драйверная модель должна быть легко расширяемой. • Драйверная модель должна позволять большинству драйверов успешно работать в пользовательском режиме. • Драйверная модель должна поддерживать написание драйверов на языках высокого уровня. • Ну и, наконец, драйверная модель должна уметь предоставлять для каждого драйвера отдельное так называемое "защищенное окружение" (protected environment), или, иными словами, уметь изолировать драйвер (driver isolation). Драйверы устройств в Windows NT Проект WDF от Microsoft состоит из 3 частей: • среда для написания драйверов режима ядра (Kernel-Mode Driver Framework (KMDF)); • среда для написания драйверов пользовательского режима (User-Mode Driver Framework (UMDF)); • инструменты для проверки и отладки драйверов. Пожалуй, самое главное в WDF — то, что эта модель в полной мере поддерживает объектно- ориентированное программирование (наборы событий, свойства и т. д. (объектная модель WDF)). Драйверы устройств в Windows NT Инструментарий для разработки драйверов для Windows: 1. Microsoft DDK (Driver Development Kit). В DDK не входит полноценная интегрированная среда разработки (Integrated Development Environment, IDE) – основный инструмент. 2. Есть пакеты разработки драйверов и от третьих фирм: WinDriver или NuMega Driver Studio, например. Но у них есть отличия базиса функций Microsoft (порой довольно большие) и масса других мелких неудобств. Так что DDK — лучший вариант. 3. Для написания драйверов с использованием новейших технологий и нововведений Microsoft — априори KMDF и UMDF. 4. Если же писать драйверы исключительно на ассемблере, то подойдет KmdKit (KerneiMode Driver Development Kit) для MASM32. Правда, этот пакет только для Windows 2000/ХР. Структура простейшего драйвера режима ядра в Windows NT DDK предназначен для разработки драйверов на языке C. Здесь речь пойдет о разработке драйвера режима ядра на ассемблере. А программисту на ассемблере без DDK также не обойтись: во-первых, понадобится документация, во-вторых, библиотечные файлы, в-третьих, заголовочные файлы (правда их придется переводить на синтаксис понятный ассемблеру), в-четвертых, конечно же, примеры исходных кодов драйверов (исходники, разумеется, написаны на C). Самые необходимые определения констант и структур в синтаксисе ассемблера masm, содержащие их включаемые файлы, а также многое другое, можно найти в KmdKit. Структура простейшего драйвера режима ядра в Windows NT Фактически драйвер можно представить как довольно-таки обычную DLL-библиотеку уровня ядра. Таким образом, далее можно представить драйвер просто как набор процедур, периодически вызываемых внешними программами. Несмотря на то, что процедуры драйверов для разных устройств сильно отличаются, есть общая структура и общие функции для всех драйверов. Главные из них составляют так называемый "скелет", на основе которого строится любой драйвер — каким бы сложным он ни был. Структура простейшего драйвера режима ядра в Windows NT Как и у любого выполнимого модуля, у драйвера должна быть точка входа, на которую система передаст управление после загрузки драйвера в память. Как и полагается, в программе на ассемблере точкой входа является первая инструкция, обозначенная меткой указанной в директиве end, и это функция инициализации драйвера: DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING . . . DriverEntry endp DriverEntry — ключевая функция драйвера. Ее главные задачи — произвести все необходимые действия по инициализации и определить точки входа для остальных функций драйвера. Эта функция вызывается при загрузке драйвера. Вышеприведенный код — прототип этой функции. Структура простейшего драйвера режима ядра в Windows NT Как видно, она принимает два аргумента — два указателя. Первый аргумент — указатель на PDRIVER_OBJECT. указатели на Он позволяет функции объект DriverObject функции DriverEntry типа определить Dispatch, Startio, а также на функцию выгрузки драйвера в объекте драйвера. Windows является объектно-ориентированной системой. Поэтому, понятие объект распространяется на все, что только можно, и что нельзя тоже. И драйверы не являются исключением. Загружая драйвер, система создает объект "драйвер" (driver object), представляющий для нее образ драйвера в памяти. Через этот объект система управляет драйвером. Звучит красиво, но не дает никакого представления о том, что же в действительности происходит. Структура простейшего драйвера режима ядра в Windows NT Если отбросить всю эту объектно-ориентированную мишуру, то станет очевидно, что объект "драйвер" представляет собой обыкновенную структуру данных типа DRIVER_OBJECT (определена в \include\w2k\ntddk.inc). Некоторые поля этой структуры заполняет система, некоторые придется заполнять программисту. Обращаясь к этой структуре, система и управляет драйвером. Аргумент RegistryPath передает функции DriverEntry указатель на раздел реестра с параметрами инициализации драйвера. При выходе из функции надо вернуть системе некое значение, указывающее на то, как прошла инициализация драйвера. Если вернуть STATUS_SUCCESS, то инициализация считается успешной, и драйвер остается в памяти. Любое другое значение указывает на ошибку, и в этом случае драйвер выгружается системой. Структура простейшего драйвера режима ядра в Windows NT Каждый драйвер имеет хотя бы одну процедуру Dispatch: DispatchRoutine proc stdcall pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP . . . DispatchRoutine endp pDeviceObject - указатель на объект "устройство" (структура DEVICE_OBJECT). Если драйвер обслуживает несколько устройств, то по параметру определяется, к какому устройству пришел запрос. pIrp - указатель на пакет запроса ввода-вывода (структура IRP). Диспетчер ввода-вывода создает IRP. Через pIrp драйверу передается указатель на IRP. Получив IRP, драйвер выполняет указанную операцию и возвращает его диспетчеру, чтобы тот, либо завершил операцию, либо передал пакет другому драйверу для дальнейшей обработки. Завершать IRP или передавать его дальше решает сам драйвер. Структура простейшего драйвера режима ядра в Windows NT Такой унифицированный диспетчеризации позволяет интерфейс диспетчеру процедур ввода-вывода вызывать любой драйвер, ничего не зная о его структуре и внутреннем устройстве, а также о содержании запроса ввода-вывода. Если драйвер устройства не может завершить все возможные запросы ввода/вывода в его Dispatch-процедуре, он должен иметь процедуру StartIo: StartIo proc stdcall pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP . . . StartIo endp Структура простейшего драйвера режима ядра в Windows NT Драйвер должен иметь процедуру DriverUnload, если он может быть выгружен в процессе работы системы: DriverUnload proc pDriverObject:PDRIVER_OBJECT . . . DriverUnload endp Здесь, конечно, перечислено далеко не все и не полностью (на это есть справочники), а только необходимая база, минимально достаточная для понимания примера простейшего драйвера. Пример. Драйвер beeper – звук из системного динамика Задача этого драйвера, сгенерировать на системном динамике звук определенной частоты и завершить свою работу. Структура простейшего драйвера режима ядра в Windows NT .386 .model flat option casemap:none include \masm32\include\w2k\ntstatus.inc include \masm32\include\w2k\ntddk.inc include \masm32\include\w2k\hal.inc includelib \masm32\lib\w2k\hal.lib .code DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING cli mov al, 10110110b out 43h, al mov eax, 7FFFH out 42h, al mov al, ah out 42h, al in al, 61h or al, 11b out 61h, al sti mov ecx, 1800000h delay: loop delay cli in al, 61h and al, 11111100b out 61h, al sti mov eax, STATUS_DEVICE_CONFIGURATION_ERROR ret DriverEntry endp end DriverEntry Структура простейшего драйвера режима ядра в Windows NT Драйвер использует in и out, обращаясь к соответствующим портам ввода-вывода. Общеизвестно, что доступ к портам вводавывода - это свято охраняемый Windows NT системный ресурс. Попытка обращения к любому из них, как на ввод, так и на вывод, из режима пользователя, неизбежно приводит к завершению приложения. Но, на самом деле, есть способ обойти и это ограничение, т.е. обращаться к портам ввода-вывода прямо из третьего кольца. Правда, для этого, опять таки, нужен драйвер. В листинге программы использован упрощенный вариант описания сегментов (.code, .data, .stack) на основе стандартных моделей памяти, задаваемых директивой .model. В этом случае директивы SEGMENT, ENDS, ASSUME не нужны. В данном случае используется модель сплошной памяти flat (только для Win32). Структура простейшего драйвера режима ядра в Windows NT Для разработки программы для модели flat перед директивой model flat следует разместить одну из следующих директив: .386, .486, .586 или . 686, указывающих тип процессора. Директива option нужна для задания некоторых настроек компиляции. В данном случае опция casemap задаёт чувствительность к регистру символов. Здесь указано значение none, тем самым установлена чувствительность к регистру символов. Это надо, для того чтобы избежать конфликтов включаемых файлов от разных авторов.