Что такое драйвер, типы драйверов и характеристики Что такое драйвер. Драйверы внешних устройств

Драйверы внешних устройств

Драйвер (driver) представляет собой специализированный программный модуль, управляющий внешним устройством. Слово driver происходит от глагола to drive (вести) и переводится с английского языка как извозчик или шофер: тот, кто ведет транспортное средство. Драйверы обеспечивают единый интерфейс для доступа к различным устройствам, тем самым устраняя зависимость пользовательских программ и ядра ОС от особенностей аппаратуры. Драйвер не обязательно должен управлять каким-либо физическим устройством. Многие ОС предоставляют также драйверы виртуальных устройств или псевдоустройств – объектов, которые ведут себя аналогично устройству ввода-вывода, но не соответствуют никакому физическому устройству. В виде псевдоустройств реализуются трубы в системах семейства Unix и почтовые ящики в VMS. Еще одним примером полезного псевдоустройства являются устройства /dev/null в Unix и аналогичное ему \DEV\NUL в MS DOS\Windows\OS/2.

Прикладные программы, использующие собственные драйверы, не так уж редки – примерами таких программ могут быть GhostScript (свободно распространяемый интерпретатор языка PostScript, способный выводить программы на этом языке на различные устройства.

Большинство ОС общего назначения запрещают пользовательским программам непосредственный доступ к аппаратуре. Это делается для повышения надежности и обеспечения безопасности в многопользовательских системах. В таких системах драйверы являются для прикладных программ единственным способом доступа к внешнему миру. Еще одна важная функция драйвера – это взаимоисключение доступа к устройству в средах с вытесняющей многозадачностью. Допускать одновременный неконтролируемый доступ к устройству нескольких параллельно исполняющихся процессов просто нельзя, потому что для большинства внешних устройств даже простейшие операции ввода-вывода не являются атомарными. Например, в большинстве аппаратных реализации последовательного порта RS232 передача байта состоит из четырех шагов: записи значения в регистр данных, записи команды “передавать” в регистр команды, ожидания прерывания по концу передачи и проверки успешности передачи путем считывания статусного регистра устройства. Нарушение последовательности шагов может приводить к неприятным последствиям – например, перезапись регистра данных после подачи команды, но до завершения передачи, может привести к остановке передачи или, что еще хуже, передаче искаженных данных и т. д. Нельзя также забывать о неприятностях более высокого уровня – например, смешивании вывода разных процессов на печати или данных – на устройстве внешней памяти. Поэтому оказывается необходимо связать с каждым внешним устройством какой-то разграничитель доступа во времени. В современных ОС эта функция возлагается именно на драйвер. Обычно одна из нитей драйвера представляет собой процесс-монитор, выполняющий асинхронно поступающие запросы на доступ к устройству. В Unix, OS/2 и Windows NT/2000/XP этот процесс называется стратегической функцией. При определении интерфейса драйвера разработчики ОС должны найти правильный баланс между противоречивыми требованиями:

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

2. желанием предоставить гибкий и интеллектуальный интерфейс к разнообразным устройствам.

Драйверы обычно разрабатываются не поставщиками операционной системы, а сторонними фирмами – разработчиками и изготовителями периферийного оборудования. Поэтому интерфейс драйвера является ничуть не менее внешним, чем то, что обычно считается внешним интерфейсом ОС – интерфейс системных вызовов. Соответственно, к нему предъявляются те же требования, что и к любому другому внешнему интерфейсу: он должен быть достаточно простым, исчерпывающе документированным и стабильным – не меняться непредсказуемо от одной версии ОС к другой. Идеальным вариантом была бы полная совместимость драйверов хотя бы снизу вверх, чтобы драйвер предыдущей версии ОС мог использоваться со всеми последующими версиями. Потеря совместимости в данном случае означает, что все независимые изготовители оборудования должны будут обновить свои драйверы. Организация такого обновления оказывается сложной, неблагодарной и часто попросту невыполнимой задачей – например, потому, что изготовитель оборудования уже не существует как организация или отказался от поддержки данного устройства. Таким образом, интерфейс драйвера часто оказывается наиболее консервативной частью ОС. К сожалению – несмотря даже на то, что в общих чертах архитектура драйвера в большинстве современных ОС удивительно похожа – идея эта, по-видимому, нереализуема. Даже для близкородственных ОС – например, систем семейства Unix – драйверы одного и того же устройства не всегда могут быть легко перенесены из одной ОС в другую, не говоря уж о возможности использования без модификаций. Еще более удивительным является тот факт, что две линии ОС – Windows 95/98/МЕ и Windows NT/2000/XP – поставляемых одной и той же компанией Microsoft и реализующих почти один и тот же интерфейс системных вызовов, – Win32 – имеют совсем разный интерфейс драйвера. Проблема здесь в том, что интерфейс между драйвером и ядром ОС всегда двусторонний: не только прикладные программы и ядро вызывают функции драйвера, но и, наоборот, драйвер должен вызывать функции ядра. Таким образом, до тех пор, пока используются ОС различной архитектуры, разработка универсального интерфейса драйвера, если теоретически и возможна, то практически вряд ли осуществима.

Пользователи знают, что для корректного функционирования абсолютно всех составляющих компьютера, называемых иногда «железом», и некоторых виртуальных устройств в системе должно присутствовать специальное программное обеспечение, называемое драйвером. Но что же такое драйвер устройства? Ответ на этот и некоторые другие вопросы можно найти в материале, приведенном ниже.

Драйвер: это что такое в общем понимании?

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

Общепринятая трактовка данного термина говорит, что драйвер - это программа, которая позволяет операционной системе идентифицировать «железное» или виртуальное устройство и сделать его работоспособным в компьютерной среде. Но это только одна сторона медали. На самом деле можно сказать, что драйвер - это некий мост, связывающий сам «железный» или виртуальный компонент компьютера с операционной системой и установленными в ней приложениями, а в конечном итоге - с пользователем.

Вопросы установки драйверов устройств

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

Windows-системы инсталлируют основные драйверы еще на стадии своей установки, определяя устройства автоматически. Но это срабатывает не всегда, и порой в системе остаются неопознанные компоненты, для которых в собственной базе данных системы, увы, подходящего программного обеспечения не нашлось. Что делать в этом случае?

Первым делом необходимо просмотреть весь список установленного оборудования в «Диспетчере устройств», который проще всего вызвать командой devmgmt.msc через консоль «Выполнить». Также можно воспользоваться стандартной «Панелью управления» или разделом администрирования через меню ПКМ на значке компьютера.

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

Как и где найти нужный драйвер, если в Windows его нет?

Например, в системе отсутствует сетевой драйвер. Для большинства сетевых адаптеров Ethernet управляющее ПО устанавливается без проблем. Но в случае с нестандартным оборудованием или сетевой картой от малоизвестного производителя его может и не оказаться. Придется скачивать драйвер самостоятельно. Но как найти именно то, что нужно?

Для этого предусмотрены специальные идентификаторы, которые соответствуют конкретному устройству (DEV и VEN). Чтобы их найти, необходимо через ПКМ в меню свойств вызвать раздел свойств и перейти на вкладку сведений, где в информационном списке выбирается ИД оборудования. Самая длинная строка и будет в полной мере соответствовать описанию устройства. Таким образом, тот же сетевой драйвер можно найти в интернете, задав в поиске именно эту комбинацию.

После скачивания драйвер можно установить самостоятельно. Если это EXE-файл - проблем нет, только запускать инсталлятор необходимо от имени администратора.

В случае с INF-файлом драйвер можно установить через меню ПКМ или указать системе установку из указанного места при обновлении драйвера в «Диспетчере устройств».

Использование автоматизированных утилит

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

Здесь на помощь приходят специализированные программные пакеты для автоматического поиска и установки драйверов вроде Driver Booster, DriverPack Solution и им подобные. А вот такие утилиты, определив все устройства, которые присутствуют в компьютерной системе, обращаются не к собственным или сторонним базам данных, а непосредственно к ресурсам производителей оборудования и разработчиков соответствующего программного обеспечения, загружая и устанавливая его без видимого участия самого пользователя (его участие сводится только к тому, чтобы согласиться с предложением инсталляции или обновления). Плюс ко всему, эти программы следят за актуальным состоянием драйверов, постоянно проверяя выход новых версий (чего Windows при всем уважении к этим системам делать не умеет).

Заключение

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

Введение.

Драйверная концепция – неотъемлемая часть современных операционных систем. Эта концепция – основа взаимодействия системы (пользователя) с какими бы то ни было устройствами (системными/периферийными, реальными/виртуальными и т.д.). Даже системные программисты далеко не всегда имеют представление об этой концепции, о принципах ее работы, о программировании с использованием этой концепции. А ведь системное программирование – ключ к пониманию основ IT.

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

У каждой операционной системы есть свои особенности, отсюда вытекает своя специфика написания драйверов под них. То же самое можно сказать и о разных типах оборудования.

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

Часть 1. Драйвера.

1.1 Основные понятия.

Драйвер – это часть кода операционной системы (небольшая программка не для пользователя, а для ОС), отвечающая за взаимодействие с аппаратурой. Под словом «аппаратура» можно подразумевать как реальные физические устройства, так и виртуальные и логические. Например, есть много разных, принтеров разных производителей. Чтобы на них печатать, нужно знать, какие команды этому принтеру нужно передавать. У разных производителей такие команды могут сильно отличаться друг от друга. Нужно как-то научить ОС работать со всеми типами принтеров – в этом помогают драйверы.

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

Список основных общих концепций драйверов в Windows и UNIX-системах выглядит так:

    Способ работы с драйверами как файлами. Т.е. функции, используемые при взаимодействии с файлами, практически идентичны таковым при взаимодействии с драйверами (лексически): open, close, read и т.д.

    Драйвер, как легко заменяемая часть ОС

    Существование режима ядра

Классификация типов драйверов для ОС Windows:

    Драйверы пользовательского режима (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

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

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 – драйвера:

    Драйвер шины обнаруживает устройство.

    Plug and Play Manager определяет местонахождение ключа устройства в ветке Enum реестра. Этот ключ содержит указатель на другой ключ реестра, определяющий функциональный драйвер (который управляет отдельным устройством и является основным драйвером устройства). Pnp-менеджер динамически загружает функциональный драйвер.

    PnP-менеджер вызывает функцию драйвера AddDevice для того, чтобы создать DRIVER_OBJECT. Если драйвер соответствует больше, чем одному фактическому устройству, PnP-менеджер вызывает AddDevice для каждого из них. С этого момента вся коммуникация драйвера с внешним миром осуществляется с использованием IRP (I/O Request Packet) – пакетов.

    PnP-менеджер выделяет все необходимые драйверу ресурсы ввода/вывода (запросы на прерывание, номера портов и т.д.) и посылает запрос для инициализации устройства.

    Некоторые устройства могут быть удалены из системы без выключения компьютера. Если устройство – одно из таких, то PnP-менеджер посылает драйверу специальный IRP-пакет, в результате чего созданный функцией AddDevice объект устройства уничтожается.

    Когда все устройства удалены, то менеджер ввода/вывода (I/O Manager) вызывает функцию DriverUnload, которая удаляет образ драйвера из памяти.

Часть 4. Структура драйвера.

Фактически драйвер можно представить как довольно-таки обычную DLL-библиотеку уровня ядра. Таким образом, далее можно представить драйвер просто как набор процедур, периодически вызываемых внешними программами. Несмотря на то, что процедуры драйверов для разных устройств сильно отличаются, есть общая структура и общие функции для всех драйверов.

DriverEntry – ключевая функция драйвера. Функция инициализации. Ее основные задачи – произвести все необходимые действия по инициализации и определить точки входа для остальных функций драйвера. Эта функция вызывается при загрузке драйвера.

IN PDRIVER_OBJECT DriverOBject,

IN PUNICODE_STRING RegistryPath

Она принимает 2 аргумента: первый – указатель на объект DriverObject типа PDRIVER_OBJECT. Он позволяет функции DriverEntry определить указатели на функции Dispatch, AddDevice, StartIo, а также на функцию выгрузки драйвера в объекте драйвера. Аргумент RegistryPath передает функции DriverEntry указатель на Unicode строку, содержащую путь к ключу драйвера в реестре.

Каждый драйвер должен иметь, по крайней мере, одну процедуру Dispatch

IN PDEVICE_OBJECT DeviceObject,

Если драйвер устройства не может завершить все возможные запросы ввод/вывода

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

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

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

IN PDRIVER_OBJECT DriverObject,

IN PVOID Context,

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

InterruptService{

IN PKINTERRUPT Iterrupt,

IN PVOID ServiceContext

Любой драйвер, имеющий ISR , должен иметь DpcForIsr или CustomDpc

IN struct _DEVICE_OBJECT *DeviceObject,

IN struct _IRP *Irp,

IN PVOID Context

IN struct _KDPC *Dpc,

IN PVOID DeferredContext,

IN PVOID SystemArgument1,

IN PVOID SystemArgument2

Любой низкоуровневый драйвер устройства, данные которого или регистры сопряженного устройства могут изменяться в его ISR и других процедурах драйвера, должен иметь одну или более процедур SynchCritSection

SynchCritSection{

IN PVOID SynchronizeContext

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

IO_ALLOCATION_ACTION

IN PDEVICE_OBJECT DeviceObject,

IN PVOID MapRegisterBase,

IN PVOID Context

IO_ALLOCATION_ACTION

ControllerControl{

IN PDEVICE_OBJECT DeviceObject,

IN PVOID MapRegisterBase,

IN PVOID Context

Клавиатура, мышь, последовательный, параллельный, звуковой драйверы и драйвер файловой системы имеют процедуру Cancel. Любой драйвер, обрабатывающий запрос в течение длительного промежутка времени (когда пользователь может отменить операцию), должен иметь процедуру Cancel. Обычно эту процедуру имеет высший драйвер в стеке обработки запроса.

IN PDEVICE_OBJECT DeviceObject,

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

IN PDEVICE_OBJECT DeviceObject,

IN PVOID Context

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

IN struct_KDPC *Dpc,

IN PVOID DeferredContext,

IN PVOID SystemArgument1,

IN PVOID SystemArgument2

Драйвер должен иметь процедуру Unload, если он может быть выгружен во время работы системы.

IN PDRIVER_OBJECT DriverObject

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

Функции 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 создает символическую ссылку между устройством и видимым пользователем именем.

IOCreateSymbolicLink{

IN PUNICODE_STRING SymbolicLinkName, // символическое имя,

// видимое пользователем

IN PUNICODE_STRING DeviceName // имя устройства

// в пространстве имен Windows

Функция IoCompleteRequest объявляет менеджеру ввода/вывода, что обработка текущего запроса ввода/вывода закончена.

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-функций. Спулер вызывает все эти функции по мере надобности.

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: в пользовательском режиме многие действия (а значит, и объекты для работы с ними) запрещены.

Разнообразный набор драйверов для широкого круга популярных периферийных устройств – непременное условие популярности ОС у пользователей.

Для разработки драйверов производителями внешних устройств необходимо наличие четкого, удобного, открытого и хорошо документированного интерфейса между драйверами и другими компонентами ОС. Драйвер взаимодействует, с одной стороны, с модулями ядра ОС (модулями подсистемы ввода-вывода, модулями системных вызовов, модулями подсистем управления процессами и памятью), а с другой стороны – с контроллерами внешних устройств. Поэтому существует два вида интерфейсов: интерфейс «драйвер-ядро» (Driver Kernel Interface, DKI) и интерфейс «драйвер-устройство» (Driver Device Interface).

Интерфейс «драйвер-ядро» должен быть стандартизован в любом случае. Подсистема ввода-вывода может поддерживать несколько различных интерфейсов DKI/DDI, предоставляя специфический интерфейс для устройств определенного класса. К наиболее общим классам относятся блочные устройства, например, диски, и символьные устройства, такие как клавиатура и принтеры. Может существовать класс сетевых адаптеров и др. В большинстве современных ОС определен стандартный интерфейс, который должен поддерживать все блочные драйверы, и второй стандартный интерфейс, поддерживаемый всеми символьными адаптерами. Эти интерфейсы включают наборы процедур, которые могут вызываться остальной операционной системой для обращения к драйверу. К этим процедурам относятся, например, процедуры чтения блока или записи символьной строки.

Кроме того, подсистема ввода-вывода поддерживает большое количество системных функций, которые драйвер может вызывать для выполнения некоторых типовых действий. Например, это операции обмена с регистрами контроллера, ведения буферов промежуточного хранения данных ввода-вывода, взаимодействия с DMA-контроллером и контроллером прерываний и др.

У драйверов устройств есть множество функций:

1. Обработка запросов записи-чтения от программного обеспечения управления устройствами. Постановка запросов в очередь.

2. Проверка входных параметров запросов и обработка ошибок.

3. Инициализация устройства и проверка статуса устройства.

4. Управление энергопотреблением устройства.

5. Регистрация событий в устройстве.

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

7. Проверка правильности завершения операции.

8. Передача запрошенных данных и статуса завершенной операции.

9. Обработка нового запроса при незавершенном предыдущем запросе (для реентерабельных драйверов).

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

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

Затем драйвер может проверить, не используется ли это устройство в данный момент. Если устройство занято, запрос может быть поставлен в очередь. Если устройство свободно, проверяется статус устройства, чтобы понять, может ли запрос быть обслужен прямо сейчас. Может оказаться необходимым включить устройство или запустить двигатель, прежде чем начнется перенос данных. Как только устройство включено и готово, начинается собственно управление устройством.

Управление устройством подразумевает выдачу ему серии команд. Именно в драйвере определяется последовательность команд в зависимости от того, что должно быть сделано. Определившись с командой, драйвер начинает записывать их в регистры контроллера устройства. После записи каждой команды в контроллер, возможно, будет нужно проверить, принял ли контроллер команду и готов ли принять следующую. Такая последовательность действий продолжается до тех пор, пока контроллеру не будут переданы все команды. Некоторые контроллеры способны принимать связные списки команд, находящихся в памяти. Они сами считывают и выполняют их без дальнейшей помощи ОС.

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

Для поддержки процесса разработки драйверов операционной системы выпускается так называемый пакет DDK (Driver Development Kit), представляющий собой набор инструментальных средств-библиотек, компиляторов и отладчиков.

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

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

Поэтому поддержка динамической загрузки драйверов является практически обязательным требованием для современных универсальных ОС.

Первоначально термин «драйвер» применялся в достаточно узком смысле – под драйвером понимается программный модуль, который:

Входит в состав ядра ОС, работая в привилегированном режиме;

Непосредственно управляет внешним устройством, взаимодействуя с его контроллером с помощью команд ввода-вывода компьютера;

Обрабатывает прерывания от контроллера устройства;

Предоставляет прикладному программисту удобный логический интерфейс работы с устройством, экранируя от него низкоуровневые детали управления устройством и организации его данных;

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

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

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

Традиционные драйверы, которые стали называть аппаратными, низкоуровневыми или драйверами устройств, освобождаются от высокоуровневых функций и занимаются только низкоуровневыми операциями. Эти низкоуровневые операции составляют фундамент, на котором можно построить тот или иной набор операций в драйверах более высоких уровней.

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

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

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

В модулях подсистемы ввода-вывода, кроме драйверов, могут присутствовать и другие модули, например, дисковый кэш. Достаточно специфичные функции кэша делают нецелесообразным оформление его в виде драйвера, взаимодействующего с другими модулями ОС только с помощью услуг менеджера ввода-вывода. Другим примером модуля, который чаще всего не оформляется в виде драйвера, является диспетчер окон графического интерфейса. Иногда этот модуль вообще выносится из ядра ОС и реализуется в виде пользовательского интерфейса. Таким образом, был реализован диспетчер окон в Windows NT 3.5 и 3.51, но этот микроядерный подход заметно замедляет графические операции, поэтому в Windows 4.0 диспетчер окон и высокоуровневые графические драйверы, а также графическая библиотека GDI были перенесены в пространство ядра.

Аппаратные драйверы после запуска операции ввода-вывода должны своевременно реагировать на завершение контроллером заданного действия путем взаимодействия с системой прерывания. Драйверы более высоких уровней вызываются не по прерываниям, а по инициативе аппаратных драйверов или драйверов вышележащего уровня. Не все процедуры аппаратного драйвера нужно вызывать по прерываниям, поэтому драйвер обычно имеет определенную структуру, в которой выделяется секция обработки прерываний (Interrupt Service Routine, ISR), которая и вызывается от соответствующего устройства диспетчером прерываний.

В унификацию драйверов большой вклад внесла ОС UNIX, в которой все драйверы были разделены на два класса: блок-ориентированные (Block-oriented) и байт-ориентированные (Character-oriented) драйверы. Это более общее деление, чем деление на вертикальные подсистемы. Например, драйверы графических устройств и сетевых устройств относятся к классу байт-ориентированных.

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

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

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

В свое время ОС UNIX сделала очень важный шаг по унификации операций и структуризации программного обеспечения ввода-вывода. В ОС UNIX все устройства рассматриваются как виртуальные (специальные) файлы, что дает возможность использовать общий набор базовых операций ввода-вывода для любых устройств независимо от их специфики. Подобная идея реализована позже в MS-DOS, где последовательные устройства – монитор, принтер и клавиатура – считаются файлами со специальными именами: con, prn, con.

Драйвер устройства выполняет несколько функций:

Обработку абстрактных запросов чтения и записи независимого от устройств и расположенного над ними программного обеспечения.

Инициализацию устройства.

Управление энергопотреблением устройства и регистрацией событий.

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

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

Управление устройством подразумевает выдачу ему серии команд. Именно в драйвере и определяется последовательность команд в зависимости оттого, что должно быть сделано. Определившись с командами, драйвер начинает записывать их в регистры контроллера устройства. Некоторые контроллеры способны принимать связные списки команд, находящихся в памяти. Они сами считывают и выполняют их без дальнейшей помощи операционной системы.

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

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

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

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



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

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

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

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

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

С именованием устройств тесно связан вопрос защиты. Как ОС предотвращает доступ пользователей к устройствам, на который у них нет прав? В UNIX и в Windows 2000 устройства представляются в файловой системе в виде именованных объектов, что дает возможность применять обычные правила защиты файлов к устройствам ввода-вывода. Системныйадминистратор может установить нужные разрешения для каждого устройства.

  • Сергей Савенков

    какой то “куцый” обзор… как будто спешили куда то