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

Как компьютер обходился без единого стандарта в эпоху MS-DOS

Первые персональные компьютеры IBM PC работали под управлением MS-DOS, и никакой единой модели драйверов там не существовало. Архитектура IBM PC того времени выделяла под периферию считанные аппаратные прерывания, а именно IRQ с номерами от 0 до 15, и звуковые карты, модемы или сетевые платы часто конфликтовали за одни и те же номера, поэтому пользователю приходилось вручную переставлять перемычки на плате или прописывать нужный номер прерывания прямо в конфигурационном файле драйвера. Программист, которому нужно было напечатать документ или вывести звук, чаще всего обращался напрямую к портам ввода-вывода или прерываниям BIOS, минуя какую-либо прослойку. Драйверы того времени, если их вообще так называли, представляли собой резидентные программы, загружаемые через файл config.sys командами device и devicehigh. Каждый производитель писал код под конкретную модель контроллера, поэтому одна и та же видеокарта могла требовать отдельного файла для разных программ. Windows 1.0, вышедшая в 1985 году, и последующие версии Windows 2.x и 3.x работали поверх MS-DOS и добавляли собственный слой драйверов дисплея и принтера, но фундаментально система оставалась однозадачной надстройкой, где сбой одного модуля нередко обрушивал всю машину.

Появление виртуальных драйверов VxD и переход к защищенному режиму

С выходом Windows 3.1 и особенно Windows 95 в 1995 году архитектура резко усложнилась благодаря переходу процессоров x86 из реального режима в защищенный. Появился формат VxD, что расшифровывается как virtual device driver, работающий на нулевом кольце привилегий процессора и способный виртуализировать доступ к оборудованию для нескольких задач одновременно. Именно VxD впервые дал Windows подобие многозадачности на уровне доступа к железу: два приложения теперь могли одновременно обращаться к устройству, а система сама разруливала конфликты. Такие драйверы использовали расширение vxd и грузились как часть ядра, что делало их быстрыми, но крайне хрупкими. Ошибка в одном VxD-модуле почти всегда приводила к синему экрану, потому что режим ядра не отделял чужой код от системного и не имел встроенной защиты памяти между драйверами.

Параллельно с потребительской линейкой Windows развивалась Windows NT, первая версия которой вышла в 1993 году и опиралась на совершенно иную архитектуру ядра. В NT драйверы подчинялись объектно-ориентированной модели с диспетчером ввода-вывода в центре, а взаимодействие между уровнями стека драйверов строилось на пакетах запросов ввода-вывода, известных как IRP. Этот подход оказался настолько удачным, что впоследствии лег в основу всех современных версий Windows, включая XP, Vista, 7, 10 и 11.

Как WDM объединила разрозненные модели в Windows 98 и Windows 2000

К концу девяностых у Microsoft фактически существовало две параллельные и несовместимые системы драйверов: VxD для потребительской линейки Windows 95 и 98, и собственная модель NT для серверов и рабочих станций. Такое раздвоение сильно раздражало производителей оборудования, которым приходилось писать и поддерживать два разных драйвера для одной и той же платы. Решением стала Windows Driver Model, представленная в 1998 году вместе с Windows 98 и получившая полноценную реализацию в Windows 2000. WDM ввела единый интерфейс, позволявший в теории использовать один и тот же драйвер на разных ветках Windows, а также закрепила слоистую архитектуру, где запрос от приложения проходит через цепочку драйверов функций, фильтров и шины, прежде чем достичь физического устройства.

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

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceControl;
    DriverObject->DriverUnload = MyUnload;
    return STATUS_SUCCESS;
}

Каждый вызов от приложения к устройству оформляется как пакет IRP с заранее известным major-кодом, а обмен управляющими командами идет через механизм IOCTL с уникальным числовым идентификатором для каждой операции. Именно такая унификация позволила сторонним разработчикам писать один драйвер сразу под несколько версий Windows и заметно снизила количество несовместимостей при выходе новых сборок системы, поскольку значительная часть логики Plug and Play, управления питанием и обработки прерываний теперь бралась из общей библиотеки ядра, а не переписывалась вручную под каждую отдельную сборку системы.

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

Классический WDM-драйвер целиком исполняется в режиме ядра, где у кода есть доступ ко всей физической памяти и аппаратным ресурсам без ограничений. Удобство оборачивается риском: любая ошибка в таком коде, будь то обращение по неверному указателю или бесконечный цикл при высоком уровне IRQL, способна уронить всю систему целиком. Поэтому начиная с Windows XP и особенно активно с выходом Windows Vista в 2007 году Microsoft подталкивала разработчиков к разделению логики драйвера на минимальную часть, работающую в режиме ядра, и основную часть, вынесенную в пользовательский режим.

