Программирование драйверов Windows

Исполняемый код драйвера


В файле Driver.cpp размещен исходный текст всех функций драйвера.

В процедуре DriverEntry выполняется регистрация процедур DriverUnload (отвечает за завершающие операции при выгрузке драйвера), DispatchCreate (при получении клиентом дескриптора для доступа к драйверу), DispatchClose (при закрытии дескриптора, полученного для доступа к драйверу), DispatchWrite (обработка IRP пакета, поступившего вследствие вызова WriteFile в приложении-клиенте), DispatchRead (обработка IRP пакета, поступившего вследствие вызова ReadFile

в приложении-клиенте).

Действия по созданию объекта устройства, символьной ссылки и подключению драйвера к прерыванию в данном Legacy драйвере тоже выполняются в DriverEntry, только лишь оформлены они в виде автономной функции CreateDevice. (В WDM драйвере реального PnP устройства эти операции следовало бы выполнять в процедуре AddDevice и обработчике IRP_MJ_PNP + IRP_MN_START_DEVICE, поскольку загрузка драйвера является только частью старта PnP устройства).

//========================================================================= // Файл driver.c // Драйвер обслуживания заглушки CheckIt (параллельный порт 378h) // By SVP, 20 June 2004 //========================================================================= #include "driver.h"

// Предварительные объявления функций static NTSTATUS CreateDevice ( IN PDRIVER_OBJECT pDriverObject, IN ULONG portBase, IN ULONG Irq ); static NTSTATUS DispatchCreate ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ); static NTSTATUS DispatchClose ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ); static VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject ); static NTSTATUS DispatchWrite ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ); static NTSTATUS DispatchRead ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp );

BOOLEAN Isr ( IN PKINTERRUPT pInterruptObject, IN PVOID pServiceContext );

BOOLEAN DoNextTransfer ( IN PVOID pContext );

VOID DpcForIsr( IN PKDPC pDpc, IN PVOID DeferredContext, IN PVOID pArg1, IN PVOID pArg2 ); //========================================================================= // Функция: DriverEntry // Назначение: Инициализирует драйвер, подключает объект устройства для // получения прерываний. // Аргументы: pDriverObject - поступает от Диспетчера ввода/вывода // pRegistryPath - указатель на Юникод-строку, // обозначающую раздел Системного Реестра, созданный // для данного драйвера. // Возвращаемое значение: // NTSTATUS - в случае нормального завершения STATUS_SUCCESS // или код ошибки STATUS_Xxx // extern "C" NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath ) { NTSTATUS status; #if DBG==1 DbgPrint("LPTPORT: in DriverEntry, RegistryPath is:\n %ws. \n", pRegistryPath-&#62Buffer); #endif // Регистрируем рабочие процедуры драйвера: pDriverObject-&#62DriverUnload = DriverUnload;


pDriverObject-&#62MajorFunction[IRP_MJ_CREATE] = DispatchCreate; pDriverObject-&#62MajorFunction[IRP_MJ_CLOSE] = DispatchClose; pDriverObject-&#62MajorFunction[IRP_MJ_WRITE] = DispatchWrite; pDriverObject-&#62MajorFunction[IRP_MJ_READ] = DispatchRead;

// Работа по созданию объекта устройства, подключению // ресурсов, прерывания, созданию символьной ссылки: status = CreateDevice(pDriverObject, 0x378, 0x7);

return status; } //========================================================================= // Функция: CreateDevice // Назначение: Создание устройства с точки зрения системы // Аргументы: pDriverObject - поступает от Диспетчера ввода/вывода // portBase - адрес базового регистра параллельного порта (378h) // Irq - прерывание (в терминах шины ISA) для обслуживания порта // Возвращаемое значение: // NTSTATUS - в случае нормального завершения STATUS_SUCCESS // или код ошибки STATUS_Xxx // NTSTATUS CreateDevice ( IN PDRIVER_OBJECT pDriverObject, IN ULONG portBase, IN ULONG Irq ) { NTSTATUS status; PDEVICE_OBJECT pDevObj; PDEVICE_EXTENSION pDevExt; // Создаем внутреннее имя устройства

UNICODE_STRING devName; RtlInitUnicodeString( &devName, L"\\Device\\LPTPORT" ); // Создаем объект устройства status= IoCreateDevice( pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj ); if (!NT_SUCCESS(status)) return status;



// Будем использовать метод буферизации BUFFERED_IO pDevObj-&#62Flags |= DO_BUFFERED_IO; // Заполняем данными структуру Device Extension pDevExt = (PDEVICE_EXTENSION)pDevObj-&#62DeviceExtension; pDevExt-&#62pDevice = pDevObj; // сохраняем - это пригодится pDevExt-&#62ustrDeviceName = devName; pDevExt-&#62Irq = Irq; pDevExt-&#62portBase = (PUCHAR)portBase; pDevExt-&#62pIntObj = NULL; pDevExt-&#62xferRest = 0; // сейчас нет неотправленных данных pDevExt-&#62pIntObj = NULL; //================================================ // Инициализируем объект DPC для последующего использования // при обработки прерываний: KeInitializeDpc( &(pDevExt-&#62DpcForIsr_Object), DpcForIsr, pDevExt // &#60- pDeferredContext в функции DpcForIsr ); //================================================ // На всякий случай блокируем поступление прерываний: WriteControlRegister ( pDevExt, CR_DEFAULT ); //================================================ // Создаем и подключаем объект прерываний: KIRQL kIrql; KAFFINITY kAffinity;



