1.DeviceIoControl的同步和异步操作:
IRP同步操作示意图
同步操作时,在DeviceIoControl的内部,会调用WaitForSingleObject函数去等待一个事件,这个事件知道IRP被结束时,才会被触发。DeviceIoControl会暂时进入睡眠状态,直到IRP被结束。而对于异步操作:
IRP异步操作示意图
在异步操作的情况下,当DeviceIoControl被调用时,其内部会产生IRP,并将该IRP传递给驱动内部的派遣函数。但此时DeviceIoControl函数不会等待该IRP结束,而是直接返回。当IRP经过以一段时间被结束时,操作系统会触发一个IRP相关事件。这个事件可以通过应用程序被执行完毕。
2.同步操作设备
如果需要同步操作设备,那么在打开设备的时候就要指定以“同步”的方式打开设备。打开设备用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.
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #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 = "buffer"; DWORD dwRet; //最后一个参数为NULL,标明采用同步方式写文件 BOOL bRet = WriteFile(hFile, Buffer, strlen(Buffer), &dwRet, NULL); CloseHandle(hFile); return 0; } |
3.异步操作设备(方式一)
异步操作设备时主要需要设置OVERLAPPED结构:
typedef struct _OVERLAPPED { 、
DWORD Internal;
DWORD InternalHigh;
DWORD Offset; //操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量是用一个64位整型表示,这个是偏移量的低32位
DWORD OffsetHigh; //偏移量的高32位
HANDLE hEvent; //这个事件用于该操作完成后通知应用程序
} OVERLAPPED;
注意:在使用OVERLAPPED结构前,需要对其内部清零,并为其创建事件。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #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 = "buffer"; 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; } |
4.异步操作设备(方式二)
除了ReadFile和WriteFile函数外,还有两个API函数可以实现异步读写,这就是ReadFileEx和WriteFileEx函数。ReadFile和WriteFile函数既可以支持同步读写操作,又可以支持异步读写操作,而ReadFileEx和WriteFileEx函数是专门用于异步读写操作的。
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)状态时,回调函数才有可能被调用。有多个函数可以使线程进入警惕状态,如SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectEx函数等。这些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
);
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include <windows.h> #include <stdio.h> VOID CALLBACK MyCompletionRoutine(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 = "MyCompletionRoutine"; //定义OVERLAPPED结构,这里不需要Event事件 OVERLAPPED ovlp = {0}; BOOL bRet = WriteFileEx(hFile, Buffer, strlen(Buffer), &ovlp, MyCompletionRoutine); //.... //其他操作 //运行到这个点,必须等待写操作完成,否则不能继续完成别的任务 SleepEx(0, TRUE); //.... //其他操作 printf("finish\n"); CloseHandle(hFile); return 0; } |
5.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请求被真正结束,既调用了IoCompleteRequest,ReadFile函数提供的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函数结束他们。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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_CLEARUP的IRP 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_CLEARUP的IRP //进而对被挂起的线程执行完成操作 CloseHandle(hFile); return 0; } |
6.取消IRP
前面提到了在设备句柄被关闭的时候,将“挂起”的IRP结束。还有另外一个办法将“挂起”的IRP逐个结束,这就是取消IRP请求。内核函数IoSetCancelRoutine可以设置取消IRP请求的回调函数,其声明如下:
PDRIVER_CANCEL
IoSetCancelRoutine(
IN PIRP Irp, //需要取消的IRP
IN PDRIVER_CANCEL CancelRoutine //IRP取消后调用的函数
);
我们可以用IoCancelIrp函数指定取消IRP请求。在IoCancelIrp函数内部,需要进行同步。DDK在IoCancelIrp内部使用一个叫做cancel的自旋锁,用来进行同步。
IoCancelIrp在内部会首先获得该自旋锁,然后会调用刚才提到的IoSetCancelRoutine函数设置的取消回调函数。因此,释放该自旋锁的任务就留给了取消回调函数。获得“取消自旋锁”的函数是IoAcquireCancelSpinLock,释放“取消自旋锁”的函数是IoReleaseCancelSpinLock。
在应用程序中,可以调用Win32 API函数取消IRP操作。在CancelIo的内部会枚举所有没有完成的IRP,然后依次调用IoCancelIrp。另外,如果应用程序没有调用CancelIo函数,应用程序在关闭设备句柄时同样会自动调用CnacelIo。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 取消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; } |
7.StartIo例程:能保证各个并行的IRP顺序执行,即串行化。
在很多情况下,对设备的操作必须是串行执行,而不能并行执行。例如,对串口的操作,假如有N个线程同时操作串口设备时,必须将这些操作排队,然后一一进行处理。
DDK为了简化程序员的工作,为程序员提供了一个内部队列,并将IRP用StartIo例程串行处理。
操作系统为程序员提供了一个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函数首先判断当前设备是“忙”还是“空闲”。如果设备空闲,则提升当前IRQL到DISPATCH_LEVEL级别,并进入StartIo例程“串行”处理该IRP。如果设备“忙”,则将IRP插入串行队列后返回。
另外,在StartIo例程结束前,应该调用IoStartNextPacket函数,其作用是从队列中抽取下一个IRP,并将这个IRP作为参数调用StartIo例程。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 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; } |
8.自定义的StartIO
我们发现,系统为我们提供的StartIo虽然可以很方便的将IRP串行化,但是存在一个问题,就是读写操作都被一起串行化。而我们有时候需要将读,写分开串行化。此时,希望有两个队列。一个队列串行IRP_MJ_READ类型的IRP,另一个队列串行IRP_MJ_WRITE类型的IRP。
此时我们需要自定义StartIo,自定义StartIo类似于前面介绍的StartIo例程,不同的是程序要需要自己维护IRP队列。程序员可以灵活的维护多个队列,分别应用于不同的IRP。
DDK提供KDEVICE_QUEUE数据结构存储队列,队列中每个元素用KDEVICE_QUEUE_ENTRY数据结构表示,在使用队列前,应该初始化队列(KeInitializeDeviceQueue)
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 自定义设备扩展: typedef struct _DEVICE_EXTENSION { PDEVICE_OBJECT pDevice; UNICODE_STRING ustrDeviceName; //设备名称 UNICODE_STRING ustrSymLinkName; //符号链接名 KDEVICE_QUEUE deviceQueue; //支持串行化的队列 } DEVICE_EXTENSION, *PDEVICE_EXTENSION; CreateDevice中初始化队列 RtlZeroBytes(&pDevExt->device_queue,sizeof(pDevExt->device_queue)); KeInitializeDeviceQueue(&pDevExt->device_queue); IRP_MJ_READ派遣函数: NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { KdPrint(("Enter HelloDDKRead\n")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; //将IRP设置为挂起 IoMarkIrpPending(pIrp); IoSetCancelRoutine(pIrp,OnCancelIRP); KIRQL oldirql; //提升IRP至DISPATCH_LEVEL KeRaiseIrql(DISPATCH_LEVEL, &oldirql); KdPrint(("HelloDDKRead irp :%x\n",pIrp)); KdPrint(("DeviceQueueEntry:%x\n",&pIrp->Tail.Overlay.DeviceQueueEntry)); if (!KeInsertDeviceQueue(&pDevExt->device_queue, &pIrp->Tail.Overlay.DeviceQueueEntry)) MyStartIo(pDevObj,pIrp); //将IRP降至原来IRQL KeLowerIrql(oldirql); KdPrint(("Leave HelloDDKRead\n")); //返回pending状态 return STATUS_PENDING; } 自定义的StartIo函数: #pragma LOCKEDCODE VOID MyStartIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP pFistIrp ) { KdPrint(("Enter MyStartIo\n")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension; PKDEVICE_QUEUE_ENTRY device_entry; PIRP Irp = pFistIrp; do { KEVENT event; KeInitializeEvent(&event,NotificationEvent,FALSE); //等3秒 LARGE_INTEGER timeout; timeout.QuadPart = -3*1000*1000*10; //定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右时间 KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout); KdPrint(("Complete a irp:%x\n",Irp)); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; // no bytes xfered IoCompleteRequest(Irp,IO_NO_INCREMENT); device_entry=KeRemoveDeviceQueue(&pDevExt->device_queue); KdPrint(("device_entry:%x\n",device_entry)); if (device_entry==NULL) { break; } Irp = CONTAINING_RECORD(device_entry, IRP, Tail.Overlay.DeviceQueueEntry); }while(1); KdPrint(("Leave MyStartIo\n")); } |
9.延迟过程调用例程(DPC)
它运行在DISPATCH_LEVEL的IRQL级别。因此除了ISR其他例程是不会将其打断的。ISR会将DPC插入一个内部队列,这样当IRQL从高的IRQL恢复到DISPATCH_LEVEL时,DPC会依次从队列中弹出,并执行相应的DPC例程。
初始化:KeInitializeDpc
ISR中执行简短的处理,将不重要的代码放到DPC例程中。
IoRequestDpc(device,device->CurremtIrp,NULL);
本文链接:http://www.alonemonkey.com/irp-synchronization-asynchronous.html