注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

从C开始

 
 
 

日志

 
 

IRP的同步  

2010-12-24 13:20:48|  分类: 驱动编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

同步操作设备

如果需要同步操作设备,那么在打开设备的时候就要指定以“同步”的方式打开设备。打开设备用CreateFile函数,其函数声明如下:

HANDLE CreateFile(

  LPCTSTR lpFileName,          // pointer to name of the file

  DWORD dwDesiredAccess,       // access (read-write) mode

  DWORD dwShareMode,           // share mode

  LPSECURITY_ATTRIBUTES lpSecurityAttributes,

                               // pointer to security attributes

  DWORD dwCreationDisposition,  // how to create

  DWORD dwFlagsAndAttributes,  // file attributes

  HANDLE hTemplateFile         // handle to file with attributes to copy

);

CreateFile函数的第六个参数dwFlagsAndAttributes是同步异步操作的关键。如果这个参数中没有设置FILE_FLAG_OVERLAPPED属性,则以后对该设备的操作都是同步的,否则所有操作都是异步的。

 

         对设备操作的Win32 API函数,例如,ReadFile函数,都会提供一个OVERLAPPED结构的参数。如下:

BOOL ReadFile(

  HANDLE hFile,                // handle of file to read

  LPVOID lpBuffer,             // pointer to buffer that receives data

  DWORD nNumberOfBytesToRead,  // number of bytes to read

  LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read

  LPOVERLAPPED lpOverlapped    // pointer to structure for data

);

在同步操作设备时,其lpOverlapped参数设置为NULL.

 

示例代码:

#include <windows.h>

#include <stdio.h>

 

int main(void)

{

         HANDLE hFile = CreateFile("c:\\test.dat",

                   FILE_GENERIC_READ | FILE_GENERIC_WRITE,

                   0, NULL, CREATE_ALWAYS,

                   FILE_ATTRIBUTE_NORMAL,  //此处没有设置FILE_FLAG_OVERLAPPED属性

                   NULL);

         if (hFile == INVALID_HANDLE_VALUE)

         {

                   printf("创建文件失败, 错误代码:%d\n", GetLastError());

                   return -1;

         }

 

         char* Buffer = "fuckyou";

         DWORD dwRet;

         //最后一个参数为NULL,标明采用同步方式写文件

         BOOL bRet = WriteFile(hFile, Buffer, strlen(Buffer), &dwRet, NULL);

 

         CloseHandle(hFile);

         return 0;

}

 

 

异步操作设备(方式一)

异步操作设备时主要需要设置OVERLAPPED结构:

typedef struct _OVERLAPPED {

    DWORD  Internal;

    DWORD  InternalHigh;

    DWORD  Offset;                 //操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量是用一个64位整型表示,这个是偏移量的低32

    DWORD  OffsetHigh;  //偏移量的高32

    HANDLE hEvent;  //这个事件用于该操作完成后通知应用程序

} OVERLAPPED;

注意:在使用OVERLAPPED结构前,需要对其内部清零,并为其创建事件。

 

示例代码:

#include <windows.h>

#include <stdio.h>

 

int main(void)

{

         HANDLE hFile = CreateFile("c:\\test.dat",

                   FILE_GENERIC_READ | FILE_GENERIC_WRITE,

                   0, NULL, CREATE_ALWAYS,

                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

                   NULL);

         if (hFile == INVALID_HANDLE_VALUE)

         {

                   printf("创建文件失败, 错误代码:%d\n", GetLastError());

                   return -1;

         }

 

         char* Buffer = "fuckyou";

         DWORD dwRet;

 

         OVERLAPPED ovlp = {0};

         HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

         ovlp.hEvent = hEvent;

         BOOL bRet = WriteFile(hFile, Buffer, strlen(Buffer), &dwRet, &ovlp);

         //…….

         //这里可以执行一些其他的操作,这些操作可以和写操作并行

         //到某一时刻,等待读操作完成

         WaitForSingleObject(hEvent, INFINITE);

         //……..

         //继续其他操作

         CloseHandle(hFile);

         CloseHandle(hEvent);

         return 0;

}

 

 

异步操作设备(方式二)