ULONG kVector = HalGetInterruptVector(Isa, 0, pDevExt-&#62Irq, pDevExt-&#62Irq, &kIrql, &kAffinity); // Замечание. Для Isa шины второй параметр (номер шины) обычно // равен 0, а третий и четвертый параметры равны.

#if DBG==1 DbgPrint( "LPTPORT: Interrupt %d converted to kIrql = %d, " "kAffinity = %d, kVector = %X(hex)\n", pDevExt-&#62Irq, kIrql, kAffinity, kVector); #endif

status = IoConnectInterrupt ( &pDevExt-&#62pIntObj, // Здесь будет создан Interrupt Object Isr, // Наша функция ISR pDevExt, // Этот указатель ISR функция будет // получать при вызове (контекстный указатель) NULL, // Не будем использовать spin-блокировку для // безопасного доступа к совместно используемым // данным kVector, // транслированное значение прерывания kIrql, // DIRQL kIrql, // DIRQL Latched, // Прерывание по перепаду TRUE, // Совместно используемое (Shared) прерывание kAffinity, // Поцессоров в мультипроцессорной системе FALSE ); // Не сохранять значения регистров сопроцессора if (!NT_SUCCESS(status)) { // В случае неудачи удаляем объект устройства IoDeleteDevice( pDevObj ); return status; } #if DBG==1 DbgPrint("LPTPORT: Interrupt successfully connected.\n"); #endif //================================================ // Создаем символьную ссылку: UNICODE_STRING symLinkName; // Сформировать символьное имя: //#define SYM_LINK_NAME L"\\??\\LPTPORT0" // ^^ проходит только в NT // Для того, чтобы работало в Windows 98 & XP : #define SYM_LINK_NAME L"\\DosDevices\\LPTPORT0"

RtlInitUnicodeString( &symLinkName, SYM_LINK_NAME );

// Создать символьную ссылку: status = IoCreateSymbolicLink( &symLinkName, &devName ); if (!NT_SUCCESS(status)) { // При неудаче - отключаемся от прерывания и // удаляем объект устройства: IoDisconnectInterrupt( pDevExt-&#62pIntObj ); IoDeleteDevice( pDevObj ); return status; }

pDevExt-&#62ustrSymLinkName = symLinkName; #if DBG==1 DbgPrint("LPTPORT: Symbolic Link is created: %ws. \n", pDevExt-&#62ustrSymLinkName.Buffer); #endif return STATUS_SUCCESS; }



Работа c DPC процедурами может проходить по двум существенно различающимся сценариям. В первом из них, который будет реализован в следующем варианте драйвера, DPC процедура соотносится с объектом устройства вызовом IoInitializeDpcRequest, и код драйвера может запланировать ее вызов путем применения IoRequestDpc

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

Другой сценарий, реализуемый ниже, предлагает связывание DPC объекта (неинициализированный DPC объект &#8212 это просто область памяти под структурой типа KDPC) с одной из функций драйвера вызовом KeInitializeDpc, см. таблицу 10.23. Такой инициализированный DPC объект может быть вставлен в очередь DPC объектов с помощью вызова KeInsertQueueDpc &#8212 так можно запланировать к вызову связанную с ним DPC функцию драйвера в любом месте кода драйвера, правда, работающем при уровне IRQL не ниже DISPATCH_LEVEL. При использовании данного сценария, драйвер (в том числе, его процедура обработки прерывания) может планировать для последующего вызова разные DPC функции. Заметим, что, временно повысив IRLQ при помощи вызова KeRaiseIrql, драйвер может планировать вызовы DPC функций при помощи KeInsertQueueDpc даже внутри кода, работающего при IRQL, равном PASSIVE_LEVEL.

При работе по этому второму сценарию следует, однако помнить, что в условиях принудительной выгрузки драйвера в системной очереди не должно оставаться DPC объектов от выгружаемого драйвера. Эта мера безопасности реализована ниже в процедуре DriverUnload, когда для удаления DPC объекта из системной очереди используется вызов KeRemoveQueueDpc.

//========================================================================= // Функция: DriverUnload // Назначение: Останавливает и удаляет объекты устройств, отключает // прерывания, подготавливает драйвер к выгрузке. // Аргументы: pDriverObject - поступает от Диспетчера ввода/вывода // Возвращаемое значение: нет // VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject ) { #if DBG==1 DbgPrint("LPTPORT: in DriverUnload now\n"); #endif PDEVICE_OBJECT pNextObj = pDriverObject-&#62DeviceObject;



// Проход по всем устройствам, контролирумым драйвером for( ; pNextObj!=NULL; ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pNextObj-&#62DeviceExtension; // Удаляем объект прерываний: if (pDevExt-&#62pIntObj) { // На всякий случай блокируем поступление прерываний // и очищаем DPC очередь от нашего DPC объекта WriteControlRegister( pDevExt, CR_DEFAULT); KeRemoveQueueDpc( &(pDevExt-&#62DpcForIsr_Object) ); IoDisconnectInterrupt( pDevExt-&#62pIntObj ); } // Удаляем символьную ссылку: IoDeleteSymbolicLink(&pDevExt-&#62ustrSymLinkName); #if DBG==1 DbgPrint("LPTPORT: SymLink %ws deleted\n", pDevExt-&#62ustrSymLinkName.Buffer); #endif // Сохраняем ссылку на следующее устройство и удаляем // текущий объект устройства: pNextObj = pNextObj-&#62NextDevice; IoDeleteDevice( pDevExt-&#62pDevice ); } // Замечание. Поскольку мы использовали ресурс (параллельный порт) // объявленные не нами, то освобождение этого ресурса можно опустить. } //========================================================================= // Функция: DispatchCreate // Назначение: Обрабатывает запрос по поводу Win32 вызова CreateFile // Аргументы: pDevObj - поступает от Диспетчера ввода/вывода // pIrp - поступает от Диспетчера ввода/вывода // Возвращаемое значение: STATUS_SUCCESS // NTSTATUS DispatchCreate ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { #if DBG==1 DbgPrint("LPTPORT: in DispatchCreate now\n"); #endif pIrp-&#62IoStatus.Status = STATUS_SUCCESS; pIrp-&#62IoStatus.Information = 0; // ни одного байта не передано IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_SUCCESS; } //========================================================================= // Функция: DispatchClose // Назначение: Обрабатывает запрос по поводу Win32 вызова CloseHandle // Аргументы: pDevObj - поступает от Диспетчера ввода/вывода // pIrp - поступает от Диспетчера ввода/вывода // Возвращаемое значение: STATUS_SUCCESS // NTSTATUS DispatchClose ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { #if DBG==1 DbgPrint("LPTPORT: in DispatchClose now\n"); #endif pIrp-&#62IoStatus.Status = STATUS_SUCCESS; pIrp-&#62IoStatus.Information = 0; // ни одного байта не передано IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_SUCCESS; } //========================================================================= // Функция: DispatchWrite // Назначение: Обрабатывает запрос по поводу Win32 вызова WriteFile // Аргументы: pDevObj - поступает от Диспетчера ввода/вывода // pIrp - поступает от Диспетчера ввода/вывода // Возвращаемое значение: // NTSTATUS - в случае нормального завершения STATUS_SUCCESS // или код ошибки STATUS_Xxx // NTSTATUS DispatchWrite ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { #if DBG==1 DbgPrint("LPTPORT: in DispatchWrite now\n"); #endif PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp ); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj-&#62DeviceExtension; ULONG xferSize = pIrpStack-&#62Parameters.Write.Length;



if( xferSize == 0 ) // Нет данных для передачи : { #if DBG==1 DbgPrint("LPTPORT: DispatchWrite: no bytes to transfer.\n"); #endif pIrp-&#62IoStatus.Status = STATUS_SUCCESS; pIrp-&#62IoStatus.Information = 0; // Нет переноса IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_SUCCESS; } if( pDevExt-&#62xferRest&#62 0 ) { // Не начинаем обрабатывать новый запрос, если остались // непереданные данные (в буфере deviceOutBuffer драйвера) #if DBG==1 DbgPrint("LPTPORT: DispatchWrite: not all data transferred\n"); #endif pIrp-&#62IoStatus.Status = STATUS_DEVICE_BUSY; pIrp-&#62IoStatus.Information = 0; // Нет переноса IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_DEVICE_BUSY; } if( xferSize &#62 MAX_BUFFER_SIZE ) { // Слишком большой запрос. Завершаем обработку IRP пакета: #if DBG==1 DbgPrint( "PLPTPORT: DispatchWrite: xferSize &#62 MAX_BUFFER_SIZE\n" ); #endif pIrp-&#62IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; pIrp-&#62IoStatus.Information = 0; // Нет переноса IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_INSUFFICIENT_RESOURCES; } // Буфер с данными, поступивший от клиента, переносим в // рабочий буфер: PUCHAR userBuffer = (PUCHAR)pIrp-&#62AssociatedIrp.SystemBuffer; RtlCopyMemory( pDevExt-&#62deviceOutBuffer, userBuffer, xferSize );

pDevExt-&#62xferRest = xferSize; pDevExt-&#62xferCount = 0;

// Запускаем перенос данных в первый раз KeSynchronizeExecution( pDevExt-&#62pIntObj, DoNextTransfer, pDevExt ); // Формально -- передача завершена: pIrp-&#62IoStatus.Information = xferSize; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_SUCCESS; }

Обработчик запросов от Win32 вызова WriteFile переносит данные во внутренний буфер deviceOutBuffer и инициирует процесс переноса вызовом DoNextTransfer при посредничестве KeSyncronizeExecution. Последний повышает текущий уровень IRQL работы до уровня, ассоциированного с объектом прерывания, указанного в качестве первого параметра pDevExt-&#62pIntObj.


В результате (это будет видно позже в распечатке log- файла из программы DebugView) код функции DoNextTransfer выполняется на уровне IRQL равном IRQL кода функции ReadDataSafely и кода функции Isr, которые равны 8 в данном тесте.

//========================================================================= // Функция: DispatchRead // Назначение: Обрабатывает запрос по поводу Win32 вызова ReadFile // Аргументы: pDevObj - поступает от Диспетчера ввода/вывода // pIrp - поступает от Диспетчера ввода/вывода // Возвращаемое значение: // NTSTATUS - в случае нормального завершения STATUS_SUCCESS // или код ошибки STATUS_Xxx // NTSTATUS DispatchRead ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj-&#62DeviceExtension; PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp ); ULONG xferSize, xferredSize; #if DBG==1 DbgPrint("LPTPORT: in DispatchRead now\n"); #endif

if( pDevExt-&#62xferRest&#62 0 ) { // Не начинаем обрабатывать новый запрос, если остались // непереданные данные #if DBG==1 DbgPrint("LPTPORT: DispatchRead: Exists nonprocessed request\n"); #endif pIrp-&#62IoStatus.Status = STATUS_DEVICE_BUSY; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return STATUS_DEVICE_BUSY; }

// Определяем размер запроса: xferSize = pIrpStack-&#62Parameters.Read.Length; if( xferSize &#62 MAX_BUFFER_SIZE ) xferSize = MAX_BUFFER_SIZE;

// Передаем не более данных, чем размер числа переданных байт xferredSize = pDevExt-&#62xferCount; xferSize = (xferSize &#60 xferredSize ? xferSize : xferredSize ); if(xferSize&#62 0) { // Копируем содержимое внутреннего входного буфера // в буфер клиента PVOID userBuffer = pIrp-&#62AssociatedIrp.SystemBuffer; RtlCopyMemory(userBuffer, pDevExt-&#62deviceInBuffer, xferSize); }

// Завершаем обработку IRP пакета: pIrp-&#62IoStatus.Status = STATUS_SUCCESS; pIrp-&#62IoStatus.Information = xferSize; // число переданных байт IoCompleteRequest( pIrp, IO_NO_INCREMENT ); #if DBG==1 DbgPrint("LPTPORT: DispatchRead: %d byted transferred.\n", xferSize); #endif return STATUS_SUCCESS; }



//========================================================================= // Процедура обслуживания прерывания: // BOOLEAN Isr ( IN PKINTERRUPT pInterruptObject, IN PVOID pServiceContext ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pServiceContext; KIRQL currentIrql = KeGetCurrentIrql(); #if DBG==1 DbgPrint("LPTPORT: In Isr procedure, ISR_Irql=%d\n", currentIrql); #endif //=============================================================== // Строго говоря, следовало бы проверить, имело ли место // прерывание и наше ли это прерывание: // UCHAR status = ReadStatusRegister( pDevExt ); // if( status & 0x04 ) return FALSE; // прерывания не было // Однако в силу упомянутых накладок с использованием бита SR.2, // не проверяем это вовсе, делая допущение, что если Isr получила // управление, то прерывание vнаше. //=============================================================== // Общей практикой является блокирование поступления прерываний // в этом месте:

// WriteControlRegister( pDevExt, CR_DEFAULT);

// Однако, мы не будем этого делать, чтобы не испортить данные, // находящиеся сейчас в Status Register. Полагаем, что кроме // нашего драйвера такие прерывания никто не генерирует, а драйвер // защищен тем, что Write запросы отвергаются до полного переноса // данных. //================================================================ // Планируем вызов DPC процедуры для обработки прерывания позднее // KeInsertQueueDpc( &(pDevExt-&#62DpcForIsr_Object), (VOID *)NULL, // &#60- Arg1 in DpcForIsr (VOID *)NULL); // &#60- Arg2 in DpcForIsr return TRUE; // нормальное завершение обработки прерывания }

Процедура обработки прерывания Isr планирует вызов DpcForIsr DPC функции через размещение DPC объекта в системной очереди. Этим ее функции и ограничиваются в столь простом драйвере.

//========================================================================= // Код, который посылает в порт данные, вызывающие (при наличии // CheckIt заглушки) сигнал прерывания: // VOID ForceInterrupt( PDEVICE_EXTENSION pDevExt, UCHAR bits ) { // Генерируем сигнал прерывания WriteControlRegister( pDevExt, bits | CR_INT_ENB | CR_DEFAULT ); KeStallExecutionProcessor(50); // Удерживаем состояние 50 мкс // Удерживая информационные биты, снимаем импульс ACK# WriteControlRegister( pDevExt, bits | CR_INT_ENB | CR_NOT_RST | CR_DEFAULT ); KeStallExecutionProcessor(50); // Удерживаем состояние 50 мкс // Удерживая информационные биты, снимаем импульс ACK# WriteControlRegister( pDevExt, bits | CR_INT_ENB | CR_DEFAULT ); } //========================================================================= // Функция DoNextTransfer безопасно (от вмешательства кода прерывания, ISR // функции) записывает данные в параллельный порт: // BOOLEAN DoNextTransfer ( IN PVOID pContext ) {



PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pContext; UCHAR nextByte = 0x0F & ( pDevExt-&#62deviceOutBuffer[pDevExt-&#62xferCount] ); #if DBG==1 DbgPrint("LPTPORT: DoNextTransfer:\n"); DbgPrint("LPTPORT: Sending 0x%02X to port %X\n", nextByte, pDevExt-&#62portBase); #endif // Отправка полубайта данных. //= 1 =============================================================== // Заглушка CheckIt работает не самым простым образом. // Бит 0 отсылаемого полубайта нужно отправить как бит 0 // в Data Register WriteDataRegister ( pDevExt, nextByte & 0x01); // // Это бит будет считан как бит 3 из Status Register

//= 2 =============================================================== // Биты 1-3 отсылаемого полубайта нужно отправить как // биты 0, 1 и 3 в Control Register UCHAR bits = (nextByte & 0x8) + ((nextByte & 0x6)&#62&#62 1); // Таким образом бит 2 всегда равен 0

bits ^= 0x3; // Инвертируем биты (0 & 1) перед // записью в Control Register // Эти биты будут считаны в Status Register как биты 4,5 и 7 #if DBG==1 DbgPrint("LPTPORT: generating next interrupt...\n"); #endif // Собственно отправляем данные вместе с генерацией // сигнала прерывания: ForceInterrupt( pDevExt, bits ); return TRUE; }

//========================================================================= // Функция ReadDataSafely выполняет чтение данных из устройства // без опасения быть прерванной кодом ISR функции: // BOOLEAN ReadDataSafely ( IN PVOID pContext ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pContext; UCHAR status = ReadStatusRegister( pDevExt ); // Преобазуем полученные через Status register биты в понятную форму UCHAR readByte = ((status & 0x8)&#60&#60 1) | ((status & 0x30)&#60&#60 1) | (status & 0x80); readByte &#62&#62= 4; pDevExt-&#62deviceInBuffer[pDevExt-&#62xferCount++] = readByte;

#if DBG==1 KIRQL currentIrql = KeGetCurrentIrql(); DbgPrint( "LPTPORT: ReadDataSafely, currentIrql=%d ReadStatus = %02X" " ReadByte = %02X\n", currentIrql, status, readByte ); DbgPrint( "LPTPORT: \n"); #endif



pDevExt-&#62xferRest--; // Число непереданных байт уменьшилось return (pDevExt-&#62xferRest&#60 1 ? FALSE : TRUE ); // это значение возвратится // через вызов KeSynchronizeExecution }

//========================================================================= // Функция: DpcForIsr // Назначение: Данная функция начинает работу по "заказу" ISR функции // и выполняет ввод/вывод путем безопасного вызова функций // ReadDataSafely и DoNextTransfer, то есть реализует // низкоуровневый ввод/вывод // Аргументы: Указатель на текущий DPC объект (не используется) // pDeferredContext - контекстный указатель - так передается // указатель на структуру Device Extension // Возвращаемое значение: нет // VOID DpcForIsr( IN PKDPC pDpc, // не используется IN PVOID pDeferredContext, IN PVOID pArg1, // не используется IN PVOID pArg2 // не используется ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDeferredContext; #if DBG==1 KIRQL currentIrql = KeGetCurrentIrql(); DbgPrint("LPTPORT: We are now in DpcForIsr, currentIrql=%d xferCount = %d\n", currentIrql, pDevExt-&#62xferCount ); #endif // Безопасно читаем данные из устройства: BOOLEAN dataNotTransferrered = KeSynchronizeExecution( pDevExt-&#62pIntObj, ReadDataSafely, pDevExt ); if ( dataNotTransferrered ) // остались непереданные данные : { // Если остались данные, то записываем следующую порцию // данных в порт и запускаем прерывание: KeSynchronizeExecution( pDevExt-&#62pIntObj, DoNextTransfer, pDevExt ); } else { #if DBG==1 DbgPrint("LPTPORT: We are now in DpcForIsr, all data transmitted.\n"); #endif } } //=========================================================================



#if DBG==1 KIRQL currentIrql = KeGetCurrentIrql(); DbgPrint("LPTPORT: TransferToUserSafely, currentIrql=%d\n",currentIrql ); DbgPrint(" requested %d bytes, while ready %d bytes.\n", xferReq, pDevExt-&#62xferCount); #endif

if( pDevExt-&#62xferCount&#60 1 || xferReq&#60 1 ) { // Нет никаких полученных данных или нулевой запрос pDevExt-&#62xferSize = 0; return FALSE; } if( xferReq &#62 MAX_BUFFER_SIZE ) xferReq = MAX_BUFFER_SIZE; // Передаем не более данных, чем все, полученные из LPT порта, // оказавшиеся во внутреннем буфере драйвера: if( xferReq &#62 pDevExt-&#62xferCount ) xferReq = pDevExt-&#62xferCount;

// Собственно перенос запрошенных данных в буфер клиента: RtlCopyMemory( pDevExt-&#62pUserBuffer, inBuffer, xferReq );

if( xferReq &#60 pDevExt-&#62xferCount) { // Перемещаем оставшиеся данные к началу буфера: ULONG i=0,j=xferReq; for(; j&#60pDevExt-&#62xferCount; ) inBuffer[i++]=inBuffer[j++]; } pDevExt-&#62xferCount -= xferReq; pDevExt-&#62xferSize = xferReq; #if DBG==1 DbgPrint(" Transferred %d, the rest %d bytes.\n", xferReq, pDevExt-&#62xferCount); #endif return TRUE; } //========================================================================= // Функция: DpcForIsr // Назначение: Данная функция начинает работу по "заказу" ISR функции // и выполняет ввод/вывод путем безопасного вызова функций // ReadDataSafely и DoNextTransfer, то есть реализует // низкоуровневый ввод/вывод // Аргументы: Указатель на текущий DPC объект (не используется) // pDeferredContext - контекстный указатель - так // передается указатель на структуру объекта устройства // Возвращаемое значение: нет // VOID DpcForIsr( IN PKDPC pDpc, // не используется IN PVOID pDeferredContext, // Внимание! Теперь здесь // находится указатель pDevObj ! IN PVOID pArg1, // не используется IN PVOID pArg2 // не используется ) { PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pDeferredContext; PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj-&#62DeviceExtension; PIRP pIrp = pDevObj-&#62CurrentIrp; PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp); ULONG total = pIrpStack-&#62Parameters.DeviceIoControl.InputBufferLength, currentByteNumToWriteToPort = (ULONG)pIrpStack-&#62Parameters.DeviceIoControl.Type3InputBuffer;



#if DBG==1 KIRQL currentIrql = KeGetCurrentIrql(); DbgPrint("LPTPORT: We are now in DpcForIsr, currentIrql=%d xferCount = %d\n", currentIrql, pDevExt-&#62xferCount ); #endif

if( currentByteNumToWriteToPort &#62 0 ) // &#60- т.е. уже были передачи { // Безопасно читаем следующий байт из устройства: KeSynchronizeExecution( pDevExt-&#62pIntObj, ReadDataSafely, pDevExt ); } if ( currentByteNumToWriteToPort &#60 total ) { // Если остались данные, то записываем следующий байт // данных в порт и запускаем прерывание: PUCHAR userBuffer = (PUCHAR)pIrp-&#62AssociatedIrp.SystemBuffer; pDevExt-&#62byteToBeOutToPort = userBuffer[currentByteNumToWriteToPort]; #if DBG==1 DbgPrint("LPTPORT: currentByteNo = %d, byteToBeOutToPort=%02X(hex)\n", currentByteNumToWriteToPort, pDevExt-&#62byteToBeOutToPort ); #endif // Заранее корректируем номер следующего байта для записи в порт // и сохраняем его там же: pIrpStack-&#62Parameters.DeviceIoControl.Type3InputBuffer = (PVOID)( currentByteNumToWriteToPort+1 ); KeSynchronizeExecution( pDevExt-&#62pIntObj, DoNextTransfer, pDevExt ); } else { #if DBG==1 DbgPrint("LPTPORT: We are now in DpcForIsr, all data transmitted.\n"); #endif if(pDevExt-&#62pEvent!=NULL) { // Устанавливаем объект события в сигнальное состояние, // что является сигналом клиету о полностью завершенном // переносе данных в параллельный порт KeSetEvent(pDevExt-&#62pEvent, IO_NO_INCREMENT , FALSE ); //^^^^^^^^^^^^^^^ Замечание А. } // Завершаем обработку текущего IRP пакета. // Поскольку это была обработка пакета от Win32 вызова // DeviceIoControl, который передавал данные в драйвер, // а назад ничего не ожидал, // то указываем, что число байт, возвращаемых клиенту // (третий аргумент вызова CompleteIrp), равно 0: PIRP pIrp = pDevObj-&#62CurrentIrp; CompleteIrp( pIrp, STATUS_SUCCESS, 0 );

// Сообщаем Диспетчеру ввода/вывода, что готовы обработать // следующий пакет: IoStartNextPacket( pDevObj, FALSE ); } return; } //========================================================================= // Функция: DeviceControlRoutine // Назначение: Обрабатывает запросы от Win32 вызова DeviceIoControl. // Аргументы: pDevObj - поступает от Диспетчера ввода/вывода, // pIrp - поступает от Диспетчера ввода/вывода. // Возвращаемое значение: // NTSTATUS - в случае нормального завершения STATUS_SUCCESS // или код ошибки STATUS_Xxx // NTSTATUS DeviceControlRoutine( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION pIrpStack=IoGetCurrentIrpStackLocation(pIrp); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj-&#62DeviceExtension; //------------------------------- #if DBG==1 KIRQL currentIrql = KeGetCurrentIrql(); DbgPrint("LPTPORT: We are now in DeviceControlRoutine, currentIrql=%d\n", currentIrql ); #endif // Диспетчеризация по IOCTL кодам: switch( pIrpStack-&#62Parameters.DeviceIoControl.IoControlCode ) { case IOCTL_SEND_TO_PORT: { // Размер данных, поступивших от клиента драйвера // (см.


таблицу 8.9): ULONG xferSize = pIrpStack-&#62Parameters.DeviceIoControl.InputBufferLength; if( xferSize &#60 1 ) { // Нет данных для отправки в порт: #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_SEND_TO_PORT,\n", " no bytes to transfer.\n"); #endif // Завершение без переноса данных клиенту: return CompleteIrp( pIrp, STATUS_SUCCESS, 0 ); } // if( xferSize &#62 MAX_BUFFER_SIZE ) ? Но размер входящих данных // уже не актуален, поскольку будет использоваться буфер, // поступивший от клиента (точнее, от Диспетчера ввода/вывода). // // Теперь мы ничего не копируем, а просто инициируем процесс // программируемого вывода, откладывая пакет в очередь // необработанных IRP пакетов (не забываем сообщить адрес // процедуры CancelRoutine, которая должна получить управление, // если клиент решит отозвать свой запрос): #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_SEND_TO_PORT,\n" " xfer size is %d Irp is pending.\n", xferSize ); #endif IoMarkIrpPending( pIrp ); IoStartPacket( pDevObj, pIrp, 0, CancelRoutine); return STATUS_PENDING; } case IOCTL_SEND_TO_USER: { // Размер данных, ожидаемых пользователем pDevExt-&#62xferSize = pIrpStack-&#62Parameters.DeviceIoControl.OutputBufferLength; if( pDevExt-&#62xferSize&#62 0 ) { // Согласно таблице 8.9, адрес клиентского буфера при // методе METHOD_BUFFERED в IOCTL запросе находится там // же, что и при обработке обычного запроса Read (см. // первый вариант драйвера, функцию DispatchRead): pDevExt-&#62pUserBuffer = (PUCHAR)pIrp-&#62AssociatedIrp.SystemBuffer; // Пытаемся безопасно перенести данные: KeSynchronizeExecution( pDevExt-&#62pIntObj, TransferToUserSafely, pDevExt ); } // Завершаем обработку IRP пакета: #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_SEND_TO_USER,\n" " %d bytes transferred to user.\n", pDevExt-&#62xferSize); #endif return CompleteIrp( pIrp, STATUS_SUCCESS, pDevExt-&#62xferSize); } case IOCTL_TAKE_EVENT: { // Размер данных, поступивших от клиента драйвера ULONG xferFromDriverSize = pIrpStack-&#62Parameters.DeviceIoControl.OutputBufferLength; if( xferFromDriverSize &#60 sizeof(HANDLE)) { #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_TAKE_EVENT,\n " "event info can not be transferred due to 0 buffer size.\n"); #endif status = STATUS_INVALID_PARAMETER; // Завершение без переноса данных клиенту: return CompleteIrp( pIrp, status, 0 ); // 0 - Нет переноса } if(pDevExt-&#62pEvent==NULL) // Объект события еще не был создан { // для данного клиента. // Создаем объект события типа // SynchronizationEvent, с автоматическим переходом в // несигнальное состояние: #define EVENT_NAME L"\\BaseNamedObjects\\LPTPORT_EVENT" UNICODE_STRING eventName; RtlInitUnicodeString( &eventName, EVENT_NAME );



HANDLE hEvent; // Объект события - без имени: PKEVENT pEvent = IoCreateSynchronizationEvent( &eventName, &hEvent ); if(pEvent==NULL) { // Объект события не был создан #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_TAKE_EVENT,\n" " error - event wasn't created.\n"); #endif // Завершение без переноса данных клиенту: return CompleteIrp( pIrp, STATUS_UNSUCCESSFUL, 0 ); } #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_TAKE_EVENT,\n" " event named %ws successfully created.\n", eventName.Buffer); #endif pDevExt-&#62pEvent = pEvent; pDevExt-&#62hEvent = hEvent; // Предустанавливаем объект события в несигнальное состояние: KeClearEvent(pDevExt-&#62pEvent); } // Cообщаем об объекте события клиенту - передаем дескриптор RtlCopyMemory( pIrp-&#62AssociatedIrp.SystemBuffer, &pDevExt-&#62hEvent, sizeof(HANDLE) ); #if DBG==1 DbgPrint( "LPTPORT: DeviceControlRoutine: IOCTL_TAKE_EVENT,\n" " event handle = %04X(hex) is sent to user.\n", pDevExt-&#62hEvent); #endif return CompleteIrp( pIrp, STATUS_SUCCESS, sizeof(HANDLE) ); } case IOCTL_CLOSE_EVENT: { if(pDevExt-&#62pEvent!=NULL) // объект события был создан { NTSTATUS sts = ZwClose(pDevExt-&#62hEvent); #if DBG==1 if(sts==STATUS_SUCCESS) DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_CLOSE_EVENT,\n" " event handle closed with STATUS_SUCCESS.\n"); DbgPrint("LPTPORT: DeviceControlRoutine: IOCTL_CLOSE_EVENT,\n" " event (handle %04Xhex) closing status = %d.\n", pDevExt-&#62hEvent, sts ); #endif // Во всяком случае, эти событием пользоваться не будем: pDevExt-&#62pEvent = NULL; pDevExt-&#62hEvent = NULL; } return CompleteIrp( pIrp, STATUS_SUCCESS, 0 ); } default: { #if DBG==1 DbgPrint("LPTPORT: DeviceControlRoutine: bad IOCTL code."); #endif // Завершение без переноса данных клиенту: status = STATUS_INVALID_DEVICE_REQUEST; CompleteIrp( pIrp, status, 0 ); } } // &#60- конец оператора "switch" return status; } //=================================================================================



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

Драйвер обрабатывает четыре IOCTL кода, которые может задать клиент при своем обращении к драйверу (из приложения пользовательского режима это делается через Win32 вызов DeviceIoControl):

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


  • IOCTL_SEND_TO_USER обеспечивает передачу данных, накопившихся на текущий момент в рабочем буфере deviceInBuffer (см. описание структуры расширения объекта устройства для второго варианта драйвера в файле Driver.h), то есть полученных из параллельного порта через механизм прерываний.


  • IOCTL_TAKE_EVENT передает клиенту дескриптор объекта события, если оно было создано или создает его, сохраняя данные в полях pEvent и hEvent структуры расширения объекта устройства (см. описание структуры расширения объекта устройства для второго варианта драйвера в файле Driver.h).


  • IOCTL_CLOSE_EVENT закрывает дескриптор текущего используемого объекта события.


  • Если речь идет о взаимодействии приложения пользовательского режима с драйвером, то, как правило, событие создается именно в приложении пользовательского режима. Операционная система по Win32 вызову CreateEvent создает событие как объект режима ядра, возвращая приложению открытый дескриптор. Этот дескриптор передается в драйвер (через IOCTL запрос), а драйвер при помощи вызова ObReferenceObjectByHandle

    получает ссылку на созданный объект события по указателю на существующий объект (ссылка на объект является в режиме ядра более привычным способом обращения с событием, мьютексом и т.д.). Описание подобного способа совместного использования события в режиме ядра и пользовательском режиме несложно отыскать в литературе или в интернете, например, на Интернет сайте codeguru.com. Особенностью же данного драйвера является то, что событие изначально создается в драйвере (в режиме ядра), и лишь после этого соответствующий ему дескриптор передается клиенту.


    Для этого используется практически не применяемый разработчиками вызов IoCreateSynchronizationEvent

    (не применяемый по причине плохого описания в документации пакета DDK всех имеющихся на сегодня версий). Между тем этот удобный вызов, вся "особая" специфика использования которого состоит в правильном задании имени создаваемого объекта (это же относится и к вызову режима ядра IoCreateNotificationEvent), возвращает не только указатель на создаваемый объект, но и его дескриптор одновременно.

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

    Данный драйвер (и первый, и второй варианты) в том виде как они приведены, не позволяют одновременно получить доступ к нему из нескольких приложений, поскольку объект устройства создается для эксклюзивного доступа. По этой причине Win32 вызов CreateFile, который должен получить дескриптор доступа к драйверу, завершается с ошибкой 5 (отказано в доступе). Для того чтобы исправить ситуацию необходимо предпоследний параметр вызова IoCreateDevice в драйверной функции CreateDevice задать равным FALSE (вместо TRUE, как указано изначально).
    Еще один вопрос-опасение, который может возникнуть у внимательного читателя: как обстоят дела с доступом ко входным буферам с данными и для данных в смысле корректности уровня IRQL, на которых к ним обращается драйвер? Например, копирование в пользовательский буфер полученных данных по IOCTL запросу IOCTL_SEND_TO_USER производится функцией TransferToUserSafely, защищенной KeSynchronizeExecution, то есть работающей на уровне IRQL, равном IRQL процедуры обслуживания прерывания Isr. Почему не происходит сбоя, поскольку достоверно известно, что пользовательские области находятся в странично организованной памяти?

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

  • в первом варианте драйвера было указано, что объект устройства имеет метод буферизации при запросах на запись/чтение pDevObj-&#62Flags |= DO_BUFFERED_IO;


  • во втором варианте аналогичное указание Диспетчер ввод/вывода получает в каждом IOCTL коде, когда при его определении явно указывается METHOD_BUFFERED.


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


    Содержание раздела