Для упрощения такого разделения появились два фреймворка, построенных поверх низкоуровневого WDM. Kernel-Mode Driver Framework, сокращенно KMDF, взял на себя рутинные операции вроде обработки питания и Plug and Play, оставив разработчику только специфичную для устройства логику. User-Mode Driver Framework, или UMDF, пошел еще дальше и позволил запускать драйверы принтеров, сканеров, веб-камер и других не самых критичных устройств в изолированном процессе пользовательского режима, где падение драйвера приводит лишь к перезапуску этого процесса, а не всей системы. Такое распределение ответственности можно свести к трем практическим случаям применения фреймворков:

  1. Драйверы высокопроизводительных устройств, таких как накопители и сетевые адаптеры, чаще всего пишутся на KMDF ради минимальных задержек и прямого доступа к памяти;
  2. Драйверы периферии со средними требованиями к скорости, например принтеры и сканеры, переносят в UMDF ради устойчивости системы к их сбоям;
  3. Простейшие виртуальные устройства и программные эмуляторы нередко реализуют вовсе без выделенного кода на C, опираясь на готовые классовые драйверы Windows.

Как обязательная цифровая подпись изменила правила установки драйверов

Отдельная и важная глава в истории драйверов Windows связана с безопасностью загрузки кода в режиме ядра. Начиная с 64-разрядных версий Windows Vista, система стала требовать цифровую подпись для любого программного обеспечения, работающего на нулевом кольце привилегий. Изначально разработчик мог временно отключить эту проверку через меню загрузки клавишей F8, что было удобно на этапе отладки, но неприемлемо для готового продукта. Подпись выпуска WHQL, получаемая после прохождения программы совместимости оборудования, стала фактическим пропуском для массового распространения драйвера через каналы Microsoft.

С приходом Windows 10 в 2015 году требования ужесточились еще раз: начиная с версии 1507 подписывать новые драйверы режима ядра можно исключительно через портал Windows Hardware Dev Center, а самостоятельно созданные тестовые сертификаты годятся только для локальной разработки на машине с включенным тестовым режимом. Такой порядок закрыл значительную часть лазеек, которыми раньше пользовались вредоносные программы, маскировавшиеся под легитимные драйверы устройств: получить доступ к нулевому кольцу привилегий без прохождения проверки подлинности стало практически невозможно даже для опытного разработчика.

Что представляют собой современные драйверы в архитектуре Windows 11

Сегодняшняя модель драйверов в Windows 11 продолжает опираться на тот же фундамент WDM и диспетчер IRP, заложенный еще во времена Windows NT, но обрастает дополнительными слоями изоляции. Технология Driver Isolation в связке с виртуализацией на основе гипервизора выносит некоторые классы драйверов в отдельные защищенные контейнеры, а функция Memory Integrity проверяет целостность кода режима ядра еще до его исполнения, опираясь на аппаратные механизмы виртуализации процессора. Для разработчиков это означает необходимость учитывать не только классическую модель безопасности WDM, но и требования HVCI, поскольку несовместимый со строгой проверкой памяти код драйвера попросту не загрузится на современной системе с включенной защитой ядра.

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

Разница между старым подходом и новым особенно заметна на примере обработки прерываний. В классическом WDM-драйвере программист сам следил за тем, на каком уровне IRQL выполняется его код, поскольку операции вроде выделения памяти с ожиданием допустимы только на низком уровне PASSIVE_LEVEL, а на высоком DISPATCH_LEVEL часть системных функций попросту недоступна и вызовет немедленный крах системы при нарушении правила. KMDF во многом взял эту заботу на себя, автоматически переключая контекст выполнения обработчика между уровнями через собственную очередь запросов, что избавило тысячи разработчиков от одного из самых частых источников синих экранов девяностых и двухтысячных годов.

Зачем обычному пользователю разбираться в устройстве драйверов

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

История драйверов Windows, если посмотреть на нее целиком, это история постепенного отказа от доверия к любому коду в режиме ядра и движения в сторону максимальной изоляции даже ценой части производительности. То, что начиналось как резидентные программы под MS-DOS без какой-либо защиты, спустя сорок лет превратилось в строго регламентированную и подписанную инфраструктуру, где каждый посредник между процессором и устройством проходит десятки проверок задолго до того, как получит право хоть раз обратиться к физической памяти компьютера.