除了ReadFileWriteFile函数外,还有两个API函数可以实现异步读写,这就是ReadFileExWriteFileEx函数。ReadFileWriteFile函数既可以支持同步读写操作,又可以支持异步读写操作,而ReadFileExWriteFileEx函数是专门用于异步读写操作的。

BOOL ReadFileEx(

  HANDLE hFile,                // handle of file to read

  LPVOID lpBuffer,             // pointer to buffer

  DWORD nNumberOfBytesToRead,  // number of bytes to read

  LPOVERLAPPED lpOverlapped,   // pointer to offset

  LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

                               // pointer to completion routine

);

注意,这里提供的OVERLAPPED结构不需要提供事件句柄ReadFileEx函数将读请求发送到驱动程序后立刻返回。驱动程序在结束读请求后,会通过调用ReadFileEx提供的回调函数,既lpCompletionRoutine参数提供的函数地址。这类似一个软中断,也就是当读操作结束后,操作系统立刻调用回调函数。Windows将这种机制称为异步过程调用(APC – Asynchronous Procedure Call

然而,APC的回调函数调用是有条件的,只有线程处于警惕(Alert)状态时,回调函数才有可能被调用。有多个函数可以使线程进入警惕状态,如SleepExWaitForSingleObjectExWaitForMultipleObjectEx函数等。这些Win32 API函数中都有一个BOOL类型的参数bAlertable,当设置为TRUE时,线程就进入警惕状态。这时,告诉系统,线程已经运行到了一个点,此时可以调用回调函数。

回调函数会报告本次操作完成状况,比如是成功还是失败,同时会报告本次读取操作实际读取的字节数等。下面是一般回调函数的声明:

VOID CALLBACK FileIOCompletionRoutine(

  DWORD dwErrorCode,                // completion code

  DWORD dwNumberOfBytesTransfered,  // number of bytes transferred

  LPOVERLAPPED lpOverlapped         // pointer to structure with I/O

                                    // information

);

 

示例代码:

#include <windows.h>

#include <stdio.h>

 

VOID CALLBACK FuckYou(DWORD dwErrorCode,               

                                                 DWORD dwNumberOfBytesTransfered, 

                                                 LPOVERLAPPED lpOverlapped)

{

         printf("调用回调函数!\n");

}

 

int main(void)

{

         HANDLE hFile = CreateFile("c:\\test.dat",

                   FILE_GENERIC_READ | FILE_GENERIC_WRITE,

                   0, NULL, CREATE_ALWAYS,

                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

                   NULL);

         if (hFile == INVALID_HANDLE_VALUE)

         {

                   printf("创建文件失败, 错误代码:%d\n", GetLastError());

                   return -1;

         }

 

         char* Buffer = "FuckYou";

         //定义OVERLAPPED结构,这里不需要Event事件

         OVERLAPPED ovlp = {0};

         BOOL bRet = WriteFileEx(hFile, Buffer, strlen(Buffer), &ovlp, FuckYou);

         //....

         //其他操作

         //运行到这个点,必须等待写操作完成,否则不能继续完成别的任务

         SleepEx(0, TRUE);

         //....

         //其他操作

         printf("fuck\n");

         CloseHandle(hFile);

         return 0;

}

IRP的同步 - Fly - 从C开始
 

 

 

IRP的异步完成

IRP被“异步完成”指的是不在派遣函数中调用IoCompleteRequest内核函数。调用IoCompleteRequest函数意味着IRP请求的结束,也标志着本次对设备操作的结束。

如果IRP是被异步完成,而发起IRP的应用程序会有三种形式发起IRP请求,分别是ReadFile函数同步读取设备,用ReadFile函数异步读取设备,用ReadFileEx异步读取数据。一下分别给出这三种读取形式的异同:

1.       ReadFile同步读取:当派遣函数退出时,由于IoCompleteRequest函数没有被调用,IRP请求没有被结束。因此ReadFile函数会一直等待,知道操作被结束。

2.       ReadFile异步读取:当派遣函数退出时,由于IoCompleteRequest函数没有被调用,IRP请求没有被结束。但ReadFile会立即返回,返回值为FALSE。通过GetLastError函数会返现错误码是ERROR_IO_PENDING,表明当前操作被“挂起”。这不是真正的操作错误,而是意味着ReadFile并没有真正的完成操作,ReadFile只是异步的返回。当IRPQ请求被真正结束,既调用了IoCompleteRequestReadFile函数提供的OVERLAPPED结构体重的Event才会被设置。这个事件可以通知应用程序ReadFile的请求真正的被执行完毕。

3.       ReadFileEx异步读取:前面部分与ReadFile相同。当是在IRP呗结束时,既调用了IoCompleteRequest后,ReadFileEx提供的回调函数会被插入到APC队列中。一旦线程进入警惕状态,线程的APC队列会自动出队列,进而ReadFileEx提供的回调函数被调用,这相当于操作系统通知应用程序操作真正的执行完毕。

 

如果派遣函数不调用IoCompleteRequest函数,则需要告诉操作系统此IRP处于“挂起”状态。这需要调用内核函数IoMarkIrpPending。同时,派遣函数应该返回STATUS_PENDING

VOID 
  IoMarkIrpPending(
    IN OUT PIRP  Irp
    );

 

我们现在假设IRP_MJ_READ的派遣函数仅仅是返回“挂起”。应用程序在关闭设备句柄的时候会产生IRP_MJ_CLEARUP类型的IRP。在IRP_MJ_CLEARUP的派遣函数中结束那些挂起的IRP_MJ_READ;

为了能存储有哪些IRP_MJ_READ IRP被挂起,这里我们需要使用一个队列,也就是把每个挂起的IRP_MJ_READ的指针都插入队列,最后在IRP_MJ_CLEARUP的派遣函数将一个个IRP出队,并且调用IoCompleteRequest函数结束他们。

 

示例代码:

定义队列的数据结构:

typedef struct _MY_IRP_LINKLIST

{

         PIRP pIrp;

         LIST_ENTRY ListEntry;

}MY_IRP_LIST, *PMY_IRP_LIST;

 

定义设备扩展:

typedef struct _DEVICE_EXTENSION {

         PDEVICE_OBJECT pDevice;

         UNICODE_STRING ustrDeviceName;      //设备名称

         UNICODE_STRING ustrSymLinkName;   //符号链接名

         PLIST_ENTRY pIRPLinkListHead;               //链表头

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

 

IRP_MJ_READ的派遣函数:

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)

{

         KdPrint(("进入IRP_MJ_READ派遣函数!\n"));

         PDEVICE_EXTENSION pDevEx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

         PMY_IRP_LIST pMyIrpList = (PMY_IRP_LIST)ExAllocatePool(PagedPool, sizeof(MY_IRP_LIST));

         pMyIrpList->pIrp = pIrp;

         //把挂起的IRP插入队列

         InsertHeadList(pDevEx->pIRPLinkListHead, &pMyIrpList->ListEntry);

         //使IRP挂起

         IoMarkIrpPending(pIrp);

         KdPrint(("离开IRP_MJ_READ派遣函数!\n"));

         //返回PENDING状态

         return STATUS_PENDING;

}

 

IRP_MJ_CLEARUP的派遣函数

NTSTATUS HelloDDKCleraUp(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)

{

         KdPrint((“进入IRP_MJ_CLEARUP派遣函数!\n”));

 

         PDEVICE_EXTENSION pDevEx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

         //1.将存在队列中的IRP逐个出对,并处理之

         PMY_IRP_LIST myIrpList;

         while (!IsListEmpty(pDevEx->pIRPLinkListHead))

         {

                   PLIST_ENTRY pListEntry = RemoveHeadList(pDevEx->pIRPLinkListHead);

                   myIrpList = CONTAINING_RECORD(pListEntry, MY_IRP_LIST, ListEntry);

                   myIrpList->pIrp->IoStatus.Information = 0;

                   myIrpList->pIrp->IoStatus.Status = STATUS_SUCCESS;

                   IoCompleteRequest(myIrpList->pIrp, IO_NO_INCREMENT);

                   ExFreePool(myIrpList);

         }
         //
处理IRP_MJ_CLEARUPIRP

         pIrp->IoStatus.Information = 0;

         pIrp->IoStatus.Status = STATUS_SUCCESS;

         IoCompleteRequest(pIrp, IO_NO_INCREMENT);

         KdPrint((“离开IRP_MJ_CLEARUP派遣函数!\n”));

         return STATUS_SUCCESS;

}

 

用户层代码:

#include <windows.h>

#include <stdio.h>

 

int main(void)

{

         HANDLE hFile = CreateFile("\\\\.\\HelloDDK", GENERIC_READ|GENERIC_WRITE,

                   0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

                   NULL);

         if (hFile == INVALID_HANDLE_VALUE)

         {

                   printf("打开设备失败!\n");

                   return -1;

         }

        

         UCHAR readBuffer[10];

         DWORD dwRet;

         OVERLAPPED ovlp1 = {0};

         OVERLAPPED ovlp2 = {0};

         //连续异步读取两次,产生两个挂起的IRP_MJ_READ

         BOOL bRet = ReadFile(hFile, readBuffer, 10, &dwRet, &ovlp1);

         if (!bRet && GetLastError() == ERROR_IO_PENDING)

         {

                   printf("挂起!\n");

         }

 

         bRet = ReadFile(hFile, readBuffer, 10, &dwRet, &ovlp2);

         if (!bRet && GetLastError() == ERROR_IO_PENDING)

         {

                   printf("挂起!\n");

         }

         Sleep(10000);

         //调用CloseHandle时系统产生IRP_MJ_CLEARUPIRP

         //进而对被挂起的线程执行完成操作

         CloseHandle(hFile);

         return 0;

}

 

 

取消IRP

前面提到了在设备句柄被关闭的时候,将“挂起”的IRP结束。还有另外一个办法将“挂起”的IRP逐个结束,这就是取消IRP请求。内核函数IoSetCancelRoutine可以设置取消IRP请求的回调函数,其声明如下:

PDRIVER_CANCEL 
  IoSetCancelRoutine(
    IN PIRP  Irp,  //
需要取消的IRP
    IN PDRIVER_CANCEL  CancelRoutine //IRP
取消后调用的函数
    );

         我们可以用IoCancelIrp函数指定取消IRP请求。在IoCancelIrp函数内部,需要进行同步。DDKIoCancelIrp内部使用一个叫做cancel的自旋锁,用来进行同步。

         IoCancelIrp在内部会首先获得该自旋锁,然后会调用刚才提到的IoSetCancelRoutine函数设置的取消回调函数。因此,释放该自旋锁的任务就留给了取消回调函数。获得“取消自旋锁”的函数是IoAcquireCancelSpinLock,释放“取消自旋锁”的函数是IoReleaseCancelSpinLock

         在应用程序中,可以调用Win32 API函数取消IRP操作。在CancelIo的内部会枚举所有没有完成的IRP,然后依次调用IoCancelIrp。另外,如果应用程序没有调用CancelIo函数,应用程序在关闭设备句柄时同样会自动调用CnacelIo

 

示例代码:

取消IRP的回调函数:

VOID

CancelIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )

{

         KdPrint(("进入取消函数!\n"));

         //设置IRP为完成状态

         Irp->IoStatus.Information = 0;

         Irp->IoStatus.Status = STATUS_CANCELLED;

         IoCompleteRequest(Irp, IO_NO_INCREMENT);

         //释放cancel自旋锁

         IoReleaseCancelSpinLock(Irp->CancelIrql);

         KdPrint(("离开取消函数!\n"));

}

 

IRP_MJ_READ派遣函数:

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

         KdPrint(("进入IRP_MJ_READ派遣函数!\n"));

        

         //设置IRP的取消执行函数

IoSetCancelRoutine(pIrp, CancelIrp);

//使IRP挂起

         IoMarkIrpPending(pIrp);

         KdPrint(("离开IRP_MJ_READ派遣函数!\n"));

         return STATUS_PENDING;

}

 

应用层代码:

#include <windows.h>

#include <stdio.h>

 

int main(void)

{

         HANDLE hFile = CreateFile("\\\\.\\HelloDDK", GENERIC_READ|GENERIC_WRITE,

                   0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

                   NULL);

         if (hFile == INVALID_HANDLE_VALUE)

         {

                   printf("打开设备失败!\n");

                   return -1;

         }

        

         UCHAR readBuffer[10];

         DWORD dwRet;

         OVERLAPPED ovlp1 = {0};

         OVERLAPPED ovlp2 = {0};

         //创建两个异步读取

         BOOL bRet = ReadFile(hFile, readBuffer, 10, &dwRet, &ovlp1);

         if (!bRet && GetLastError() == ERROR_IO_PENDING)

         {

                   printf("挂起!\n");

         }

 

         bRet = ReadFile(hFile, readBuffer, 10, &dwRet, &ovlp2);

         if (!bRet && GetLastError() == ERROR_IO_PENDING)

         {

                   printf("挂起!\n");

         }

         Sleep(10000);

         //显示调用CancelIo,其实在关闭设备句柄的时候会自动调用此函数

         CancelIo(hFile);

         CloseHandle(hFile);

         return 0;

}

 

StartIo例程 串行化执行IRP请求

在很多情况下,对设备的操作必须是串行执行,而不能并行执行。例如,对串口的操作,假如有N个线程同时操作串口设备时,必须将这些操作排队,然后一一进行处理。

DDK为了简化程序员的工作,为程序员提供了一个内部队列,并将IRPStartIo例程串行处理。

操作系统为程序员提供了一个IRP队列来实现串行,这个队列用KDEVICE_QUEUE数据结构表示:

typedef struct _KDEVICE_QUEUE {
CSHORT Type;
CSHORT Size;
LIST_ENTRY devicelisthead;
KSPIN_LOCK Lock;
BOOLEAN Busy;
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *RESTRICTED_POINTER PRKDEVICE_QUEUE;

 

这个队列的队列头保存在设备对象的DeviceObject->DeviceQueue子域中。插入和删除队列中的元素都是操作系统负责的。在使用这个队列的时候,需要向操作系统提供一个叫做StartIo的例程,如下:

extern "C" NTSTATUS DriverEntry (

                            IN PDRIVER_OBJECT pDriverObject,

                            IN PUNICODE_STRING pRegistryPath   )

{

         。。。

         //设置StartIo例程

         pDriverObject->DriverStartIo = HelloDDKStartIo;

         。。。

}

 

这个StartIo例程运行在DISPATCH_LEVEL级别,因此这个例程不会被线程所打断,所以我们在声明时要加上#pragma LOCKEDCODE修饰符。

#pragma LOCKEDCODE

VOID HelloDDKStartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP  Irp)

{

         ...

}

 

派遣函数如果想把IRP串行化,只需要在IRP的派遣函数中调用IoStartPacket函数,即可将IRP插入串行化队列了。IoStartPacket函数首先判断当前设备是“忙”还是“空闲”。如果设备空闲,则提升当前IRQLDISPATCH_LEVEL级别,并进入StartIo例程“串行”处理该IRP。如果设备“忙”,则将IRP插入串行队列后返回。

另外,在StartIo例程结束前,应该调用IoStartNextPacket函数,其作用是从队列中抽取下一个IRP,并将这个IRP作为参数调用StartIo例程。

示例代码:

IRP_MJ_READ派遣函数:

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

KdPrint(("进入IRP_MJ_READ派遣函数!\n"));

//IRP设为挂起状态

IoMarkIrpPending(pIrp);

//IRP插入系统串行化队列

IoStartPacket(pDevObj, pIrp, 0 ,OnCancelIrp);

KdPrint(("离开IRP_MJ_READ派遣函数!\n"));

return STATUS_PENDING;

}

 

在派遣函数中调用IoStartPacket内核函数指定取消例程:

VOID OnCancelIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )

{

         KdPrint(("进入取消函数!\n"));

         if (Irp == DeviceObject->CurrentIrp)

         {

                   //当前IRP正由StartIo处理

 

                   KIRQL odlirql = Irp->CancelIrql;

                   //释放cancel自旋锁

                   IoReleaseCancelSpinLock(Irp->CancelIrql);

                   //继续下一个IRP

                   IoStartNextPacket(DeviceObject, TRUE);

                   KeLowerIrql(odlirql);

         }

         else

         {

                   //从设备队列中将该IRP抽取出来

                   KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,

                                                                           &Irp->Tail.Overlay.DeviceQueueEntry);

                   //释放自旋锁

                   IoReleaseCancelSpinLock(Irp->CancelIrql);

         }

         Irp->IoStatus.Information = 0;

         Irp->IoStatus.Status = STATUS_CANCELLED;

         IoCompleteRequest(Irp, IO_NO_INCREMENT);

         KdPrint(("离开取消函数!\n"));

}

 

StartIo例程

#pragma LOCKEDCODE

VOID HelloDDKStartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP  Irp)

