p align="left">Каждая ячейка IRP-стека содержит: MajorFunction типа UCHAR - это код, описывающий назначение операции; MinorFunction типа UCHAR - это код, описывающий суб-код операции; DeviceObject типа PDEVICE_OBJECT - это указатель на объект устройства, которому был адресован данный запрос IRP; FileObject типа PFILE_OBJECT - файловый объект для данного запроса; Parameters типа union - применение зависит от значения MajorFunction. Диспетчер ввода/вывода использует поле MajorFunction для того, чтобы извлечь из массива MajorFunction нужную для обработки запроса процедуру. Каждая процедура обработки IRP пакетов должна в качестве параметров принимать: Указатель на объект устройства, для которого предназначен IRP запрос; Указатель на пакет IRP, описывающий этот запрос; 2.4.3.3 Функция обработки пакетов IRP_MJ_CREATE. Данная функция предназначена для обработки запросов на получение дескриптора драйвера от пользовательских приложений или вышестоящих драйверов. Как правило, эта функция просто помечает IRP-пакет, как завершённый. 2.4.3.4 Функция обработки пакетов IRP_MJ_CLOSE. Данная функция предназначена для обработки запросов на закрытие дескриптора драйвера от пользовательских приложений или вышестоящих драйверов. Как правило, эта функция просто помечает IRP-пакет, как завершённый. 2.4.3.5 Функция обработки пакетов IRP_MJ_DEVICE_CONTROL. Данная функция позволяет обрабатывать расширенные запросы от клиентов пользовательского режима, служат чаще всего для обмена данными между приложением и драйвером. Такой запрос может быть сформирован посредством вызова функции DeviceIoControl из пользовательского режима. Здесь используются IOCTL-коды (I/O Control code), часть из которых предопределена операционной системой, а часть может создаваться разработчиком драйвера. Такой код задаётся в запросе Диспетчером ввода/вывода при формировании IRP-пакета. Операции драйвера, которые работают с IOCTL-запросами, часто требуют задания буферной области для размещения входных или выходных данных. Возможна такая ситуация, когда в одном запросе используются оба буффера. Метод доступа к данным, обеспечиваемый Диспетчером ввода/вывода, определяется в IOCTL-коде. Такими методами могут быть: METHOD_BUFFERED: входной пользовательский буфер копируется в системный, а по окончании обработки системный копируется в выходной пользовательский буфер. METHOD_IN_DIRECT: необходимые страницы пользовательского буфера загружаются с диска в оперативную память и блокируются. Далее с помощью DMA осуществляется передача данных между устройством и пользователем. METHOD_OUT_DIRECT: необходимые страницы пользовательского буфера загружаются с диска в оперативную память и блокируются. Далее с помощью DMA осуществляется передача данных между устройством и пользователем. METHOD_NEITHER: при данном методе передачи не производится проверка доступности памяти, не выделяются промежуточные буфера и не создаются MDL. В IRP-пакете передаются виртуальные адреса буферов в адресном пространстве инициатора запроса ввода/вывода. В данном случае флаги, определяющие тип буферизации в объекте устройства, не имеют значения при работе с IOCTL запросами. Механизм буферизованного обмена определяется при каждом задании значения IOCTL в специально предназначенном для этого фрагменте этой структуры данных. Данный подход обеспечивает максимальную гибкость при работе с вызовом пользовательского режима DeviceIoControl. С точки зрения драйвера, доступ к буферным областям, содержащим данные или предназначенным для данных, осуществляется с помощью следующих полей структур [1]: |
| METHOD_BUFFERED | METHOD_IN_DIRECT или METHOD_OUT_DIRECT | METHOD_NEITHER | | Input Буфер с данными | Использует буферизацию (системный буфер) Адрес буфера в системном адресном пространстве указан в pIrp->AssociatedIrp.SystemBuffer | Клиентский виртуальный адрес в Parameters. DeviceIoControl. Type3InputBuffer | | | Длина указана в Parameters.DeviceIoControl.InputBufferLength | | Output Буфер для данных | Использует буферизацию (системный буфер) Адрес буфера в системном адресном пространстве указан в pIrp-> AssociatedIrp. SystemBuffer | Использует прямой доступ, клиентский буфер преобразован в MDL список, указатель на который размещен в pIrp->MdlAddress | Клиентский виртуальный адрес в pIrp->UserBuffer | | | Длина указана в Parameters.DeviceIoControl.OutputBufferLength | | |
2.4.4 ISR - процедура обработки прерываний Эту функцию драйвер регистрирует, чтобы она получала управление в момент, когда аппаратура, обслуживаемая драйвером, передала сигнал прерывания. Задача этой функции выполнить минимальную работу и зарегистрировать процедуру отложенного вызова (DPC) для обслуживания прерывания. Вызов диспетчером прерываний ядра может произойти в любом контексте: как ядра, так и пользовательского процесса. 2.4.5 DPC - процедура отложенного вызова Такие процедуры выполняются при более низком уровне запроса прерывания (IRQL), чем ISR, что позволяет не блокировать другие прерывания. В них может выполняться вся или завершаться начатая в ISR работа по обслуживанию прерываний. 3. Конструкторский раздел Так выглядит схема взаимодействия пользовательского приложения с драйвером через компоненты системы: 3.1 Legacy-драйвер В Legacy-драйвере данного курсового проекта реализованы следующие процедуры: DriverEntry; DriverUnload; DispatchCreate (обработка IRP_MJ_CREATE-пакета); DispatchClose (обработка IRP_MJ_CLOSE-пакета); DispatchDeviceControl (обработка IRP_MJ_DEVICE_CONTROL-пакета). 3.1.1 Процедура DriverEntry Здесь выполняются типичные для инициализации драйвера драйвера действия. Регистрируются точки входа в драйвер: pDriverObject->DriverUnload = SpectatorDriverUnload; PDRIVER_DISPATCH * majorFunction = pDriverObject->MajorFunction; majorFunction[ IRP_MJ_CREATE ] = SpectatorDispatchCreate; majorFunction[ IRP_MJ_CLOSE ] = SpectatorDispatchClose; majorFunction[ IRP_MJ_DEVICE_CONTROL ] = SpectatorDispatchDeviceControl; Создаётся объект устройства с именем DEVICE_NAME: #define DEVICE_NAME L"\\Device\\Spectator" RtlInitUnicodeString( &deviceName, DEVICE_NAME ); status = IoCreateDevice (pDriverObject, sizeof( DEVICE_EXTENSION ), &deviceName, FILE_DEVICE_SPECTATOR, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject); Для созданного обекта устройства регистрируется символьная ссылка SYMBOLIC_LINK: #define SYMBOLIC_LINK L"\\DosDevices\\Spectator" RtlInitUnicodeString( &symbolicLink, SYMBOLIC_LINK ); status = IoCreateSymbolicLink( &symbolicLink, &deviceName ); Создаётся объект ядра мьютекс: NTSTATUS CreateMutex() {BEGIN_FUNC( CreateMutex ); NTSTATUS status = STATUS_SUCCESS; status = _ExAllocatePool( g_pMutex, NonPagedPool, sizeof( KMUTEX ) ); if ( NT_SUCCESS( status ) ) {KeInitializeMutex( g_pMutex, 0 ); status = STATUS_SUCCESS;} END_FUNC( CreateMutex ); return ( status );} Впервые загружается информация о процессах и их потоках: if ( LockInfo() == STATUS_SUCCESS ) {ReloadInfo(); UnlockInfo();} Функции LockInfo() и UnlockInfo() являются просто напросто функциями-обёртками для функций LockMutex() и UnlockMutex() соответственно. Первая из последних двух функций ожидает на объекте ядра мьютекс. Объекты ядра «мьютексы» гарантируют потокам взаимоисключающий доступ к един ственному ресурсу. Отсюда и произошло название этих объектов (mutual exclusion, mutex). Они содержат счетчик числа пользователей, счетчик рекурсии и переменную, в которой запоминается идентификатор потока. Мьютексы ведут себя точно так же, как и критические секции. Однако, если последние являются объектами пользователь ского режима, то мьютексы -- объектами ядра. Кроме того, единственный объект-мью текс позволяет синхронизировать доступ к ресурсу нескольких потоков из разных процессов; при этом можно задать максимальное время ожидания доступа к ресурсу. Именно благодаря этому мьютексу обеспечивается требование по безопасности при обращении к хранимой информации. Инициализируется работа таймера: Таймер необходим для того, чтобы с определённым интервалом обновлять хранимую информацию. Для этого создаётся объект ядра «таймер»: status = _ExAllocatePool( g_pTimer, NonPagedPool, sizeof( KTIMER ) ); KeInitializeTimerEx( g_pTimer, SynchronizationTimer ); Замечание: память под объекты ядра должна выделяться исключительно в нестраничном пуле (ключевое слово NonPagedPool). Таймеры могут быть двух типов: SynchronizationTimer -- по истечении указанного временного интервала или очередного периода, он переводится в сигнальное состояние, пока один из потоков, ждущих его, не будет пробуждён. Тогда же таймер переводится в несигнальное состояние. NotificationTimer -- по истечении указанного временного интервала или очередного периода, он переводится в сигнальное состояние, причём пробуждаются все потоки ожидающие на нём. Такой таймер остаётся в сигнальном состоянии до тех пор, пока он не будет явно переведён в несигнальное. Для того, чтобы выполнять какую-то полезную работу по таймеру, необходимо зарегистрировать DPC-процедуру OnTimer(). Для неё необходимо создать собственный DPC-объект, который будет периодически ставится в общесистемную очередь: status = _ExAllocatePool( g_pTimerDpc, NonPagedPool, sizeof( KDPC ) ); KeInitializeDpc( g_pTimerDpc, OnTime, NULL ); Далее, в силу того, что в данном драйвере по таймеру должны выполняться действия, требующие пользовательского контекста, необходимо их вынести из функции OnTimer(), которая является DPC-процедурой, а следовательно, во время её выполнения доступен лишь системный контекст. Тем не менее, необходимо обеспечить приемлемую синхронность выполнения необходимой работы с моментом извлечения DPC-объекта функции из очереди для обработки. Для этого создадим поток, который будет посвящён ожиданию некоторого события: OBJECT_ATTRIBUTES objectAttributes; InitializeObjectAttributes( &objectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL ); status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, &objectAttributes, NULL, NULL, UpdateThreadFunc, NULL ); KeInitializeEvent( g_pUpdateEvent, SynchronizationEvent, FALSE ); Замечание: объекты ядра «события» по своему типу идентичны объектам ядра «таймер». При поступлении этого события поток будет обновлять системную информацию о процессах и их потоках. Объект этого события будем переводить в сигнальное состояние в функции OnTimer(). Данный способ синхронизации позволил обеспечить выполнение необходимых действий через заданный интервалом с точностью до милисекунды, что следует из нижеприведённых сообщений, перехваченных программой DebugView от отладочной версии драйвера: 0.00075233 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^ 0.00116579 [Spectator] ======== LockInfo ======== 0.00118814 [Spectator] ======== ReloadInfo ======== 0.99727142 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^ 1.00966775 [Spectator] ======== LockInfo ======== 1.00968981 [Spectator] ======== ReloadInfo ======== 1.99729049 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^ 2.05610037 [Spectator] ======== LockInfo ======== 2.05632067 [Spectator] ======== ReloadInfo ======== 2.99727035 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^ 2.99741030 [Spectator] ======== LockInfo ======== 2.99743295 [Spectator] ======== ReloadInfo ======== 3.99727631 [Spectator] ^^^^^^^^ OnTime ^^^^^^^^
Страницы: 1, 2, 3
|