Драйвер может быть связан с виртуальным устройством

Обновлено: 04.07.2024

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

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

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

СОДЕРЖАНИЕ

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

Разработка

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

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

У Apple есть платформа с открытым исходным кодом для разработки драйверов для macOS , которая называется I / O Kit.

В среде Linux программисты могут создавать драйверы устройств как части ядра , отдельно как загружаемые модули или как драйверы пользовательского режима (для определенных типов устройств, где существуют интерфейсы ядра, например, для устройств USB). Makedev включает список устройств в Linux, включая ttyS (терминал), lp ( параллельный порт ), hd (диск), цикл и звук (в их число входят микшер , секвенсор , dsp и аудио).

Файлы Microsoft Windows .sys и файлы .ko Linux могут содержать загружаемые драйверы устройств. Преимущество загружаемых драйверов устройств заключается в том, что их можно загружать только при необходимости, а затем выгружать, тем самым экономя память ядра.

Режим ядра и режим пользователя

Драйверы устройств, особенно на современных платформах Microsoft Windows , могут работать в режиме ядра ( Ring 0 на процессорах x86 ) или в пользовательском режиме (Ring 3 на процессорах x86). Основным преимуществом запуска драйвера в пользовательском режиме является повышенная стабильность, поскольку плохо написанный драйвер устройства пользовательского режима не может вызвать сбой системы из-за перезаписи памяти ядра. С другой стороны, переходы между пользовательским режимом и режимом ядра обычно приводят к значительным накладным расходам производительности, что делает драйверы режима ядра предпочтительными для работы в сети с малой задержкой.

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

Приложения

Из-за разнообразия современного оборудования и операционных систем драйверы работают в самых разных средах. Драйверы могут взаимодействовать с:

