news 2026/6/10 20:07:06

通过异步的方式处理IO和延迟过程调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
通过异步的方式处理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_LEVEL(2)并以先进先出的方式处理队列中的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内核安全编程》。:

#include<ntddk.h>typedefstruct_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 pDevExt=reinterpret_cast<PDEV_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 pDevExt=reinterpret_cast<PDEV_EXT>(deferredContext);PVOID pBuffer=nullptr;ULONG ulBufferLen=0;PIO_STACK_LOCATION pIrpStack=nullptr;do{if(!pDevExt){break;}// 检查IRP链表是否为空if(IsListEmpty(&pDevExt->irpList)){break;}// 从链表中取出一个IRP并完成该IRP。取出的是ListEntry的地址PLIST_ENTRY pListEntry=reinterpret_cast<PLIST_ENTRY>(RemoveHeadList(&pDevExt->irpList));if(!pListEntry){break;}pIrp=reinterpret_cast<PIRP>(CONTAINING_RECORD(pListEntry,IRP,Tail.Overlay.ListEntry));pIrpStack=IoGetCurrentIrpStackLocation(pIrp);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"当前DPC Irp: 0x%X\n",pIrp);// 直接IOpBuffer=MmGetSystemAddressForMdl(pIrp->MdlAddress);if(pBuffer==nullptr){pIrp->IoStatus.Status=STATUS_UNSUCCESSFUL;pIrp->IoStatus.Information=0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);break;}ulBufferLen=pIrpStack->Parameters.Read.Length;DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"读取长度: %d\n",ulBufferLen);// 支持5字节以下的读请求ulBufferLen=ulBufferLen>13?13:ulBufferLen;// 复制请求内容RtlCopyMemory(pBuffer,"hello lyshark",ulBufferLen);pIrp->IoStatus.Status=STATUS_SUCCESS;pIrp->IoStatus.Information=ulBufferLen;// 完成该IRPIoCompleteRequest(pIrp,IO_NO_INCREMENT);}while(false);// 重新设置定时器KeSetTimer(&pDevExt->timer,pDevExt->LiDueTime,&pDevExt->dpc);}NTSTATUSCreateDispatch(PDEVICE_OBJECT pDevObj,PIRP pIrp){pIrp->IoStatus.Status=STATUS_SUCCESS;pIrp->IoStatus.Information=0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnSTATUS_SUCCESS;}NTSTATUSCloseDispatch(PDEVICE_OBJECT pDevObj,PIRP pIrp){pIrp->IoStatus.Status=STATUS_SUCCESS;pIrp->IoStatus.Information=0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnSTATUS_SUCCESS;}// 使用异步的方式,它不处理请求,而是将请求尾插到拓展设备的链表中// 由定时器NTSTATUSReadDispatch(PDEVICE_OBJECT pDevObj,PIRP pIrp){PIO_STACK_LOCATION pIrpStack=IoGetCurrentIrpStackLocation(pIrp);PDEV_EXT pDevExt=reinterpret_cast<PDEV_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_devName=RTL_CONSTANT_STRING(L"\\Device\\My_Device");PDEVICE_OBJECT pDevObj=nullptr;// 创建设备时分配自定义的拓展设备status=IoCreateDevice(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->AlignmentRequirement=FILE_WORD_ALIGNMENT;pDevObj->Flags&=~DO_DEVICE_INITIALIZING;// 设置拓展设备PDEV_EXT pDevExt=reinterpret_cast<PDEV_EXT>(pDevObj->DeviceExtension);g_symLinkName=RTL_CONSTANT_STRING(L"\\??\\My_Device");status=IoCreateSymbolicLink(&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_cast<PKDEFERRED_ROUTINE>(_CustomDpc),pDevExt);// 设置定时时间为1spDevExt->LiDueTime=RtlConvertLongToLargeInteger(-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->DriverUnload=DriverUnload;pDriver->MajorFunction[IRP_MJ_CREATE]=CreateDispatch;pDriver->MajorFunction[IRP_MJ_CLOSE]=CloseDispatch;pDriver->MajorFunction[IRP_MJ_READ]=ReadDispatch;NTSTATUS status=CreateDevice(pDriver);if(!NT_SUCCESS(status)){returnstatus;}returnstatus;}

驱动程序并不在派遣函数中处理IRP,而是将其插入到拓展设备中保存的链表尾部,通过内核时钟的方式来回调DPC。真正的处理过程在DPC中。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 20:00:40

2026年怎么降低论文AIGC率?7种高效方法必收藏!

最近学校对论文原创性的要求真的越来越严了&#xff01;好多直接把AIGC率纳入核心考核指标——要是AI痕迹太重&#xff0c;别说评优拿奖学金&#xff0c;能不能顺利过审都悬&#xff01;别慌&#xff0c;除了手动调整&#xff0c;我整理了几个亲测有效的降AI小技巧&#xff0c;…

作者头像 李华
网站建设 2026/6/10 19:59:25

【UE5】雷达覆盖区域效果

目录 效果 步骤 一、启用插件 二、制作雷达材质 三、创建雷达Actor 效果 雷达覆盖区域效果如下,可修改“覆盖区域半径”、“水平夹角”、“垂直夹角”、“圆弧细分数”、“垂直细分数”参数 步骤 一、启用插件 启动“Procedural Mesh Component”插件

作者头像 李华
网站建设 2026/6/10 19:58:53

【Redis分布式缓存实战】第22章 企业级Redis缓存项目架构复盘

电商平台全链路缓存架构复盘一、复盘概述1.1 复盘背景电商平台是典型的读多写少、峰值流量集中、数据热度分层明显的高并发业务场景&#xff0c;日常商品浏览、用户访问、购物车操作流量平稳&#xff0c;大促、秒杀、直播间带货场景下瞬时QPS会暴涨10-50倍&#xff0c;纯MySQL数…

作者头像 李华
网站建设 2026/6/10 19:52:21

Web分布式网站架构之-Squid缓存【20260608】004篇-【传统代理】

文章目录 CentOS 7.9 + Win10 搭建 Squid **传统代理** 完整实验手册 一、实验环境规划 1. 主机IP与角色 2. 前置准备(CentOS 7.9 统一操作) (1)关闭防火墙 & SELinux (2)测试网络互通 二、方式选择:YUM 安装 Squid(推荐,简单稳定) 1. 安装 Squid 2. 查看默认信息…

作者头像 李华