{

         KdPrint(("进入StartIo处理例程!\n"));

         KIRQL oldIrp;

         //获取cancel自旋锁

         IoAcquireCancelSpinLock(&oldIrp);

         if (Irp != DeviceObject->CurrentIrp || Irp->Cancel)

         {

                  //如果当前有正在处理的IRP,则简单的入队列,并直接返回

                   //入队列的工作由系统完成

                   IoReleaseCancelSpinLock(oldIrp);

                   KdPrint(("离开StartIo处理例程!\n"));

                   return;

         }

         else

         {

                   //由于正在处理该IRP,所以不允许调用取消例程

                   //因此将此IRP的取消例程设置为NULL

                   IoSetCancelRoutine(Irp ,NULL);

                   //释放自旋锁

                   IoReleaseCancelSpinLock(oldIrp);

         }

         KEVENT event;

         KeInitializeEvent(&event, NotificationEvent, FALSE);

         //定义一个三秒钟的延时

         LARGE_INTEGER time;

         time.QuadPart = -3*1000*1000*10;

         //等三秒,模拟IRP操作需要大约三秒钟时间

         KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &time);

         Irp->IoStatus.Information = 0;

         Irp->IoStatus.Status = STATUS_SUCCESS;

         IoCompleteRequest(Irp, IO_NO_INCREMENT);

         //在队列中读取下一个IRP,并进行StartIo

         IoStartNextPacket(DeviceObject, TRUE);

         KdPrint(("离开StartIo处理例程!\n"));

}

 

应用层代码:

#include <windows.h>

#include <stdio.h>

#include <process.h>

 

UINT WINAPI ThreadProc(LPVOID lpParam)

{

         printf("进入新建的线程!\n");

         OVERLAPPED ovlp = {0};

         ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

         CHAR readBuffer[10];

         DWORD dwRet;

         BOOL bRet = ReadFile(*(PHANDLE)lpParam, readBuffer, 10, &dwRet, &ovlp);

         //可以试验下取消例程

         //CancelIo(*(PHANDLE)lpParam);

         WaitForSingleObject(ovlp.hEvent, INFINITE);

         return 0;

}

 

int main(void)

{

         HANDLE hFile = CreateFile("\\\\.\\HelloDDK", GENERIC_READ|GENERIC_WRITE,

                   0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

                   NULL);

         if (hFile == INVALID_HANDLE_VALUE)

         {

                   printf("打开设备失败!\n");

                   return -1;

         }

        

         HANDLE hThread[2];

         hThread[0] = (HANDLE)_beginthreadex(NULL, 0 ,ThreadProc, &hFile, 0 , NULL);

         hThread[1] = (HANDLE)_beginthreadex(NULL, 0 ,ThreadProc, &hFile, 0 , NULL);

 

         WaitForMultipleObjects(2, hThread, TRUE, INFINITE);

         CloseHandle(hFile);

         return 0;

}

 

 

自定义StartIo

我们发现,系统为我们提供的StartIo虽然可以很方便的将IRP串行化,但是存在一个问题,就是读写操作都被一起串行化。而我们有时候需要将读,写分开串行化。此时,希望有两个队列。一个队列串行IRP_MJ_READ类型的IRP,另一个队列串行IRP_MJ_WRITE类型的IRP

此时我们需要自定义StartIo,自定义StartIo类似于前面介绍的StartIo例程,不同的是程序要需要自己维护IRP队列。程序员可以灵活的维护多个队列,分别应用于不同的IRP

 

DDK提供KDEVICE_QUEUE数据结构存储队列:

