通过异步的方式处理IO和延迟过程调用
通过异步的方式处理IO同步IO和异步IO先前我们编写的驱动程序对于IRP的处理一直是同步的在派遣函数中处理完毕IRP。这时我们可以在派遣函数中直接调用IoCompleteRequest来结束这个IO请求并返回给用户态的程序。但是绝大多数的硬件操作都是异步的。硬件需要时间来完成请求而驱动程序不能在这段时间阻塞用户线程应该尽快返回。所以派遣函数只是将IRP标记为pending状态并将其排到驱动接收队列的末尾返回pending。真正处理IRP的是硬件的ISR这个过程是异步的。在ISR执行结束之后控制权回到系统中。系统在降低IRQL并检查DPC执行队列。应用程序 (ReadFile) ↓ I/O管理器 (创建IRP) ↓ 派遣函数 (排队IRP) ←→ 硬件中断 (ISR) ←→ DPC (完成IRP) ↓ ↑ 返回PENDING ------------------------------- 回调应用程序下面是从硬件中断开始的详细过程硬件中断发生 ↓ CPU 提升到 DIRQL ↓ 内核调用ISR ↓ ISR 执行在 DIRQL │ ├→ 确认中断 ├→ 清除中断源 ├→ 保存数据 └→ 调用 IoRequestDpc() 排队 DPC ← 关键 ↓ ISR 返回 ↓ 内核降低 IRQL ← 关键 │ ├→ 当降到 DISPATCH_LEVEL 时 ├→ 内核检查 DPC 队列 ├→ 发现你的 DPC └→ 调用你的 DPC 例程 ↓ DPC 例程执行 ↓ DPC 返回 ↓ 继续降低 IRQL ↓ 返回被中断的代码这里的问题是ISR通常运行以高IRQL运行但是IoCompleteRequest只能在低IRQL的权限下运行。有时候一些高IRQL的代码需要执行一些低IRQL的代码。但是在高IRQL的情况下低IRQL的代码是不能够执行的。DPC解决了这一问题。使用DPC延迟过程调用KeInsertQueueDpc注册了ISR的驱动程序需要预先准备好DPC对象这需要从非分页池中分配一个KDPC结构并用一个回调函数调用KeInitializeDpc进行初始化。然后在ISR被调用时就在退出之前ISR调用KeInsertQueueDpc将此DPC排队以请求尽快执行这个DPC。当DPC函数执行时将会调用IoCompleteRequest。因此DPC就像一个折中—它在IRQL的DISPATCH_LEVEL上执行意即不会有调度出现不能访问分页内存等等但是这级别还没有高到会阻止硬件中断的进入及在同一个处理器上进行处理。系统中的每一个处理器都有它自己的DPC队列。KeInsertQueueDpc默认将DPC排到当前处理器的DPC队列中。在ISR返回时在IRQL能降回到零之前会查看在处理器的队列上有没有DPC存在。如果有处理器会把IRQL降到DISPATCH_LEVEL2并以先进先出的方式处理队列中的DPC逐个调用相应的函数直到队列清空为止。只有这时处理器的IRQL才能降到零并恢复执行中断到来时被打断的原始代码。带有DPC的中断处理过程首先CPU正在执行IRQL为0的代码此时一个IRQL为5的中断抵达CPU就在当前线程的上下文中执行对应的ISR1假设在执行ISR1的过程中来了一个IRQL为6的中断那么CPU将会优先处理该中断执行ISR2。ISR2会把自己的DPC放到DPC队列中并返回然后ISR1继续执行即将执行完毕时ISR1也将自己的DPC放到DPC队列中。当CPU处理完中断后即将返回到IRQL0时会检查DPC队列然后以IRQL2的级别执行DPC中的调用。参考《Windows Internal 7th》第六章内核时钟内核时钟用KTIMER结构表示允许被设置成在未来的某个时间到期。当时钟到期时会调用先前设置的回调函数。内核时钟提供了用DPC作为其回调的方式。示例代码这里我们使用内核时钟来调用DPC。下面是驱动程序的代码参考了《Windows内核安全编程》。#includentddk.htypedefstruct_DeviceExtention{LIST_ENTRY irpList;KTIMER timer;LARGE_INTEGER LiDueTime;KDPC dpc;}DEV_EXT,*PDEV_EXT;UNICODE_STRING g_devName;UNICODE_STRING g_symLinkName;VOIDDriverUnload(PDRIVER_OBJECT pDriver){PDEV_EXT pDevExtreinterpret_castPDEV_EXT(pDriver-DeviceObject-DeviceExtension);KeCancelTimer(pDevExt-timer);IoDeleteSymbolicLink(g_symLinkName);IoDeleteDevice(pDriver-DeviceObject);}VOID_CustomDpc(PKDPC dpc,PVOID deferredContext,PVOID systemArgument1,PVOID systemArgument2){PIRP pIrp;PDEV_EXT pDevExtreinterpret_castPDEV_EXT(deferredContext);PVOID pBuffernullptr;ULONG ulBufferLen0;PIO_STACK_LOCATION pIrpStacknullptr;do{if(!pDevExt){break;}// 检查IRP链表是否为空if(IsListEmpty(pDevExt-irpList)){break;}// 从链表中取出一个IRP并完成该IRP。取出的是ListEntry的地址PLIST_ENTRY pListEntryreinterpret_castPLIST_ENTRY(RemoveHeadList(pDevExt-irpList));if(!pListEntry){break;}pIrpreinterpret_castPIRP(CONTAINING_RECORD(pListEntry,IRP,Tail.Overlay.ListEntry));pIrpStackIoGetCurrentIrpStackLocation(pIrp);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,当前DPC Irp: 0x%X\n,pIrp);// 直接IOpBufferMmGetSystemAddressForMdl(pIrp-MdlAddress);if(pBuffernullptr){pIrp-IoStatus.StatusSTATUS_UNSUCCESSFUL;pIrp-IoStatus.Information0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);break;}ulBufferLenpIrpStack-Parameters.Read.Length;DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,读取长度: %d\n,ulBufferLen);// 支持5字节以下的读请求ulBufferLenulBufferLen13?13:ulBufferLen;// 复制请求内容RtlCopyMemory(pBuffer,hello lyshark,ulBufferLen);pIrp-IoStatus.StatusSTATUS_SUCCESS;pIrp-IoStatus.InformationulBufferLen;// 完成该IRPIoCompleteRequest(pIrp,IO_NO_INCREMENT);}while(false);// 重新设置定时器KeSetTimer(pDevExt-timer,pDevExt-LiDueTime,pDevExt-dpc);}NTSTATUSCreateDispatch(PDEVICE_OBJECT pDevObj,PIRP pIrp){pIrp-IoStatus.StatusSTATUS_SUCCESS;pIrp-IoStatus.Information0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnSTATUS_SUCCESS;}NTSTATUSCloseDispatch(PDEVICE_OBJECT pDevObj,PIRP pIrp){pIrp-IoStatus.StatusSTATUS_SUCCESS;pIrp-IoStatus.Information0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnSTATUS_SUCCESS;}// 使用异步的方式它不处理请求而是将请求尾插到拓展设备的链表中// 由定时器NTSTATUSReadDispatch(PDEVICE_OBJECT pDevObj,PIRP pIrp){PIO_STACK_LOCATION pIrpStackIoGetCurrentIrpStackLocation(pIrp);PDEV_EXT pDevExtreinterpret_castPDEV_EXT(pDevObj-DeviceExtension);IoMarkIrpPending(pIrp);InsertTailList(pDevExt-irpList,pIrp-Tail.Overlay.ListEntry);returnSTATUS_PENDING;}NTSTATUSCreateDevice(PDRIVER_OBJECT pDriver){DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,CreateDevice开始执行\n);NTSTATUS status;g_devNameRTL_CONSTANT_STRING(L\\Device\\My_Device);PDEVICE_OBJECT pDevObjnullptr;// 创建设备时分配自定义的拓展设备statusIoCreateDevice(pDriver,sizeof(DEV_EXT),g_devName,FILE_DEVICE_UNKNOWN,0,false,pDevObj);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,IoCreateDevice: %d\n,status);if(!NTSTATUS(status)){returnstatus;}pDevObj-Flags|DO_DIRECT_IO;pDevObj-AlignmentRequirementFILE_WORD_ALIGNMENT;pDevObj-Flags~DO_DEVICE_INITIALIZING;// 设置拓展设备PDEV_EXT pDevExtreinterpret_castPDEV_EXT(pDevObj-DeviceExtension);g_symLinkNameRTL_CONSTANT_STRING(L\\??\\My_Device);statusIoCreateSymbolicLink(g_symLinkName,g_devName);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,IoCreateSymbolicLink: %d\n,status);if(!NTSTATUS(status)){returnstatus;}// 初始化IRP链表InitializeListHead(pDevExt-irpList);// 初始化定时器KeInitializeTimer(pDevExt-timer);// 初始化DPC(延迟过程调用)KeInitializeDpc(pDevExt-dpc,reinterpret_castPKDEFERRED_ROUTINE(_CustomDpc),pDevExt);// 设置定时时间为1spDevExt-LiDueTimeRtlConvertLongToLargeInteger(-10000000);// 启动定时器KeSetTimer(pDevExt-timer,pDevExt-LiDueTime,pDevExt-dpc);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,CreateDevice执行完毕\n);returnstatus;}NTSTATUSDriverEntry(PDRIVER_OBJECT pDriver,PUNICODE_STRING RegistryPath){UNREFERENCED_PARAMETER(RegistryPath);NTSTATUS status;pDriver-DriverUnloadDriverUnload;pDriver-MajorFunction[IRP_MJ_CREATE]CreateDispatch;pDriver-MajorFunction[IRP_MJ_CLOSE]CloseDispatch;pDriver-MajorFunction[IRP_MJ_READ]ReadDispatch;NTSTATUS statusCreateDevice(pDriver);if(!NT_SUCCESS(status)){returnstatus;}returnstatus;}驱动程序并不在派遣函数中处理IRP而是将其插入到拓展设备中保存的链表尾部通过内核时钟的方式来回调DPC。真正的处理过程在DPC中。