Общие уровни абстракции для драйверов устройств включают:

  • Для оборудования:
    • Прямое взаимодействие
    • Запись в регистр управления устройством или чтение из него
    • Использование интерфейса более высокого уровня (например, Video BIOS )
    • Использование другого драйвера устройства более низкого уровня (например, драйверов файловой системы, использующих драйверы диска)
    • Моделируя работу с железом, делая при этом что-то совсем другое
    • Предоставление операционной системе прямого доступа к аппаратным ресурсам
    • Реализация только примитивов
    • Реализация интерфейса для программного обеспечения без драйверов (например, TWAIN )
    • Реализация языка, иногда довольно высокого уровня (например, PostScript )

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

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

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

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

    Существует несколько вариантов драйверов виртуальных устройств, таких как VxD , VLM и VDD.

    Драйверы с открытым исходным кодом

    Описание наиболее часто используемых драйверов устройств в Solaris :

    • fas: Быстрый / широкий контроллер SCSI
    • hme: Fast (10/100 Мбит / с) Ethernet
    • isp: Дифференциальные контроллеры SCSI и карта SunSwift
    • glm: (Gigabaud Link Module) Контроллеры UltraSCSI
    • scsi: устройства с последовательным интерфейсом малых компьютеров (SCSI)
    • sf: soc + или socal Fibre Channel Arbitrated Loop (FCAL)
    • soc: контроллеры SPARC Storage Array (SSA) и управляющее устройство
    • socal: последовательные оптические контроллеры для FCAL (soc +)
    • Модель драйвера дисплея Windows (WDDM) - архитектура драйвера графического дисплея для Windows Vista , Windows 7 , Windows 8 и Windows 10 .
    • Унифицированная аудио модель (UAM)
    • Фонд драйверов Windows (WDF)
    • Declarative Componentized Hardware (DCH) - драйвер универсальной платформы Windows
    • Модель драйвера Windows (WDM)
    • Спецификация интерфейса сетевого драйвера (NDIS) - стандартный API драйвера сетевой карты
    • Расширенная звуковая архитектура Linux (ALSA) - с 2009 года стандартный интерфейс звукового драйвера Linux
    • Scanner Access Now Easy (SANE) - общедоступный интерфейс к оборудованию сканера растровых изображений
    • Устанавливаемая файловая система (IFS) - API файловой системы для IBM OS / 2 и Microsoft Windows NT
    • Open Data-Link Interface (ODI) - API сетевой карты, похожий на NDIS
    • Uniform Driver Interface (UDI) - кроссплатформенный проект интерфейса драйвера.
    • Dynax Driver Framework (dxd) - кроссплатформенная среда драйверов C ++ с открытым исходным кодом для KMDF и IOKit

    Идентификаторы

    Устройство на шине PCI или USB идентифицируется двумя идентификаторами, каждый из которых состоит из 4 шестнадцатеричных чисел. Идентификатор поставщика идентифицирует поставщика устройства. Идентификатор устройства идентифицирует конкретное устройство от этого производителя / поставщика.

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

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

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


    Когда приложению требуется операция в/выв, то происходит обращение к драйверу. Для этого приложение может давать запрос на чтение данных из устройства или запись данных на устройство. А если требуется какое-то другое действие, например, опрос или управление устройством, либо что-либо другое, то для этого используется т.н. IOCTL-интерфейс (Device In-Out Control).

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

    Когда приложению требуется операция в/выв, то происходит обращение к драйверу. Для этого может использоваться т.н. IOCTL-интерфейс (Device In-Out Control).

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

    1) Открытие файла и получение его дескриптора:

    invoke CreateFile, ссылка на устройство,

    GENERIC_READ + GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL

    В результате, если все произошло успешно, мы получаем дескриптор устройства.

    2) Посылка драйверу кода действия (что делать, драйвер может реализовывать много различных действий):

    invoke DeviceIoControl, дескриптор, код действия, адрес входного буфера, размер входных данных, адрес выходного буфера, размер выходных данных, адрес буфера для реального количества байтов

    3) Закрытие файла и, соответственно, освобождение дескриптора.

    invoke CloseHandle дескриптор устройства

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


    Такой же код действия используется и в приложении, и в драйвере.

    Код действия в приложении и в драйвере можно записывать в 16-ричном виде, а можно использовать макрос CTL_CODE, как это сделано в примере лаб. работы в файле common.inc.

    Рассмотрим пример кода действия из драйвера виртуального устройства, который используется в лабораторной работе. Имя - IOCTL_GET.


    В случае виртуального устройства файловый флаг равен 0.

    Тип устройства - FILE_DEVICE_UNKNOWN = 22h

    Права доступа - FILE_READ_ACCESS+FILE_WRITE_ACCESS = 1+2=3=11b

    Функциональный код - в диапазоне от 800h до FFFh. У нас - 800h.

    Метод буферизации - способ передачи данных между приложением и драйвером (возможны три):

    - Для небольшого объема данных используется обычно METHOD_BUFFERED (00b) - выделяется дополнительный буфер в нестраничной памяти, достаточный для размещения входного и выходного буфера. Адрес этого буфера размещается в IRP в поле AssociatedIrp.SystemBuffer. Диспетчер в/выв сам берет на себя работу перезаписи данных между пользовательским и дополнительным буфером.

    - Прямой доступ к данным (без буфера) - METHOD_OUT_DIRECT (2) - для вывода либо METOD_IN_DIRECT (1) - для ввода; поле из IRP - MdlAddress. Это непосредственное обращение - диспетчер в/выв фиксирует в памяти физические страницы, содержащие буфер пользовательского режима. При этом создает вспомогательную структуру MDL (Memory Descriptor List) для описания зафиксированных страниц. И разработчик драйвера работает с MDL.

    - Доступ через буфер пользовательского уровня - METHOD_NEITHER (3); поле из IRP - SystemBuffer. Диспетчер в/выв передает в драйвер виртуальные адреса пользовательского режима. И в драйвере нужно очень осторожно с ними работать, потому что драйвер в этом случае должен работать только в контексте вызывающего потока.

    Когда приложение посылает драйверу код действия, то начинает работу диспетчер ввода-вывода. Он отвечает за формирование пакета запроса ввода-вывода (I/O request packet, IRP) и посылку его драйверу для дальнейшей обработки.

    Мы будем рассматривать 3 типа запросов:

    - IRP_MJ_CREATE - будет передан при CreateFile,

    - IRP_MJ_DEVICE_CONTROL - будет передан при DeviceIoControl

    - IPR_MJ_CLOSE - при CloseHandle

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


    В каждом стеке размещения находится указатель на объект-устройство DeviceObject и на объект-файл FileObject, для которого инициирован запрос. Пакеты IRP всегда хранятся в невыгружаемой памяти.


    Для работы драйвера создаются и применяются следующие объекты:

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

    Этапы работы драйвера.

    1) Создание объекта драйвера. Создается при загрузке драйвера на этапе его запуска. В этот момент запускается функция DriverEntry и заполняется массив MajorFunction, а также указатель на объект устройства и обратно.

    В состав объекта устройства входят:

    - ссылка на объект драйвера, который обрабатывает запросы к устройству;

    2) Создание символьной ссылки на устройство. Для того чтобы объект "устройство" стал доступен коду режима пользователя, драйвер должен создать в доступном ему (коду режима пользователя) каталоге "??" еще один объект - символьную ссылку (symbolic link). Драйвер shablon.sys создает символьную ссылку "slshablon" на свое устройство "devshablon" в каталоге "??", значением которой является строка "Devicedevshablon".

    Таким образом, уже при загрузке драйвера (в нашем случае, на этапе загрузки ОС) мы имеем три объекта в памяти: драйвер "Drivershablon", устройство "Deviceshablon" и символьную ссылку на устройство "??slshablon".

    3) Открытие. Дальше при запуске приложения вызывается CreateFile. Там есть ссылка на устройство. Из структуры объекта устройства DEVICE_OBJECT извлекаются сведения об обслуживающем его драйвере. Диспетчер ввода-вывода формирует пакет запроса ввода-вывода IRP типа IRP_MJ_CREATE и направляет его драйверу. Так драйвер узнает о том, что код режима пользователя пытается получить доступ к его устройству. Если драйвер не имеет ничего против, то он возвращает код успеха. У нашего драйвера есть специальная процедура диспетчеризации, которая реагирует на это IRP - DispatchCreateClose (там совмещенная процедура для открытия и закрытия устройства). В ней в поле Io.Status.Status передается STATUS_SUCCESS, а в Io.Status.Information - 0, т.к. в этом случае ничего не нужно передавать. Такой ответ от драйвера является сигналом диспетчеру объектов о создании виртуального файла. При этом в таблице описателей (handle table) процесса создается новый элемент с указателем на объект "файл", и коду режима пользователя возвращается новый дескриптор.

    Если все ОК, то мы сохраняем дескриптор файла, возвращенный CreateFile, в переменной hDevice.

    4) Операции в/выв. Теперь мы имеем возможность осуществлять операции управления этим устройством посредством вызова функций DeviceIoControl. Поскольку драйвер устройства может в принципе выполнять много различных задач, необходимо как-то дифференцировать запросы. Для этого и предназначен второй параметр dwIoControlCode, называемый управляющим кодом ввода-вывода (I/O control code), который строится по определенным правилам.

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

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

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

    В приложении эти данные форматируются и выводятся.

    5) Закрытие. Как и полагается поступать с дескрипторами, которые больше не нужны, вызовом функции CloseHandle, закрываем описатель устройства.

    6) Выгрузка драйвера. Удаляем символьную ссылку и удаляем объект устройства.

    Комплекс (2) состоит из двух программ:

    - приложение, которое обращается к драйверу за адресом IRP, а затем этот адрес выводит в стандартное окно Windows.

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

    Приложение в выходном буфере получает содержимое esi, преобразует его для вывода в шестнадцатеричном виде и выводит в стандартное окно Windows.

    Если необходимо в драйвере получить информацию из CMOS, то требуется:

    - послать в порт 70h смещение в CMOS, которое нас интересует;

    - взять из порта 71h информацию в al.

    Затем записать эту информацию в выходной буфер.

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

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

    Чтобы проверить, что драйвер установлен, выбираем в панели управления Система, Оборудование, Диспетчер устройств.

    Работа операционных систем Windows 95/98 (как и Windows 3.1) в значительной степени основана на использовании специального рода программ — так называемых виртуальных драйверов устройств (или просто виртуальных драйверов, virtual device). Основное назначение виртуального драйвера — виртуализация устройства, то есть возможность нескольким приложениям одновременно использовать одно и то же физическое устройство. Например, виртуальный драйвер дисплея (VDD) обеспечивает многооконный режим, в котором каждое приложение, выводя информацию на экран, считает, что весь физический экран находится в его распоряжении, в то время как в действительности вывод приложения поступает в выделенное для него окно. Виртуальный контроллер прерываний (VPICD) дает возможность нескольким приложениям совместно использовать единую систему прерываний компьютера. Виртуальный драйвер клавиатуры (VKD) позволяет вводить с клавиатуры символьные строки в любую из выполняемых программ. Разработка нового виртуального драйвера может понадобиться при установке на компьютер новой аппаратуры (или нового программного обеспечения, предназначенного для обслуживания других приложений), которая будет использоваться в многозадачном режиме и для которой в системе Windows не предусмотрено средств виртуализации.

    Другое, возможно, более важное для прикладного программиста приложение виртуальных драйверов состоит в использовании их в качестве универсального инструментального средства. Дело в том, что виртуальный драйвер работает в плоской модели памяти на нулевом уровне привилегий. Плоской моделью памяти называется такая организация адресного пространства, когда в дескрипторе указаны нулевой базовый линейный адрес сегмента и предел, соответствующий максимальному размеру сегмента, — 4 Гбайт. Такой дескриптор может входить как в локальную, так и в глобальную таблицу дескрипторов. В первом случае речь идет о приложении, работающем в плоской модели памяти на уровне привилегий 3 (это характерно для 32-разрядных приложений Windows); во втором — о системных компонентах, в частности о виртуальных драйверах. Для виртуального драйвера доступно все линейное адресное пространство (4 Гбайт), все программные составляющие Windows (в частности, системные виртуальные драйверы) и все программно-управляемые аппаратные средства процессора и компьютера в целом. Виртуальный драйвер представляет собой идеальную среду для исследования системы Windows и контролируемого вмешательства в ее работу. Поэтому решение каких-либо нестандартных задач, например разработка программ управления измерительным оборудованием, подключенным к компьютеру, может потребовать написания специфических виртуальных драйверов, смысл использования которых состоит совсем не в виртуализации аппаратно-программных средств, а в возможности выполнения действий, допустимых лишь на нулевом уровне привилегий (например, обращения к запрещенным портам или к системе прерываний).

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

    Первая виртуальная машина, создаваемая после загрузки Windows и называемая системной виртуальной машиной, в Windows 95/98 предназначена для выполнения 16- и 32-разрядных приложений Windows. Для каждого сеанса DOS создается своя виртуальная машина, и таким образом одновременно в системе может существовать несколько виртуальных машин.

    Управление виртуальными машинами возлагается на менеджера виртуальных машин (Virtual machine manager, VMM), являющегося ядром операционных систем Windows 95/98. VMM представляет собой 32-разрядную систему защищенного режима, работающую на нулевом уровне привилегий в плоской модели памяти. В функции VMM входит создание, управление и завершение виртуальных машин, а также управление памятью, процессами, прерываниями и нарушениями защиты. Так, если приложение делает попытку записи или чтения по адресам памяти, не принадлежащим данной виртуальной машине, или выполняет команды ввода-вывода в запрещенные порты, процессор возбуждает исключение и управление передается VMM. VMM анализирует причину исключения и либо с помощью виртуальных драйверов обеспечивает выполнение затребованной операции, либо аварийно завершает приложение.

    Для обеспечения функционирования VMM и виртуальных драйверов система создает два глобальных селектора: 28h — для программных кодов и 30h — для системных полей данных. Дескрипторы этих селекторов почти одинаковы: в них указано нулевое значение DPL, нулевой базовый линейный адрес и граница 4 Гбайт (рис. 1). В регистр CS загружается селектор 28h, во все остальные сегментные регистры (DS, SS, ES, FS и GS) — селектор 30h. Ни VMM, ни виртуальные драйверы никогда не изменяют содержимое сегментных регистров.

    VMM вместе с системными виртуальными драйверами предоставляет большое количество служебных функций, позволяющих виртуальным драйверам активизировать по ходу своего выполнения те или иные системные процедуры и алгоритмы. Например, функция VMM _MapPhysToLinear отображает заданный участок физических адресов на линейное адресное пространство, что дает возможность виртуальному драйверу выполнять чтение или запись в физической памяти; функции Install_Handler и Install_Mult_IO_Handlers устанавливают в системе прикладные процедуры обслуживания заданных портов; функция Simulate_Push помещает указанный параметр в стек приложения. Служебные функции имеются и у системных виртуальных драйверов. Так, виртуальный контроллер прерываний VPICD позволяет с помощью функции VPICD_Virtualize_IRQ организовать прикладную обработку аппаратных прерываний, а виртуальный таймер VTD предоставляет функции VTD_Begin_Min_Period и VTD_End_Min_Period для управления частотой системного таймера. Служебные функции VMM и системных виртуальных драйверов, в отличие от функций DOS или Windows, не могут вызываться непосредственно из приложения; обращение к ним возможно только из виртуальных драйверов.

    Взаимодействуя с VMM и виртуальными машинами, виртуальные драйверы используют целый ряд системных полей и структур данных. Одним из важнейших системных идентификаторов, создаваемых VMM для каждой виртуальной машины, является ее дескриптор. Он однозначно идентифицирует виртуальную машину и используется при вызовах функций VMM для задания VM, к которой обращено требуемое действие. Заметим, что дескриптор виртуальной машины является базовым адресом, от которого ведется отсчет адресов ряда важных полей VMM. Так, в двойном слове по адресу [дескриптор VM — 8] располагается адрес таблицы векторов прерываний защищенного режима.

    Дескриптор VM фактически является линейным 32-разрядным адресом управляющего блока данной VM. Управляющий блок представляет собой структуру из пяти двойных слов, содержащую информацию о виртуальной машине. Эта структура определена в файле VMM.INC и имеет следующий вид:

    В слове состояния VM каждый бит закреплен за определенной характеристикой текущего состояния VM. Так, установленный бит 1 (символическое обозначение VMStat_Background) говорит о том, что VM выполняется в фоновом режиме; бит 6 (VMStat_PM_App) свидетельствует о наличии в VM приложения защищенного режима, бит 7 (VMStat_PM_Use32) характеризует 32-разрядное приложение защищенного режима и т.д. Иногда в процессе отладки виртуального драйвера приходится обращаться к этому слову для определения состояния виртуальной машины.

    Старший линейный адрес заслуживает отдельного рассмотрения. При запуске в рамках Windows одного или нескольких сеансов DOS (и создания соответствующих виртуальных машин) происходит копирование первого мегабайта физического адресного пространства в различные участки расширенной памяти. В каждой копии DOS имеется своя таблица векторов прерываний, своя видеопамять и даже своя копия ПЗУ BIOS, что дает возможность приложениям DOS различных сеансов параллельно и независимо работать каждому с собственной памятью, не разрушая память других сеансов. Для того чтобы с сеансами DOS могли взаимодействовать VMM или виртуальные драйверы, физическая память сеансов DOS отображается на некоторые линейные адреса (4-го гигабайта линейного адресного пространства), которые и называются старшими линейными адресами. Для каждой VM существует собственный старший адрес (рис. 2).

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

    Структура клиента, адрес которой CB_Client_Pointer входит в управляющий блок, является важнейшим набором данных, обеспечивающим обмен информацией между приложением и виртуальным драйвером. Когда при вызове виртуального драйвера процессор переходит на нулевой уровень привилегий, в стеке уровня 0 (не в стеке приложения!) сохраняются значения всех регистров вызывающего приложения, соответствующие точке вызова драйвера. Совокупность этих значений и называется структурой клиента. При входе в драйвер адрес структуры клиента находится в регистре EBP, а обращение к отдельным элементам этой структуры осуществляется с помощью наглядных символических обозначений вида Client_AX, Client_FLAGS, Client_CS и т. д.:

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

    Виртуальные драйверы обычно пишутся на языке ассемблера, хотя внутренние процедуры драйвера вполне могут составляться на языке С (С++). Использование языка высокого уровня упрощает реализацию вычислительных и логических алгоритмов в процедурах драйвера, однако делает текст драйвера менее наглядным. Поскольку нашей задачей является освоение основных принципов разработки драйверов и приводимые ниже примеры относительно просты, мы ограничимся языком ассемблера. Для создания выполнимого модуля необходимо использовать 32-разрядные ассемблер и компоновщик, входящие, в частности, в пакет DDK (Device Development Kit — пакет для разработки драйверов). В том же пакете можно найти заголовочные и другие используемые при разработке драйверов, включаемые файлы с многочисленными макросами и определениями констант.

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

    Структура виртуального драйвера

    Исходный текст драйвера начинается с директивы ассемблера .386, позволяющей использовать расширенный набор команд современных процессоров, в частности привилегированных команд защищенного режима. Следующая далее директива .XLIST подавляет вывод в листинг трансляции всех последующих предложений программы вплоть до отмены ее действия директивой .LIST. В нашем случае подавляется вывод в листинг весьма обширного файла VMM.INC с определениями макросов и констант, фактически обеспечивающего использование в драйвере средств менеджера виртуальной машины VMM.

    Как видно из приведенного листинга, драйвер в простейшем случае состоит из блока описания драйвера и трех сегментов: сегмента команд инициализации в реальном режиме VxD_REAL_INIT, в котором размещается процедура инициализации реального режима (вместе с относящимися к ней данными); сегмента данных VxD_DATA_SEG со всеми данными, которые могут потребоваться драйверу в процессе его работы; сегмента команд защищенного режима VxD_CODE, содержащего в нашем случае процедуры VMyD_Control, V86_API_Handler и V86_API_Handler (имена процедур произвольны).

    В тексте драйвера широко используются макросы, определенные в файле VMM.INC. Так, макросы VxD_REAL_INIT_SEG и VxD_REAL_INIT_ENDS задают начало и конец сегмента инициализации реального режима, макросы VxD_CODE_SEG и VxD_CODE_ENDS — начало и конец сегмента защищенного режима, а макросы BeginProc и EndProc — границы процедур, входящих в состав драйвера. Имена макросов можно набирать как строчными, так и прописными буквами. Приведенное в примере начертание взято из документации Windows.

    Блок описания драйвера, с которого начинается текст драйвера, вводится с помощью макроса Declare_Virtual_Device. Этот макрос требует указания восьми параметров, располагаемых в следующем порядке:

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

    В нашем случае драйверу дано произвольное имя VMyD, управляющей процедуре (которая обязательно должна присутствовать, хотя у нас она фактически пуста) — VMyD_Control, процедурам обслуживания реального и защищенного режимов, которые в дальнейшем мы будем называть API-процедурами, — V86_API_Handler и PM_API_Handler соответственно. Для версии выбран номер 1.0, а для идентификационного кода — 8000h. При разработке коммерческого виртуального драйвера его следует зарегистрировать в корпорации Microsoft и получить для него идентификационный код. Порядок инициализации важен для драйверов, обращающихся друг к другу; для наших драйверов порядок инициализации не имеет значения, что указано с помощью константы Undefined_Init_Order.

    API (Application Program Interface — интерфейс прикладных программ) процедуры являются, можно сказать, содержательной частью драйвера. Эти процедуры, выполняемые на нулевом уровне привилегий в плоской модели памяти, могут быть вызваны из любой прикладной программы (выполняемой, естественно, на третьем уровне защиты). Для обращения к драйверу из приложений DOS (реальный режим) и Windows (защищенный режим) предусматриваются две различные API-процедуры; если действия драйвера в ответ на обращение программ в обоих случаях одинаковы, в соответствующих полях блока описания драйвера можно указать имя единственной процедуры. В некоторых случаях драйвер работает «сам по себе», без обращения к нему из программы пользователя, тогда как и сами процедуры, и их имена в блоке описания драйвера отсутствуют. В дальнейшем мы будем рассматривать только драйверы, предназначенные для работы с приложениями Windows, и в блоке описания драйвера имя API-процедуры реального режима будет отсутствовать:

    Для ускорения процесса подготовки выполнимого модуля виртуального драйвера удобно создать пакет из трех файлов. Приводимый ниже пример их содержимого предполагает, что пакет DDK установлен на диске G: в каталоге DDK95, исходный файл с текстом драйвера имеет имя VMYD.ASM, а выполнимый модуль драйвера — VMYD.VXD.

    Для того чтобы в операторе include в исходном тексте драйвера не описывать длинный путь к файлу VMM.INC, можно в файл AUTOEXEC.BAT включить команду

    Загружаемый модуль драйвера подготавливается запуском в сеансе DOS приведенного выше командного файла W95.BAT.

    Установка созданного драйвера в системе осуществляется путем включения в раздел [386Enh] файла SYSTEM.INI следующей строки (в предположении, что программа драйвера находится в каталоге DRIVERS диска F:):

    Проблемы, описанные ниже, могут появиться при настройке конфигурации с использованием виртуальных COM портов.

    Виртуальный порт не появляется в группе "Порты (COM и LPT)".

    Виртуальные устройства, созданные нашей программой появляются в отдельной группе "Virtual Serial Port".

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

    Данная проблема может возникать, если вы пытаетесь создать порт с номером, который уже существует в системе. Для решения этой проблемы, попробуйте создать виртуальный порт с номером COM20. Если требуется создать порт с уже занятым номером (например, COM1), то выполните следующие действия:

    1. Закройте все программы, которые могут использовать COM1.

    2. Найдите COM1 в диспетчере устройств.

    3. Нажмите правую кнопку мыши на нем.

    4. Выберите "Свойства" и перейдите к закладке "Параметры порта".

    6. Из выпадающего списка "Номер COM-порта" выберите свободный порт и нажмите кнопку "ОК".

    7. Дождитесь окончания изменения операционной системой номера порта. Возможно, что потребуется перезагрузка компьютера.

    com-port-select1

    Рис.1. Параметры COM порта

    com-port-select2

    Рис.2. Выбор нового COM порта

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

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

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

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

    Для 64-битной Windows: c:\Program Files (x86)\TCP COM Bridge\inf\

    Для 32-битной Windows: c:\Program Files\TCP COM Bridge\inf\

    В операционной системе после Windows Vista или Windows Server 2012 политика безопасности на вашем компьютере или сервере может запрещать установку драйверов для всех новых устройств, или для выбранных классов устройств. Такая политика может применяться на уровне организации, если ваш компьютер подключен к домену.

    Самый простой способ проверить вашу политику безопасности это запустить надстройку "rsop.msc":

    1. Нажмите клавиши на клавиатуре Windows + R.

    2. Введите rsop.msc и нажмите Ввод.

    3. Надстройка проведет проверку существующей политики безопасности и покажет, какие правила заданы в ней.

    4. Необходимо убедиться, что отсутствуют запрещающие или ограничивающие правила в группе:

    Конфигурация компьютера - Административные шаблоны - Система - Установка устройств - Ограничения на установку устройств

    В частности правила:

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

    Для самостоятельного настроек политики можно воспользоваться надстройкой "gpedit.msc".

    Внимание: Редактор политики безопасности (gpedit.msc) по умолчанию не установлен в редакциях Starter, Home или Home Premium. Требуется установить его дополнительно, или воспользоваться редактором реестра.

    1. Нажмите клавиши на клавиатуре Windows + R.

    2. Введите gpedit.msc и нажмите Ввод.

    3. Перейдите к группе настроек "Конфигурация компьютера - Административные шаблоны - Система - Установка устройств - Ограничения на установку устройств".

    4. Отключите правила "Запретить установку съемных устройств" и "Запретить установку устройств, не описанных другими параметрами политики". Для этого, дважды кликните на имени правила в списке и выберите "Отключено".

    5. В программе TCP COM Bridge выберите пункт меню "Устройства - Переустановить драйверы". Дождитесь окончания установки драйверов.

    6. Создайте нужные виртуальные порты.

    7. Сохраните настройки.

    8. Дождитесь окончания установки драйверов для новых виртуальных портов.

    Использование редактора реестра:

    1. В меню "Старт", в поиске наберите regedit.

    2. Нажмите правую кнопку мыши на найденном приложении "Редактор реестра" и выберите "Запустить от имени администратора".

    3. Перейдите к ветке реестра HKLM\Software\Policies\Microsoft\Windows\DeviceInstall\Restrictions.

    4. Установите значение DenyRemovableDevices (REG_DWORD) равное 0 (отключено).

    5. Установите значение DenyUnspecified (REG_DWORD) равное 0 (отключено).

    6. Перезагрузите компьютер , чтобы изменения вступили в силу.

    7. В программе TCP COM Bridge выберите пункт меню "Устройства - Переустановить драйверы". Дождитесь окончания установки драйверов.

    8. Создайте нужные виртуальные порты.

    9. Сохраните настройки.

    10. Дождитесь окончания установки драйверов для новых виртуальных портов.

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

    Убедитесь, что включена установка драйверов из центра обновлений.

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

    2. Включите опцию "Да (рекомендуется)".

    3. Нажмите "ОК" и сохраните настройки.

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

    1. В меню "Старт", в поиске наберите cmd.

    2. Нажмите правую кнопку мыши на найденном приложении "Командная строка" и выберите "Запустить от имени администратора".

    3. В командной строке наберите: sfc /scannow и нажмите Ввод.

    4. Будет произведена проверка системных файлов операционной системы.

    5. Обязательно дождитесь окончания процедуры проверки.

    Невозможность установить драйвер может быть связана с поврежденной базой данных устройств.

    1. Выполните шаги 1 и 2 из решения 5.

    2. В командной строке наберите для проверки: Dism /Online /Cleanup-Image /ScanHealth

    3. Или для исправления ошибок: Dism /Online /Cleanup-Image /RestoreHealth

    Читайте также: