Реферат Теория разработки драйверов
Работа добавлена на сайт bukvasha.net: 2015-10-28Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
от 25%
договор
Введение.
Драйверная концепция – неотъемлемая часть современных операционных систем. Эта концепция – основа взаимодействия системы (пользователя) с какими бы то ни было устройствами (системными/периферийными, реальными/виртуальными и т.д.). Даже системные программисты далеко не всегда имеют представление об этой концепции, о принципах ее работы, о программировании с использованием этой концепции. А ведь системное программирование – ключ к пониманию основ IT.
Написание драйверов – достаточно сложная, но, тем не менее, очень интересная и актуальная отрасль программирования. Знание особенностей технологии написания драйверов открывает огромное количество возможностей – написание драйверов для устройств, уже не поддерживаемых производителем, для устройств, драйвера к которым еще не написаны, исправление ошибок в драйверах, написание драйверов к различным промышленным устройствам и т.д.
У каждой операционной системы есть свои особенности, отсюда вытекает своя специфика написания драйверов под них. То же самое можно сказать и о разных типах оборудования.
Разработкой драйверов обычно занимаются профессионалы, каждая крупная компания, выпускающая технику имеет целый штат сотрудников, занимающихся разработкой драйверов. Прежде всего, разработчик драйвера должен владеть программированием на языке С (без расширений С++), поскольку описание синтаксиса и применения конструкций этого языка не рассматриваются в данной книге вовсе. Во-вторых, разработчик драйверов, пусть начинающий, должен иметь твердо сформировавшееся представление о программировании в многозадачной среде при интенсивном использовании многопоточности.
Часть 1. Драйвера.
1.1 Основные понятия.
Драйвер – это часть кода операционной системы (небольшая программка не для пользователя, а для ОС), отвечающая за взаимодействие с аппаратурой. Под словом «аппаратура» можно подразумевать как реальные физические устройства, так и виртуальные и логические. Например, есть много разных, принтеров разных производителей. Чтобы на них печатать, нужно знать, какие команды этому принтеру нужно передавать. У разных производителей такие команды могут сильно отличаться друг от друга. Нужно как-то научить ОС работать со всеми типами принтеров – в этом помогают драйверы.
С момента своего появления и до сегодняшнего дня драйвер непрерывно эволюционировал, и процесс этот продолжается до сих пор. Один из моментов эволюции драйвера – это эволюция концепции драйвера, как легко заменяемой части операционной системы. Как отдельный и довольно независимый модуль драйвер сформировался не сразу, да и сейчас многие драйверы практически неотделимы от операционной системы. Во многих случаях это приводит к необходимости переустановки системы (ОС Windows) или переборки ее ядра (в UNIX-системах).
Список основных общих концепций драйверов в Windows и UNIX-системах выглядит так:
1) Способ работы с драйверами как файлами. Т.е. функции, используемые при взаимодействии с файлами, практически идентичны таковым при взаимодействии с драйверами (лексически): open, close, read и т.д.
2) Драйвер, как легко заменяемая часть ОС
3) Существование режима ядра
Классификация типов драйверов для ОС Windows:
1. Драйверы пользовательского режима (User-Mode Drivers):
- Драйверы виртуальных устройств (Virtual Device Drivers, VDD) – используются для поддержки программ MS-DOS;
- Драйверы принтеров (Printer Drivers);
2. Драйверы режима ядра (Kernel-Mode Drivers):
- Драйверы файловой системы (File System Drivers) – осуществляют ввод/вывод на локальные и сетевые диски
- Унаследованные драйверы (Legacy Drivers) – написаны для предыдущих версий Windows
- Драйверы видеоадаптеров (Video Drivers) – реализуют графические операции
- Драйверы потоков устройств (Streaming Drivers) – осуществляют ввод/вывод потоков видео и звука
- WDM-драйверы (Windows Driver Model) – поддерживают технологию Plug and Play и управления электропитанием.
Драйверы бывают одно- и многоуровневыми. Если драйвер является многоуровневым, то обработка запросов ввода/вывода распределяется между несколькими драйверами, каждый из которых выполняет свою часть работы. Между этими драйверами можно поставить любое количество фильтр-драйверов (filter-drivers). При обработке запроса данные идут от вышестоящих драйверов к нижестоящим, а при возврате – наоборот. Одноуровневый драйвер (monolithic) драйвер является противоположностью многоуровневому.
Для технологии Plug and Play существуют три уровня-типа драйверов:
- шинные драйверы
- фильтр-драйверы
- функциональные драйверы
Системное ПО, поддерживающее Plug and Play предоставляет следующие возможности:
- автоматическое распознавание подключенных к системе устройств
- распределение и перераспределение ресурсов (таких, как порты ввода/вывода и участки памяти) между запросившими их устройствами
- загрузка необходимых драйверов
- предоставление драйверам необходимого интерфейса для взаимодействия с технологией Plug and Play
- реализация механизма, позволяющего драйверам и приложениям получать информацию касаемо изменений в наборе устройств, подключенных к системе устройств, и совершить необходимые действия
- предоставление драйверам необходимого интерфейса для взаимодействия с технологией Plug and Play
- реализация механизма, позволяющего драйверам и приложениям получать информацию касаемо изменений в наборе устройств, подключенных к системе устройств, и совершать необходимые действия.
1.2. Инструментарий.
Существует много утилит, которые могут понадобиться при разработке драйверов. Основным средством разработки является Microsoft Windows DDK, Device Driver Kit, — пакет разработки драйверов, включающий компилятор, редактор связей (линкер), заголовочные файлы, библиотеки, большой набор примеров (часть из которых является драйверами, реально работающими в операционной системе) и, разумеется, документацию. В состав пакета входит также отладчик WinDbg, позволяющий проводить интерактивную отладку драйвера на двухкомпьютерной конфигурации и при наличии файлов отладочных идентификаторов операционной системы WinDbg кроме того, позволяет просматривать файлы дампа (образа) памяти, полученного при фатальных сбоях операционной системы (так называемый crash dump file).
Языком программирования, который используется в DDK является язык С, допускающий вставки на языке ассемблер, который в былые времена был основным и единственным языком программирования драйверов.
В бесплатно распространяемом пакете DDK всегда отсутствовала интегрированная среда разработки. Поэтому есть необходимость использовать Visual Studio в качестве средства редактирования исходного кода. При должной настройке этой среды процесс выявлений синтаксических ошибок существенно облегчается — неотъемлемое преимущество интегрированных сред программирования. Компилятор и редактор связей Visual Studio C++ создают нормальный бинарный код, вполне работоспособный при указании соответствующих опций (настроек) компиляции, однако эталоном следует считать бинарный код, получающийся при компиляции кода драйвера с использованием утилиты Build из состава пакета DDK. Разумеется, встроенный интерактивный отладчик Visual Studio и прилагаемая документация становятся для разработки драйвера совершенно бесполезными, поскольку не предназначены для работы с программным обеспечением для режима ядра.
Есть пакеты разработки драйверов и от других фирм: WinDriver или NuMega Driver Studio, но у них есть отличия базиса функций Microsoft (порой довольно большие) и масса других мелких неудобств.
Так же необходимо использовать некоторые сторонние утилиты, такие как: редактор реестра, мониторы обращений к реестру и файлам, средство просмотра каталогов имен объектов, программы просмотра, сохранения и т.д. отладочных сообщений.
Что же касается написания драйверов под 64х битные системы, то очень полезными являются такие инструменты, как:
- анализатор PREfast, от Microsoft. Это статический анализатор кода, который обнаруживает некоторые классы ошибок, которые часто встречаются как в драйверах, так и в обычных C и C++ программах.
- статический анализатор кода общего назначения Gimpel Software PC-lint, который обнаруживает помимо некоторых из перечисленных ошибок, еще и огромное количество ошибок, встречающихся в обычных программах.
- статический анализатор кода Viva64 предназначен для поиска ошибок в C++ программах, проявляющихся при переносе кода с 32-битных на 64-битные системы.
Часть 2. Архитектура Windows.
Цели разработки.
Пять фундаментальных задач Windows последнего поколения:
- Совместимость. Операционная система должна поддерживать максимально возможное множество программного и аппаратного обеспечения.
- Переносимость. Операционная система должна функционировать на максимально возможном количестве имеющихся сейчас и ожидаемых в перспективе аппаратных платформ.
- Расширяемость. Поскольку требования рынков постоянно растут, операционная система должна уметь с легкостью расширять набор своих возможностей и перечень поддерживаемой аппаратуры при минимальном вмешательстве в свой внутренний код.
- Надежность и устойчивость. Операционная система должна быть стойкой к неумышленному или преднамеренному неправильному использованию компонентов. Пользовательские приложения не должны иметь возможности приведения системы к фатальным сбоям.
- Производительность. Операционная система должна обеспечивать хорошую производительность на всех поддерживаемых аппаратных платформах.
Разумеется, провозглашение и достижение цели не всегда есть одно и то же, и серьезные компромиссы общих и текущих задач оказываются неизбежными. Windows столь же подвержена компромиссам, как и все остальные операционные системы.
Уровни аппаратных привилегий в Windows.
Для достижения устойчивости в работе системы, разработчики Windows выбрали для построения ядра так называемую 'архитектуру клиент-сервер'. В данном случае, пользовательское приложение и является клиентом служб операционной системы.
Пользовательское приложение функционирует в специальном режиме (относительно аппаратного обеспечения), называемом 'user mode' — пользовательский режим. В пределах этого режима, код приложения ограничен выполнением "безвредных" инструкций. Например, через реализацию "таинственного" маппинга (mapping, отображение) виртуальной памяти (страничное представление виртуальной памяти) пользовательский код лишается возможности доступа к виртуальной памяти, предоставленной другим приложениям (за исключением случаев обоюдного согласия, что реализуется специально предназначенными на тот случай методами). Инструкции аппаратного ввода/вывода также не могут быть выполнены кодом пользовательского режима. Целый класс инструкций центрального процессора (называемых привилегированными) запрещен в Windows для выполнения кодом пользовательского режима, как, например, команды процессора IN, OUT. Если вдруг приложению потребуется выполнить что-нибудь из числа таких запрещенных для нее действий, оно должно запросить соответствующую службу операционной системы.
Код самой операционной системы выполняется в так называемом 'kernel mode' — режиме ядра (режиме уровня ядра). Код режима ядра вправе выполнить любую процессорную инструкцию, не исключая инструкций ввода/вывода. Память, принадлежащая любому приложению, может быть доступна коду режима ядра, конечно, если страничная память приложения в данный момент не сброшена на жесткий диск.
Современные процессоры реализуют несколько форм привилегированного режима в отличие от непривилегированного. Код режима ядра выполняется в привилегированном контексте, в то время как пользовательский код выполняется в непривилегированной среде. Так как разные процессоры (и платформы на их основе) реализуют привилегированные режимы по-разному, то, для обеспечения переносимости, разработчики операционной системы ввели особые абстрактные элементы программирования, которые позволяют разграничивать пользовательский режим и режим ядра. Код операционной системы использует их для переключения привилегированного/непривилегированного контекста, следовательно, при перенесении операционной системы только лишь код этих дополнительных элементов необходимо "портировать" (переписывать конкретно под специфику новой аппаратной платформы). На платформе Intel пользовательский режим реализуется из набора инструкций Ring 3 (Кольца 3), в то время как режим ядра реализован с использованием Ring 0 (Кольца 0).
Ring 3 – это наименее привилегированное кольцо, в котором есть множество ограничений по работе с памятью, устройствами и т.д. Например, в третьем кольце приложения не могут видеть адресное пространство других приложений без особого на то разрешения, выполнять привилегированные команды процессора, напрямую общаться к оборудованию и т.д.
Режим ядра (защищенный режим) – это основной режим работы процессора (32-разрядного). Главные механизмы, регулируемые режимом ядра:
- механизм защиты памяти и ввода/вывода, состоящий из 4 уровней
- механизм переключения задач (любая задача имеет состояние – состояние всех регистров процессора, с ней связанных. Это состояние хранится на сегментах)
- особая организация памяти. При этой организации памяти используется 2 способа ее преобразования: разбивка на страницы и сегментация (сегмент – это отдельный блок общего пространства памяти, в 32-разрядной адресации максимальный размер сегмента – 4 Гбайт, а максимальное количество сегментов 8192). Сегментация обеспечивает неплохую защиту данных. Страничный же способ организации помогает использовать большее количество памяти, чем сегментация. Базируется она также на 32-разрядной адресации, но в качестве базового объекта использует отдельный блок памяти размером 4 Кбайт.
- механизм защиты из 4 уровней
Драйверы уровня ядра (режима ядра) работают в привилегированном контексте. Соответственно, плохо написанный драйверный код может оказаться вредоносным для операционной системы. Разработчик должен с особым вниманием относиться к создаваемому коду, чтобы не обрушить все здание операционной системы. Фирма Microsoft пытается решить проблему надежности драйверов, поставляемых в составе дистрибутива Windows, через механизм тестирования и подписания драйверов.
Переносимость.
В качестве способа решения задачи переносимости конструкторы Windows выбрали многослойную архитектуру.
| |
Слой аппаратных абстракций (Hardware Abstraction Layer, HAL) изолирует процессорные и платформенные особенности от кода операционной системы. Его услугами Microsoft предлагает пользоваться и разработчику драйверного кода. Вполне возможно так написать драйвер, что для перенесения его на другую платформу потребуется разве что перекомпилировать его. Как можно это сделать, если изначально драйвер есть такая программная единица, которая жестко привязана и к своему устройству, и к конкретному процессору, и к конкретной платформе?! Просто драйвер должен обратиться к использованию средств уровня HAL для взаимодействия с аппаратными регистрами и аппаратной поддержкой шин. В отдельных случаях разработчик драйвера может опереться на код, предоставляемый Диспетчером ввода/вывода, для работы с совместно используемыми аппаратными ресурсами. Например, при DMA операциях (прямого доступа к памяти) используется такая абстракция программирования, как объект адаптера
Расширяемость.
Ядро несет ответственность за планировку активности программных потоков (threads). Поток является всего лишь "независимой тропинкой" в выполнении программного кода. Чтобы сохранить независимость от деятельности других потоков, для каждого из них необходимо сохранять уникальный потоковый контекст (thread context). Потоковый контекст состоит из состояния регистров процессора (включая также изолированный стек и счетчик инструкций, Program Counter), сохраненного ID (идентификатора потока, так называемого Thread ID или TID), значения приоритета, распределения памяти, связанной с потоком (Thread Local Storage), и другой информации, имеющей отношение к данному потоку.
Обязанностью планировщика потоков является определение, какой поток должен выполняться в данный момент. В среде с единственным процессором, в каждый момент времени только один поток получает в свое распоряжение процессор. В многопроцессорной конфигурации разные потоки могут выполняться на разных процессорах, реализуя настоящую параллельность выполнения кода. Планировщик в большинстве случаев выделяет потоку процессор на фиксированный временной интервал, известный под названием thread time quantum (потоковый временной квант). Предоставление процессора происходит, главным образом, на основе величины приоритета потока.
Так как основной задачей ядра является управление потоками, работа по управлению памятью, вопросами доступа (security) и действиями по вводу/выводу возлагается на другие компоненты операционной системы. Эти компоненты известны под собирательным названием 'Executive', Исполнительные Компоненты. Они сконструированы как модульное программное обеспечение (хотя, Диспетчер ввода/вывода сам является существенным исключением из этого правила).
Идея поддержания ядра как "маленького и чистого", при сохранении модульности исполнительных компонентов, обеспечивает основу заявления Microsoft o сохранении курса на расширяемость. По крайней мере, следует признать, что эта операционная система выдержала более десяти лет переработок и регулировок, значительно улучшив свои показатели.
Производительность.
Поскольку многие участники сложных программных проектов хорошо знакомы с такой особенностью многослойного программного обеспечения как его "невзрачная" производительность, то особое внимание со стороны разработчиков Windows было уделено быстрому межслойному взаимодействию.
Во-первых, все слои, обсуждаемые далее, выполняются в одном аппаратном режиме — режиме ядра. Следовательно, межслойные вызовы не используют ничего сложнее, чем инструкция процессора CALL. Средства же, предоставляемые уровнем HAL, в основном, представляет собой макроопределения, являющиеся inline-включаемым кодом.
Во-вторых, разработчики предприняли много усилий, направленных на то, чтобы заставить работать параллельно максимально возможное число программных потоков исполнительных компонентов. Вспомогательные процедуры редко блокируются или находятся в состоянии ожидания, что минимизирует время простоя процессора.
Вопросы повышение производительности в значительной степени касаются и разработчиков драйверов. В момент, когда пользовательское приложение или системный поток посылает запрос к обслуживаемому драйвером устройству (точнее сказать, объекту устройства), жизненно важно, чтобы драйверный код не блокировал выполнения запроса. Если же запрос не может быть обработан немедленно (например, устройство занято или медленно работает), запрос должен быть поставлен в очередь для последующей обработки. Диспетчер ввода/вывода предоставляет необходимые для этого процедуры.
Часть 3. Архитектура WDM
WDM (Windows Driver Model) – это драйверная модель от Microsoft для ОС Windows, пришедшая на смену предыдущей среде написания драйверов для ОС Windows – VxD (Virtual Device Driver).
WDM в настоящий момент – одна из важнейших концепций в написании драйверов. Ее главные особенности:
- совместимость на уровне двоичных кодов между драйверами для систем Windows 98 и Windows NT
- поддержка управления питанием
- поддержка Plug and Play
- поддержка «продвинутого» шинного управления (advanced bus management)
«Жизненный цикл» среднестатистического WDM – драйвера:
1. Драйвер шины обнаруживает устройство.
2. Plug and Play Manager определяет местонахождение ключа устройства в ветке Enum реестра. Этот ключ содержит указатель на другой ключ реестра, определяющий функциональный драйвер (который управляет отдельным устройством и является основным драйвером устройства). Pnp-менеджер динамически загружает функциональный драйвер.
3. PnP-менеджер вызывает функцию драйвера AddDevice для того, чтобы создать DRIVER_OBJECT. Если драйвер соответствует больше, чем одному фактическому устройству, PnP-менеджер вызывает AddDevice для каждого из них. С этого момента вся коммуникация драйвера с внешним миром осуществляется с использованием IRP (I/O Request Packet) – пакетов.
4. PnP-менеджер выделяет все необходимые драйверу ресурсы ввода/вывода (запросы на прерывание, номера портов и т.д.) и посылает запрос для инициализации устройства.
5. Некоторые устройства могут быть удалены из системы без выключения компьютера. Если устройство – одно из таких, то PnP-менеджер посылает драйверу специальный IRP-пакет, в результате чего созданный функцией AddDevice объект устройства уничтожается.
6. Когда все устройства удалены, то менеджер ввода/вывода (I/O Manager) вызывает функцию DriverUnload, которая удаляет образ драйвера из памяти.
Часть 4. Структура драйвера.
Фактически драйвер можно представить как довольно-таки обычную DLL-библиотеку уровня ядра. Таким образом, далее можно представить драйвер просто как набор процедур, периодически вызываемых внешними программами. Несмотря на то, что процедуры драйверов для разных устройств сильно отличаются, есть общая структура и общие функции для всех драйверов.
DriverEntry – ключевая функция драйвера. Функция инициализации. Ее основные задачи – произвести все необходимые действия по инициализации и определить точки входа для остальных функций драйвера. Эта функция вызывается при загрузке драйвера.
NSTATUS
DriverEntry{
IN PDRIVER_OBJECT DriverOBject,
IN PUNICODE_STRING RegistryPath
};
Она принимает 2 аргумента: первый – указатель на объект DriverObject типа PDRIVER_OBJECT. Он позволяет функции DriverEntry определить указатели на функции Dispatch, AddDevice, StartIo, а также на функцию выгрузки драйвера в объекте драйвера. Аргумент RegistryPath передает функции DriverEntry указатель на Unicode строку, содержащую путь к ключу драйвера в реестре.
Каждый драйвер должен иметь, по крайней мере, одну процедуру Dispatch
NTSTATUS
XxxDispatchpnP{
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
};
Если драйвер устройства не может завершить все возможные запросы ввод/вывода
В его Dispatch-процедуре, он должен иметь либо процедуру StartIo, либо заводить одну или более внутренних очередей и управлять собственным механизмом отложенных запросов на прерывание.
VIOD
XxxStartIo{
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
};
В зависимости от уровня, занимаемого драйвером в сетке обработки запроса на прерывание, драйвер может обладать следующими процедурами.
Вдобавок к процедуре DriverEntry драйвер может иметь процедуру Reinitialize, вызываемую один или несколько раз в процессе загрузки системы после того, как DriverEntry вернет управление.
VOID
Reinitialize{
IN PDRIVER_OBJECT DriverObject,
IN PVOID Context,
IN ULONG Count
};
Любой драйвер физического устройства, который генерирует прерывания, должен иметь эту процедуру. Этот драйвер всегда самый низкий в стеке.
BOOLEAN
InterruptService{
IN PKINTERRUPT Iterrupt,
IN PVOID ServiceContext
};
Любой драйвер, имеющий ISR , должен иметь DpcForIsr или CustomDpc
OID
DpcForIsr{
IN PKDPC Dpc,
IN struct _DEVICE_OBJECT *DeviceObject,
IN struct _IRP *Irp,
IN PVOID Context
};
VOID
CustomDpc{
IN struct _KDPC *Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
};
Любой низкоуровневый драйвер устройства, данные которого или регистры сопряженного устройства могут изменяться в его ISR и других процедурах драйвера, должен иметь одну или более процедур SynchCritSection
BOOLEAN
SynchCritSection{
IN PVOID SynchronizeContext
};
Любой драйвер устройства, исользующий DMA, должен иметь процедуру AdapterControl. Любой драйвер устройства, который должен синхронизировать операции с физическим контроллером для нескольких устройств или каналов устройства, должен иметь ControllerControl.
IO_ALLOCATION_ACTION
AdapterControl{
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID MapRegisterBase,
IN PVOID Context
};
IO_ALLOCATION_ACTION
ControllerControl{
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID MapRegisterBase,
IN PVOID Context
};
Клавиатура, мышь, последовательный, параллельный, звуковой драйверы и драйвер файловой системы имеют процедуру Cancel. Любой драйвер, обрабатывающий запрос в течение длительного промежутка времени (когда пользователь может отменить операцию), должен иметь процедуру Cancel. Обычно эту процедуру имеет высший драйвер в стеке обработки запроса.
VOID Cancel{
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
};
Любой драйвер верхнего уровня, который создает запросы к более низкоуровневым драйверам, должен иметь, по крайней мере, одну процедуру IOComlpetion для освобождения всех структур IRP, созданных драйвером. Таким образом, любой драйвер высшего уровня должен иметь процедуру IOComlpetion. Другие процедуры драйвера могут сказать, чтобы IOComlpetion была вызвана, когда все низкоуровневые драйвера обработают текущий запрос.
NTSTATUS
IOComlpetion{
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
};
Для отслеживания времени, занимаемого процедурой ввода/вывода, или для некоторой другой цели, определяемой разработчиком, любой драйвер должен иметь процедуры IoTimer и/или CustomTimerDpc. IoTimer вызывается раз в секунду, когда драйвер включает таймер. Вторая же может быть вызвана в более мелкий или переменный интервал.
VOID
CustomTimerDpc{
IN struct_KDPC *Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
};
Драйвер должен иметь процедуру Unload, если он может быть выгружен во время работы системы.
VOID
XxxUnload{
IN PDRIVER_OBJECT DriverObject
};
Для осуществления обращений к микроядру, работы с реестром, памятью, объектами, синхронизацией и прочее существует набор функций, называющихся функциями поддержки ядра.
Функции IoCreateDevice создает новый объект устройства и инициализирует его для использования драйвером. Объект устройства представляет собой физическое, виртуальное или логическое устройство, которое необходимо драйверу для поддержки динамического управления этим устройством.
NTSTATUS
IoCreateDevice{
IN PDRIVER_OBJECT DriverObject, // указатель на объект драйвера
IN ULONG DeviceExtensionSize, // размер блока пользовательской
// информации в байтах
IN PUNICODE_STRING DeviceName,// имя устройства (иногда опускаются)
IN DEVICE_TYPE DeviceType, // тип устройства (последовательное,
// диск, мышь и т.п.)
IN ULONG DeviceCharacteristics, // параметры устройства
// (вынимаемое и пр.)
IN BOOLEAN Exclusive, // параллельность доступа к устр.
OUT PDEVICE_OBJECT *DeviceObject // указатель на объект
// создаваемого устройства
};
Функция IOCreateSymbolicLink создает символическую ссылку между устройством и видимым пользователем именем.
NTSTATUS
IOCreateSymbolicLink{
IN PUNICODE_STRING SymbolicLinkName, // символическое имя,
// видимое пользователем
IN PUNICODE_STRING DeviceName // имя устройства
// в пространстве имен Windows
};
Функция IoCompleteRequest объявляет менеджеру ввода/вывода, что обработка текущего запроса ввода/вывода закончена.
VOID
ioCompleteRequest{
IN PIRP Irp, // указатель на запрос ввода/вывода
IN CCHAR PriorityBoost // повышение приоритета драйвера для обработки
// запроса. Зависит от обрабатываемого устройства.
// IO_NO_INCREMENT при ошибке или очень быстрой обработке запроса
};
Часть 5. Драйвер для принтера
5.1. Архитектура печати в Windows
Самые главные компоненты этой архитектуры – это принтерный спулер и набор драйверов принтеров.
Спулер – это компонент архитектуры Windows, являющийся сервером печати, работающим с очередями печати. Он имеет клиент-серверную архитектуру.
К клиенту относятся:
- приложение, которому нужны услуги печати или какие-либо, связанные с ними, которые может ему предоставить спулер; все запросы приложение отправляет к GDI (это нижестоящий компонент)
- Winspool.drv – пользовательский интерфейс, предоставляемый спулером. Этот компонент находится еще ниже. Все компоненты в этом списке перечисляются от вышестоящих к нижестоящим.
Серверные компоненты:
- Spoolsv.exe – API-сервер спулера
- Spoolss.dll – это «роутер» спулера. Он разбирает поступающие к нему запросы и определяет, к какому провайдеру их нужно переадресовать.
Самый важный компонент – это провайдер печати – компонент, работающий с определенными для него локальными и удаленными устройствами печати. Также через провайдер печати можно производить различные действия с очередями печати.
Провайдеры печати бывают следующих типов:
- провайдер локальной печати; файл – localspl.dll
- провайдер сетевой печати; файл – win32spl.dll
- провайдер печати Novell NetWare; файл – win32spl.dll
- провайдер печати, работающий с HTTP; файл – inetpp.dll
Провайдер локальной печати.
Основное его назначение – управлять всеми ресурсами в системе, связанными с печатью:
- очередями печати
- драйвером принтера
- заданиями принтера
- портами
Все функции, определенные провайдером печати, делятся на:
- функции инициализации
- функции управления драйвером принтера, очередями печати, заданиями принтера, портами, реестром и т.д.
Кроме того, есть еще функция XcvData, обеспечивающая связь между серверной и клиентской DLL - библиотеками монитора порта.
Локальный провайдер печати, помимо поддержки стандартного набора функций, также должен поддерживать:
- архитектура драйвера принтера вместе в вызовами к DLL-библиотеке интерфейса локального принтера.
- архитектуру предоставляемого производителем процессора печати
- архитектуру предоставляемого производителем монитора порта
5.2 Драйвера принтеров
Существует несколько типов драйверов принтера Windows:
- Microsoft Universal Printer Driver – универсальный драйвер принтера
- Microsoft PostScript Printer Driver – драйвер для PostScript-принтера
- Microsoft Plotter Driver – драйвер для плоттера
Процесс создания универсального драйвера модно разбить на три компонента и реализовывать их отдельно:
- плагин для Microsoft Render
- мини-драйвер принтера
- монитор порта
Мини-драйвер принтера отвечает за предоставление информации о принтере для механизма рендеринга. Рендер перехватывает задания принтера, формирует из них растровые строки, а затем уже передает их спулеру. Информация о принтере ему нужна для того, чтобы корректно обработать задания принтера. Мини-драйвер принтера создается при помощи утилиты Unitool, входящей в состав DDK. Благодаря ей данные GPC (General Printer Charecterization) для одного или нескольких схожих растровых принтеров будут определены и организованы в мини-драйвере.
Плагин для Microsoft Render: существует зарегистрированная функция IPrintOemUni::FilterGraphics, которая получает доступ к сформированным растровым строкам перед их отправкой спулеру. Это дает возможность модифицировать строки перед отправкой: зашифровать, сжать и т.д.
Монитор порта (port monitor) – это часть архитектуры подсистемы печати Windows. Каждый монитор порта поддерживает стандартный набор API-функций. Спулер вызывает все эти функции по мере надобности.
LPMONITOREX
InitializePrintMonitor (LPWSTR pRegistryRoot);
Спулер вызывает эту функцию во время инициализации и получает от нее структуру, содержащую точки входа для остальных функций. У монитора порта есть только две точки точки входа. Одна из них находится в этой функции, а другая – в функции DllEntryPoint. Монитор порта экспортирует все функции в структуре, полученной спулером. Эта функция вызывается спулером в момент загрузки им монитора порта посредством функции Win32 API LoadLibrary. В остальных случаях играет роль точки входа для спулера для загрузки DLL-библиотеки в память и больше ничего не делает.
Спулер вызывает функцию OpenPort в момент назначения порта принтеру. Эта функция возвращает дескриптор порта в pName. Спулер использует возвращенный функцией дескриптор в последующих вызовах монитора порта: StartDocPort, WritePort, ReadPort и EndDocPort. Спулер ожидает завершения функции OpenPort (успеха или неудачи)приемлемое время. Все процедуры инициализации, которые может иметь монитор порта, выполняются в этой функции.
Обычно вызывает ClosePort, когда порту, указанному параметром hPort, больше не соответствует ни одного принтера.
Спулер вызывает функцию StartDocPort, когда он готов к отсылке задания на принтер.
Функция WritePort посылает данные, указанные в pBuffer, принтеру. Спулер вызывает эту функцию, если ему необходимо отослать полное задание на принтер. Спулер устанавливает размер блока в параметре cbBuf. Если от принтера нет отклика, WritePort будет ждать приемлемое время и, не получив ответа, вернет FALSE. Функция WritePort всегда должна проинициализировать pcbWritten нулем перед попыткой записи в порт. Если попытка записи в порт оказалась успешной, то в pcbWritten будет находиться число посланных байтов.
Функция ReadPort поддерживает принтеры, обеспечивающие двунаправленный обмен информацией. Если принтер ничего не предает, то ReadPort выждет положенное время, чтобы окончательно убедиться в отсутствии поступающих от принтера данных, и вернет FALSE. Функция ReadPort всегда должна проинициализировать pcbRead нулем перед попыткой принятия данных от принтера. Если чтение данных, принятых от принтера, оказалось успешным, то pcbRead будет содержать число переданных байтов.
Принтер вызывает функцию EndDocPort после того, как задание завершено. Мониторы должны вызвать Win32-функцию SetJob для того, чтобы проинформировать спулер о завершении работы. Функция монитора порта EndDocPort должна вызвать SetJob с параметром dwComand, установленным в JOB_CONTROL_SENT_TO_PRINTER. Когда работа принтера проходит через «языковый» монитор, спулер игнорирует любые оповещения, получаемые от монитора порта. Следовательно, монитор, который может определить реальное окончание работы принтера, должен задержать вызов SetJob до тех пор, пока принтер не уведомит об окончании работы. Для этой цели может использоваться функция EndDocPort. Language-монитор должен передать JOB_CONTROL_LAST_PAGE_ELECTED, когда он получает уведомление от принтера об окончании работы. Мониторам может понадобиться изменить это, если пользователь убирал или перезапускал задание. Для того, чтобы определить происхождение этого события, нужно вызвать Win32-функцию GetJob и проверить, не установлен ли статус работы в JOB_STATUS_DELETING или JOB_STATUS_RESTART. Функция EndDocPort также должна освободить все ресурсы, выделенные функцией StarDoc.
AddPort создает порт и добавляет его в список портов, поддерживаемых в настоящий момент указанным монитором в окружении спулера. AddPort разрешает интерактивное добавление портов. Монитор должен спросить пользователя об имени порта в диалоговом окне, ассоциированном с параметром hWnd. Функция AddPort должна проверить введенное имя порта путем вызова Win32-функции EnumPorts, которая проверяет, что в окружении спулера нет портов с идентичными номерами. Монитор должен также удостовериться в том, что порт входит в число поддерживаемых им.
Спулер вызывает функция DeletePort для удаления порта из окружения монитора. Монитор должен удалить указанный порт из своего состояния.
Спулер вызывает EnumPorts для получения списка портов, содержащихся в мониторе. Во время своей инициализации спулер вызывает EnumPorts для всех установленных мониторов порта, чтобы создать список доступных портов.
Спулер вызывает функцию ConfigurePort для выполнения конфигурирования порта. ConfigurePort может вывести диалоговое окно для получения от пользователя всей или только некоторой необходимой информации для конфигурирования принтера.
Функция SetPortTimeOuts необязательная, она устанавливает тайм-аут ожидания ответа порта.
Функция GetPrinterDataFromPort тоже необязательная, она получает данные принтера из порта.
Часть 6. Написание 64-битных драйверов
6.1. 64-битные ОС. Преимущества и недостатки.
64-битная ОС для своей работы требует использование 64-битного процессора. Большинство 64-битных систем могут выполнять 32-битное программное обеспечение в так называемом "режиме совместимости", который важен по причине того факта, что "родные" 64-битные приложения всё ещё встречаются довольно редко. Процессор при необходимости переключается в 32-битный режим. Запуск же 32-битной ОС на 64-битном CPU обычно приводит к тому, что процессор всё время работает в наследственном режиме. Если 64-битное программное обеспечение может на 64-битной ОС работать быстрее (если оно должным образом оптимизировано), то 32-битные приложения на 64-битных ОС обычно дают прежний уровень производительности.
Недостатком 64-битных вычислений является другая модель памяти, а также отсутствие 64-битных приложений в целом. Низкоуровневые компоненты, такие как драйверы, доступны не для всех устройств, с которыми вы планируете работать. Что касается поддержки 32-битного ввода/вывода в 64-битных драйверах, то подсистема WOW 64 позволяет 32-битным драйверам запускаться под 64-битной Windows. Но эта подсистема работает только для приложений, для драйверов эта система не работает. Почти все они не могут выполняться в 32-битном режиме совместимости.
Преимущества 64 бит.
- 32-битная версия Windows ограничена поддержкой максимум 4 Гбайт памяти, и даже при этом она не будет отдавать весь объём вашим приложениям - система Windows будет использовать часть памяти для собственных нужд, в результате вы получите 3 Гбайт или чуть больше. Поэтому максимальный объём памяти 32-битной Windows на самом деле ограничен 3+ Гбайт. 64-битная версия Windows будет поддерживать любой объём памяти, доступный сегодня.
- 4-битные ОС с большим количеством памяти лучше работают с большими файлами. Представьте себе 5-Гбайт файл под 32-битной версией Windows, где доступно всего 3 Гбайт памяти: системе придётся работать с файлом, загружая его в память по частям.
- Есть научные приложения, которые не дают достаточно точных результатов, если не получают достаточное количество битов в операциях с плавающей запятой. Они могут работать только в виде 64-битных приложений под 64-битной ОС.
6.2 Требования к драйверу
- драйвер не может быть модифицировать код ядра во время выполнения
- драйвер не может модифицировать такие таблицы, как IDT и GDT
- драйвер не может модифицировать недокументированные структуры ядра
- драйвер не может создавать и использовать свой собственный стек
Это главные правила. Теперь о соглашении о вызовах (Calling convention) 64-битных драйверов.
Существуют три типа СС: STDCALL, FASTCAL, CDECL.
CDECL – один из типов соглашения от вызовов, являющихся стандартным для программ на языках С и С++. Аргументы передаются через стек, справа налево, вызванная функция забирает аргументы из стека. Отличается от STDCALL, прежде всего тем, что генерирует исполняемые файлы заметно большего размера. Это происходит потому, что в CDECL любой вызывающий код должен включать в себя функциональность по очистке стека.
FASTCAL – характеризуется тем, что первые два аргумента передаются через регистры ECX и EDX. Остальные аргументы передаются через стек справа налево. Вызванная функция забирает аргументы из стека.
STDCALL – характеризуется тем, что вызывающая функция «кладет» параметры, передаваемые подпрограмме, в стек справа налево. Аргументы по умолчанию передаются по значению. Вызванная функция «забирает» свои параметры из стека.
Ближе всего СС 64-битных систем к FASTCALL. Главные отличия от последнего – это 64-битная адресация и наличие шестнадцати 64-битных регистров.
Биты, байты, слова и двойные слова остались прежними. Теперь у нас есть четвертные и восьмеричные слова – это Quadword для 64 бит, и Octalword – размером 128 байт.
Что касается поддержки 32-битного ввода/вывода в 64-битных драйверах, то подсистема WOW 64 позволяет 32-битным драйверам запускаться под 64-битной Windows. Но эта подсистема работает только для приложений, для драйверов эта система не работает.
Компиляторы 64-битных систем должны выполнять несколько дополнительных функций - обеспечить поддержку ANSI/ISO совместимости с языком С++, а кроме того, их компоновщики обязаны поддерживать оптимизацию так называемых «дальних переходов». Последняя опция позволяет компоновщику успешно работать с программами, регионы которых превышают 16 Мбайт.
Появились новые атрибуты, макросы и ключевые слова, используемые при программировании для 64-битных платформ. Написана новая 64-битная библиотека времени выполнения для языка С.
Ограничения 64-битного компилятора:
-компиляторы 64-битных платформ генерируют только низкоуровневый, «сырой» код
-больше не поддерживаются проверки безопасности
-если вы хотите использовать ассемблерный код, его необходимо внести в отдельный файл или же использовать встроенные функции.
6.3 Портирование 32-битных драйверов на 64-битную платформу
Цель и концепция нового стиля программирования в 64-разрядной версии Windows – максимально унифицировать процесс разработки приложений одновременно и под 32-битную, и под 64-битную версии Windows. Такой принцип предполагается обеспечить для написания драйверов. Определенные шаги в этом направлении уже сделаны.
Очень многие профессиональные С-программисты привыкли к тому, что целочисленный, long-типы и указатели одного размера (32 бита). Такой ситуации пришел конец – в новом 64-битном окружении типы не одинакового размера. Указатели приобрели длину 64 бита. Понятно, что это необходимо, т.к. возникла задача адресации объемов памяти больших 16 Тбайт. А изменять размеры стандартных типов данных пока что нет необходимости.
Еще одна деталь: в 64-битной ОС Windows не работает больше механизм автоматического устранения ошибок выравнивания памяти (в режиме ядра); поэтому перед переносом своего 32-битного драйвера на 64-битную платформу необходимо исправить все такие ошибки в его коде.
Новые типы данных. Они делятся на три группы: целочисленные с фиксированной точностью, целочисленные с точностью указателя и целочисленные со специальной точностью. Все эти типы выведены и стандартных типов с – int и double. Всем разработчикам предлагается работать с новыми типами данных и проверять работу написанного кода на обеих платформах – 32-битной и 64-битной.
Новые типы данных очень полезны для устойчивости, поэтому переход оправдан. Но необходимо пересматривать код на предмет небезопасных использований указателей и т.п.
Число -1 на 64-битной системе уже не равно 0xffffffff, как это было на 32-битной.
Оно равно 0xffffffffffffffff, в то время как 0xffffffff - это всего лишь 0x00000000ffffffff.
-1 – это очень важная цифра в программировании, она используется очень часто для индикации ошибки. Например, функция поиска буквы «а» в строке может возвращать номер буквы «а», если она в строке есть и -1, если нет.
Часть 7. Windows Driver Foundation
7.1 Новая драйверная модель
WDM (Windows Driver Model) – это старая драйверная модель. Ей на смену постепенно идет ЦВА – новая драйверная модель компании Microsoft, которая использует возможности новых аппаратных и программных средств, устраняет недостатки старой модели, облегчает процесс написания драйверов.
WDF работает только для следущих версий Windows:
- Microsoft Windows 7
- Microsoft Windows Vista
- Microsoft Windows Server 2003
- Microsoft Windows XP
- Microsoft Windows 2000
Особенности WDF:
- новая драйверная модель простая и гибкая. Простая – для облегчения процесса написания драйверов, гибкая – для быстрой «адаптации» к новым возможностям системы.
- драйверная модель не зависит от основных компонентов ОС (их изменение, добавление и т.д. не тянет за собой проблемы и/или вынужденные изменения при разработке драйверов)
- драйверная модель поддерживает версионность, т.е. один исполняемый файл работает на разных ОС
- драйверная модель легко расширяема
- драйверная модель позволяет большинству драйверов успешно работать в пользовательском режиме
- драйверная модель поддерживает написание драйверов на языках высшего уровня
- драйверная модель позволяет легко писать, анализировать, верифицировать и т.д. DDI
- драйверная модель умеет представлять для каждого драйвера отдельное называемое «защищенное окружение» (protected environment), или, уметь изолировать (driver isolation)
17.2 Особенности объектной модели WDF
- в этой модели объекты представляют собой «строительные блоки» для драйверов. Драйвер может менять эти блоки через специальные интерфейсы
- набор событий одинаково применим ко всем типам блоков. Среда располагает стандартными обработчиками для каждого события. Если драйверу необходимо самому обработать какое-либо событие, то он создает для этой цели специальную call-back процедуру
- объектная модель WDF предоставляет набор объектов, содержащих объекты, общие для любых драйверов – такие как устройства, очереди и т.д.
- у каждого объекта есть свойства (properties), которые определяют характеристики объекта. Для каждого свойства определен свой метод, работающий с ним
- все объекты собраны в иерархическую систему. В ее вершине стоит главный WDF-объект устройства, также являющийся родительским объектом по умолчанию для всех объектов, для которых таковой не указан. Практически для любого типа создаваемого дочернего объекта драйвер может указать нужный объект-родитель. Но некоторые типы объектов обладают неизменяемыми дефолтными объектами-родителями. Удаление родительского объекта автоматически приводит к удалению всех дочерних объектов.
Объекты KMDF – объекты режима ядра, они непрозрачны для драйвера, и он никогда не имеет прямого к ним доступа. Драйвер может выполнять какие-либо действия с объектом только при помощи указателя. Все объекты режима ядра уникальны. Ими невозможно управлять с помощью функций семейства ObXxx. Также ими невозможно управлять с помощью менеджера объектов Windows. Создание и управление этими объектами возможно только для самой среды и для WDF-драйверов.
Сущность UMDF-объектов – это COM. UMDF-объекты используют подмножество COM для реализации интерфейсов запросов и прочего. В драйверах пользовательского режима как драйвер, так и среда реализуют и предоставляют интерфейсы в стиле СОМ. Само собой, отпадает необходимость использования указателей.
Разных объектов UMDF меньше, чем KMDF: в пользовательском режиме многие действия (а значит, и объекты для работы с ними) запрещены.
Еще одна деталь: в 64-битной ОС Windows не работает больше механизм автоматического устранения ошибок выравнивания памяти (в режиме ядра); поэтому перед переносом своего 32-битного драйвера на 64-битную платформу необходимо исправить все такие ошибки в его коде.
Новые типы данных. Они делятся на три группы: целочисленные с фиксированной точностью, целочисленные с точностью указателя и целочисленные со специальной точностью. Все эти типы выведены и стандартных типов с – int и double. Всем разработчикам предлагается работать с новыми типами данных и проверять работу написанного кода на обеих платформах – 32-битной и 64-битной.
Новые типы данных очень полезны для устойчивости, поэтому переход оправдан. Но необходимо пересматривать код на предмет небезопасных использований указателей и т.п.
Число -1 на 64-битной системе уже не равно 0xffffffff, как это было на 32-битной.
Оно равно 0xffffffffffffffff, в то время как 0xffffffff - это всего лишь 0x00000000ffffffff.
-1 – это очень важная цифра в программировании, она используется очень часто для индикации ошибки. Например, функция поиска буквы «а» в строке может возвращать номер буквы «а», если она в строке есть и -1, если нет.
Часть 7. Windows Driver Foundation
7.1 Новая драйверная модель
WDM (Windows Driver Model) – это старая драйверная модель. Ей на смену постепенно идет ЦВА – новая драйверная модель компании Microsoft, которая использует возможности новых аппаратных и программных средств, устраняет недостатки старой модели, облегчает процесс написания драйверов.
WDF работает только для следущих версий Windows:
- Microsoft Windows 7
- Microsoft Windows Vista
- Microsoft Windows Server 2003
- Microsoft Windows XP
- Microsoft Windows 2000
Особенности WDF:
- новая драйверная модель простая и гибкая. Простая – для облегчения процесса написания драйверов, гибкая – для быстрой «адаптации» к новым возможностям системы.
- драйверная модель не зависит от основных компонентов ОС (их изменение, добавление и т.д. не тянет за собой проблемы и/или вынужденные изменения при разработке драйверов)
- драйверная модель поддерживает версионность, т.е. один исполняемый файл работает на разных ОС
- драйверная модель легко расширяема
- драйверная модель позволяет большинству драйверов успешно работать в пользовательском режиме
- драйверная модель поддерживает написание драйверов на языках высшего уровня
- драйверная модель позволяет легко писать, анализировать, верифицировать и т.д. DDI
- драйверная модель умеет представлять для каждого драйвера отдельное называемое «защищенное окружение» (protected environment), или, уметь изолировать (driver isolation)
17.2 Особенности объектной модели WDF
- в этой модели объекты представляют собой «строительные блоки» для драйверов. Драйвер может менять эти блоки через специальные интерфейсы
- набор событий одинаково применим ко всем типам блоков. Среда располагает стандартными обработчиками для каждого события. Если драйверу необходимо самому обработать какое-либо событие, то он создает для этой цели специальную call-back процедуру
- объектная модель WDF предоставляет набор объектов, содержащих объекты, общие для любых драйверов – такие как устройства, очереди и т.д.
- у каждого объекта есть свойства (properties), которые определяют характеристики объекта. Для каждого свойства определен свой метод, работающий с ним
- все объекты собраны в иерархическую систему. В ее вершине стоит главный WDF-объект устройства, также являющийся родительским объектом по умолчанию для всех объектов, для которых таковой не указан. Практически для любого типа создаваемого дочернего объекта драйвер может указать нужный объект-родитель. Но некоторые типы объектов обладают неизменяемыми дефолтными объектами-родителями. Удаление родительского объекта автоматически приводит к удалению всех дочерних объектов.
Объекты KMDF – объекты режима ядра, они непрозрачны для драйвера, и он никогда не имеет прямого к ним доступа. Драйвер может выполнять какие-либо действия с объектом только при помощи указателя. Все объекты режима ядра уникальны. Ими невозможно управлять с помощью функций семейства ObXxx. Также ими невозможно управлять с помощью менеджера объектов Windows. Создание и управление этими объектами возможно только для самой среды и для WDF-драйверов.
Сущность UMDF-объектов – это COM. UMDF-объекты используют подмножество COM для реализации интерфейсов запросов и прочего. В драйверах пользовательского режима как драйвер, так и среда реализуют и предоставляют интерфейсы в стиле СОМ. Само собой, отпадает необходимость использования указателей.
Разных объектов UMDF меньше, чем KMDF: в пользовательском режиме многие действия (а значит, и объекты для работы с ними) запрещены.