- •Введение 5
- •1 Исследовательский раздел
- •1.1 Анализ существующих аналогичных систем
- •1.1.1 Обзор архитектуры устройств usb
- •1.2 Обоснование выбора программно-аппаратных средств
- •1.3 Постановка задачи
- •1.4 Развернутое техническое задание
- •1.4.1 Общие сведения
- •2.1.1 Основные дескрипторы usb драйвера
- •2.1.1.1 Дескриптор устройства
- •2.1.1.2 Дескриптор расширения устройства
- •2.1.1.3 Дескриптор конфигурации
- •2.1.1.4 Дескриптор интерфейса
- •2.1.1.5 Дескриптор конечной точки
- •2.2 Разработка функциональной схемы драйвера
- •2.2.1 Драйвер в иерархии wdm
- •2.2.2 Уровни обмена данными usb устройств
- •2.2.3 Архитектура системного драйвера usb
- •2.2.4 Основные рабочие процедуры драйвера
- •2.2.5 Управление перемещаемостью кода в драйвере
- •2.3 Разработка алгоритмического обеспечения
- •2.3.1 Инициализация драйвера
- •2.3.3 Обработка расширенных запросов ioctl
- •2.3.4 Поддержка запросов Plug and Play
- •2.3.5 Управление питанием
- •2.3.5.1 Обработка запросов irp_mj_power
- •2.3.6 Процедура деинициализации драйвера
- •2.4 Разработка программного обеспечения
- •2.4.1 Процедура DriverEntry
- •2.4.2 Процедура DriverUnload
- •2.4.3 Процедура AddDevice
- •2.4.4 Процедура передачи запроса usbd
- •2.4.5 Обработчики usbCreate и usbClose
- •2.4.6 Обработчик ConfigureDevice
- •2.4.7 Обработчики запросов на чтение и запись
- •3 Технологический раздел
- •3.1 Технология разработки драйверов для операционных систем семейства Windows
- •3.1.1 Архитектура Windows Driver Model
- •3.1.2 Выбор типа разрабатываемого драйвера
- •3.1.3 Разработка usb драйвера
- •3.2 Технология отладки драйверов в операционных системах семейства Windows
- •3.2.1 Основные отладочные тесты
- •3.2.2 Основные «проблемы», возникающие при отладке драйвера
- •3.2.2.1 Аппаратные проблемы
- •3.2.2.2 Программные проблемы
- •3.2.3 Основные отладчики и утилиты для проверки драйвера
- •3.2.3.1 Отладчик WinDbg
- •3.2.3.2 Driver Verifier
- •3.2.4 Общие приемы отладки драйвера
- •3.2.4.1 Установка фиксированных точек прерывания
- •3.2.4.2 Промежуточный вывод на экран
- •3.2.4.3 Сохранение отладочного кода в исходном тексте драйвера
- •3.2.4.4 Перехват некорректных условий
- •3.2.4.5 Обнаружение утечек памяти
- •3.2.5 Замечания по отладке драйверов
- •4 Безопасность жизнедеятельности
- •4.1 Анализ эргономических параметров рабочего места пользователя пэвм
- •4.1.1 Общие эргономические аспекты рабочего места
- •4.2 Организация рабочего места пользователя с учётом эргономических требований
- •4.2.1 Организация рабочего стола
- •4.2.2 Рабочее кресло
- •4.2.3 Работа с клавиатурой и мышью
- •4.2.4 Расположение и эргономические характеристики монитора
- •4.2.5 Внутренний объем
- •4.2.6 Рабочая поза пользователя пэвм
- •4.3 Экологическая оценка и переработка узлов компьютерной техники содержащих платину
- •4.3.1 Извлечение платины из отработанных катализаторов
- •4.3.2 Извлечение платины из радиооборудования и сплавов для электрических контактов
- •5 Экономический раздел
- •5.1 Планирование разработки драйвера с построением графика выполнения работ
- •5.1.1 Определение этапов и работ по созданию программного продукта
- •5.1.2 Расчет трудоемкости и продолжительности работ
- •5.1.3 Построение графика выполнения работ
- •5.2 Расчет затрат на разработку
- •5.3 Оценка экономической эффективности проекта
- •1 К исследовательскому разделу
- •2 К специальному разделу
- •3 К технологическому разделу
- •4 К разделу «Безопасность Жизнедеятельности»
- •5 К экономическому разделу
- •Приложение а Установка драйвера с помощью inf-файла
- •Приложение б Графические материалы
2.2.5 Управление перемещаемостью кода в драйвере
Системы Windows работают на компьютерах с поддержкой виртуального адресного пространства. Виртуальные адреса отображаются либо на физическую память, либо (по крайней мере, на концептуальном уровне) на страничные блоки в файле подкачки на диске. Сильно упрощая, можно представить себе адресное пространство разделенным на две части: часть режима ядра и часть пользовательского режима, как показано на рис. 2.3.
Рисунок 2.3 – Составляющие адресного пространства Windows
Каждый процесс пользовательского режима обладает собственным адресным контекстом, то есть отображением виртуальных адресов пользовательского режима на уникальную совокупность физических страничных блоков. Другими словами, смысл виртуального адреса изменяется по мере того, как планировщик Windows переключается с потоков одного процесса на потоки другого процесса. Одной из стадий переключения потоков является замена таблиц страниц, используемых процессором, и приведение их в соответствие с контекстом процесса нового потока [2.2].
Таким образом, основной целью систем виртуальной памяти является возможность создания виртуального адресного пространства, значительно превышающего объем физической памяти компьютера. Для достижения этой цели менеджер памяти (Memory Manager) выгружает страничные блоки из физической памяти и загружает их обратно по мере надобности. Некоторые компоненты операционной системы выгружаться не могут, потому что они необходимы для обеспечения работы самого менеджера памяти [2.2].
Итак, одни части драйвера должны постоянно оставаться резидентными, а другие могут выгружаться. Традиционно для передачи компилятору инструкций о размещении кода в определенной секции использовалась директива #pragma alloc_text. Поскольку эта директива поддерживается не всеми компиляторами, в заголовочных файлах DDK предусмотрена специальная константа ALLOC_PRAGMA. Далее директива alloc_text вызывается для определения размещения отдельных функций драйвера по секциям, как в следующем фрагменте:
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, AddDevice)
#pragma alloc_text(PAGE, DispatchPnp)
#endif
Эти директивы обеспечивают размещение функций AddDevice и DispatchPnp в перемещаемой памяти.
Однако я предпочитаю определять размещение секций для целых блоков программного кода при помощи директивы Microsoft #pragma code_seg. Иначе говоря, следующий фрагмент приказывает компилятору Microsoft приступить к размещению функций в перемещаемой памяти:
#pragma code_seg("PAGE")
NTSTATUS AddDeviсе(...){...}
NTSTATUS DispatchPnp(...){...}
Обе функции, AddDevice и DispatchPnp, попадают в перемещаемую память. Чтобы вернуться к кодовой секции по умолчанию, необходимо использовать директиву #pragma code_seg без аргумента:
#pragma code_seg()
2.3 Разработка алгоритмического обеспечения
2.3.1 Инициализация драйвера
Процедура DriverEntry присутствует в любом драйвере и имеет данное стандартное имя. Драйверы «в-стиле-NT» выполняют в DriverEntry большую работу, нежели более «старые» WDM драйвера — последние откладывают часть работы по инициализации устройства до момента обнаружения устройства системой, когда будет вызвана процедура AddDevice.
Таблица 2.8 - Параметры вызова функции DriverEntry
NTSTATUS DriverEntry |
IRQL == PASSIVE_LEVEL |
Параметры |
Описание |
IN PDRIVER_OBJECT DriverObject |
Адрес объекта драйвера |
IN PUNICODE_STRING RegistryPath |
Путь в регистре к подразделу драйвера |
Возвращаемое значение |
|
Получив от Диспетчера ввода/вывода указатель на структуру DRIVER_OBJECT, драйвер должен заполнить в ней определенные поля, а именно [2.3]:
Поле pDriverObject->DriverUnload — для регистрации собственной функции Unload, которая вызывается перед выгрузкой драйвера.
Поле pDriverObject->DriverStartIo — для регистрации собственной функции Startlo, которая необходима для организации обработки очереди необработанных запросов System Queuing.
Поле pDriverObject->DriverExtension->AddDevice — в структуре расширения объекта драйвера DRIVER_EXTENSION, в котором WDM драйвер регистрирует собственную процедуру AddDevice.
В массиве pDriverObject->MajorFunction[IRP_MJ_Xxx] драйвер регистрирует точки входа в собственные рабочие процедуры.
Помимо регистрации функций, процедура DriverEntry драйвера «в стиле-NT» может выполнять следующую работу [2.3]:
DriverEntry определяет аппаратное обеспечение, которое драйвер будет контролировать. Это аппаратное обеспечение выделяется драйверу, то есть помечается как находящееся под управлением данного драйвера.
Если драйвер управляет многокомпонентным (multiunit) или многофункциональным контроллером, используется IoCreateController для создания объекта контроллера, после чего инициализируется структура расширения контроллера.
Выполняет вызов IoCreateDevice для создания объекта устройства для каждого физического или логического устройства под управлением данного драйвера, в процессе которого инициализируется структура расширения устройства для каждого созданного объекта устройства. Рекомендуется сразу же после этого вызова явно установить флаги (поле Flags в объекте устройства), описывающие способ буферизации, используемый данным устройством.
Созданные устройства затем делаются видимыми для приложений пользовательского режима путем выполнения вызова IoCreateSymbolicLink.
Устройство подключается к объекту прерываний. В случае, если ISR процедура требуют использования объекта DPC (отложенного процедурного вызова), то он создается и инициализируется на этом этапе.
Шаги с 3 по 5 повторяются для каждого физического или логического устройства, работающего под управлением данного драйвера.
В случае успешного завершения, функция DriverEntry должна возвратить Диспетчеру ввода/вывода значение STATUS_SUCCESS.
2.3.2 Обработка запросов ввода\вывода
Пакеты ввода / вывода (IRP‑пакеты) используются для передачи запросов к драйверу от его клиентов. Они являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых ячейками стека ввода/вывода (I/O Stack Location) [2.3].
В таблице 2.9 приведена структура заголовка IRP‑пакета [2.3]:
Таблица 2.9 − Структура заголовка IRP‑пакета
Поля |
Описание |
IO_STATUS_BLOCK IoStatus |
Статус запроса |
PVOID AssociatedIrp.SystemBuffer |
Указатель на системный буфер для случая, если устройство поддерживает буферизованный ввод / вывод |
PMDL MdlAddress |
Указатель на MDL‑список в случае, если устройство поддерживает прямой ввод/вывод |
PVOID UserBuffer |
Адрес пользовательского буфера для ввода/вывода |
BOOLEAN Cancel |
Индикатор того, что IRP‑пакет должен быть аннулирован |
Основное назначение ячеек стека ввода/вывода состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/вывод. Ниже, в таблице 2.10 приводятся поля ячеек стека ввода/вывода, к которым драйвер может обращаться непосредственно по указателю (чего не рекомендуется делать для остальных полей) [2.3]:
Таблица 2.10 − Структура ячейки стека ввода/вывода
Поля |
Описание |
UCHAR MajorFunction |
Код IRP_MJ_XXX, описывающий назначение операции |
UCHAR MinorFunction |
Субкод операции |
PDEVICE_OBJECT DeviceObject |
Указатель на объект устройства, которому был адресован данный объект IRP |
PFILE_OBJECT FileObject |
Файловый объект для данного запроса, если он задан |
union Parameters (трактовка определяется значением MajorFunction) |
|
struct Read |
Параметры для IRP типа IRP_MJ_READ: ULONG Length ULONG Key LARGE_INTEGER ByteOffset |
struct Write |
Параметры для IRP типа IRP_MJ_WRITE: ULONG Length ULONG Key LARGE_INTEGER ByteOffset
|
struct DeviceControl |
Параметры для IRP типа IRP_MJ_DEVICE_CONTROL: ULONG OutputBufferLength ULONG InputBufferLength ULONG IoControlCode PVOID Type3InputBuffer |
Приведем графическое представление структуры IRP‑пакета (рис. 2.4):
Рисунок 2.4 − Структура IRP пакета
Общение с USB‑накопителями в ОС Windows NT на уровне драйверов происходит посредством передачи URB‑пакетов. Указатели на URB‑пакеты содержат ячейки стека IRP‑пакета, доступ к этим указателям осуществляется следующим образом [2.3]:
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PURB Urb = IrpSp->Parameters. Others. Argument1;
Приведем частичное объявление структуры из справочной документации Microsoft. Отметим только поля, использование которых необходимо в рамках данной темы [2.3]:
typedef struct _URB
{
union
{
struct _URB_HEADER UrbHeader;
struct _URB_SELECT_INTERFACE UrbSelectInterface;
struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
}
} URB, *PURB;
Поле UrbHeader хранит информацию о коде URB‑пакета, по которому можно определить, какая операция запрашивается.
Поля UrbSelectInterface и UrbSelectConfiguration служат для запроса по выбору интерфейса и конфигурации устройства, которые будут использоваться при работе с устройством. Пакеты этой структуры отправляются хостом к устройству в начале его работы, при конфигурировании.
Поле UrbBulkOrInterruptTransfer несет наиболее важную в рамках данной курсовой работы информацию – указатели на блоки ввода / вывода USB‑устройства. Приведем описание структуры _URB_BULK_OR_INTERRUPT_TRANSFER [2.3]:
struct _URB_BULK_OR_INTERRUPT_TRANSFER
{
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;
struct _URB *UrbLink;
...
...
};
Поля этой структуры описаны в следующей таблице 2.11:
Таблица 2.11 − Поля структуры URB_BULK_OR_INTERRUPT_TRANSFER
Поле |
Описание |
struct _URB_HEADER Hdr |
Стандартный заголовок URB‑пакета, содержащий код запроса |
USBD_PIPE_HANDLE PipeHandle |
Дескриптор канала, на который передаются данные |
ULONG TransferFlags |
Флаги, определяющие направление передачи данных и способ обработки ошибок |
ULONG TransferBufferLength |
Длина передаваемого блока данных в байтах |
PVOID TransferBuffer |
Указатель на передаваемый буфер. Буфер находится в нестраничной памяти |
PMDL TransferBufferMDL |
Указатель на MDL‑список, несущий передаваемую информацию. Буфер находится в страничной памяти |
Следует отметить, что один из указателей TransferBuffer или TransferBufferMDL равен NULL, то есть в пределах одного пакета передается только одна порция данных. Задача протоколирования обмена информацией сводится к перехвату и сохранению буферов TransferBuffer и TransferBufferMDL.