typedef struct _KDEVICE_QUEUE {
CSHORT Type;
CSHORT Size;
LIST_ENTRY devicelisthead;
KSPIN_LOCK Lock;
BOOLEAN Busy;
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *RESTRICTED_POINTER PRKDEVICE_QUEUE;

 

队列中每个元素用KDEVICE_QUEUE_ENTRY数据结构表示:

typedef struct _KDEVICE_QUEUE _ENTRY{
LIST_ENTRY  DeviceListEntry;

ULONG  SortKey;

BOOLEAN  Inserted;
} KDEVICE_QUEUE_ENTRY, *PKDEVICE_QUEUE_ENTRY, *RESTRICTED_POINTER PRKDEVICE_QUEUE_ENTRY;

 

在使用队列前,应该初始化队列:

VOID 
  KeInitializeDeviceQueue(
    IN PKDEVICE_QUEUE  DeviceQueue
    );

队列应该存储在设备扩展中,在初始化设备的时候一起初始化该队列。

 

插入队列:

BOOLEAN 
  KeInsertDeviceQueue(
    IN PKDEVICE_QUEUE  DeviceQueue, //
要插入的队列
    IN PKDEVICE_QUEUE_ENTRY  DeviceQueueEntry //
要插入的元素
    );

该函数的返回值为BOOL值,如果当前设备不忙,则可以直接处理该IRP,因此这时候不需要插入队列,返回FALSE。如果设备正在处理别的请求,这时候需要将IRP插入队列,这时候返回TRUE

 

删除元素:

PKDEVICE_QUEUE_ENTRY 
  KeRemoveDeviceQueue(
    IN PKDEVICE_QUEUE  DeviceQueue
    );

 

示例代码:

自定义设备扩展:

typedef struct _DEVICE_EXTENSION {

         PDEVICE_OBJECT pDevice;

         UNICODE_STRING ustrDeviceName;      //设备名称

         UNICODE_STRING ustrSymLinkName;   //符号链接名

         KDEVICE_QUEUE deviceQueue;              //支持串行化的队列

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

注意:上面的代码仅是为了演示,所以只定义了一个队列,通常我们会定义多个队列,以此来分别串行处理不同的IRP请求。

 

IRP_MJ_READ派遣函数

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

         KdPrint(("进入IRP_MJ_READ派遣函数!\n"));

         //获得设备扩展

         PDEVICE_EXTENSION pdex = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

         //IRP设置为挂起

         IoMarkIrpPending(pIrp);

         //设置取消例程

         IoSetCancelRoutine(pIrp, OnCancelIrp);

         //提升IRPDISPATCH_LEVEL

         KIRQL oldIrql;

         KeRaiseIrql(DISPATCH_LENGTH, &oldIrql);

         KdPrint(("HelloDDKRead IRP : %x\n", pIrp));

         KdPrint(("DeviceQueueEntry : %x\n",&pIrp->Tail.Overlay.DeviceQueueEntry));

         //插入设备队列

         if (!KeInsertDeviceQueue(&pdex->deviceQueue, &pIrp->Tail.Overlay.DeviceQueueEntry))

         {

                   //如果设备空闲”,就立即调用MyStartIo例程处理IRP

                   MyStartIo(pDevObj, pIrp);

         }       

         //IRP降至原来的IRQL级别

         KeLowerIrql(oldIrql);

         KdPrint(("离开IRP_MJ_READ派遣函数!\n"));

return STATUS_PENDING;

}

 

自定义的StartIo函数

#pragma LOCKEDCODE

//让函数运行在非分页内存

VOID MyStartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP  firstIrp)

{

         KdPrint(("进入MyStartIo处理例程!\n"));

         //获得设备扩展

         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

         PKDEVICE_QUEUE_ENTRY pDeviceQueueEntry;

         PIRP pIrp = firstIrp;

         do

         {

                   KEVENT event;

                   //初始化同步事件

                   KeInitializeEvent(&event, NotificationEvent, FALSE);

                  //定义一个三秒钟的延时

                   LARGE_INTEGER time;

                   time.QuadPart = -3*1000*1000*10;

                   //等三秒,模拟IRP操作需要大约三秒钟时间

                   KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &time);

                   KdPrint(("Complete a irp:%x\n", pIrp));

                   pIrp->IoStatus.Information = 0;

                   pIrp->IoStatus.Status = STATUS_SUCCESS;

                   IoCompleteRequest(pIrp, IO_NO_INCREMENT);

                   //取出队列中的下一个元素

                   pDeviceQueueEntry = KeRemoveDeviceQueue(&pdx->deviceQueue);

                   KdPrint(("pDeviceQueueEntry:%x\n", pDeviceQueueEntry));

                   if (pDeviceQueueEntry == NULL)

                   {

                            break;

                   }

                   pIrp = CONTAINING_RECORD(pDeviceQueueEntry, IRP, Tail.Overlay.DeviceQueueEntry);

         } while (1);

         KdPrint(("离开MyStartIo处理例程!\n"));

}

  评论这张
 
阅读(1722)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018