接收I/O请求完成通知

2023-11-05

《Windows via C/C++》学习笔记 —— 设备I/O之“接收I/O请求完成通知”

   上一篇,讨论了如何发送I/O请求。在异步的设备I/O请求方式下,要考虑的问题就是当I/O请求完成之后,驱动程序如何通知你的应用程序。本篇主要讨论获得通知的方法。

 FILE_SKIP_COMPLETION_PORT_ON_SUCCESS

  Windows 提供了4种不同的技术方法来得到I/O完成的通知。

技术 

概要 

通知一个设备内核对象

当一个设备同时有多个I/O请求的时候,该方法不适用。

允许一个线程发送一个I/O请求,另一个线程处理之。

通知一个事件内核对象

允许一个设备同时有多个I/O请求。

允许一个线程发送一个I/O请求,另一个线程处理之。

告警I/O

允许一个设备同时有多个I/O请求。

必须在同一个线程中发送并处理同一个I/O请求。

I/O完成端口

允许一个设备同时有多个I/O请求。

允许一个线程发送一个I/O请求,另一个线程处理之。

该方法伸缩性好,而且性能高。

 

  本篇主要讨论前3种。

 

通知一个设备内核对象

  在Windows中,一个设备内核对象可以处于“已通知”或“未通知”状态。ReadFile和WriteFile在发送I/O请求之前让指定的设备内核对象处于“未通知”状态。当设备驱动程序完成了I/O请求,驱动程序将设备内核对象设置为“已通知”状态。

  一个线程可以查看一个异步的I/O请求是否完成,通过等待函数即可实现:WaitForSingleObject或WaitForMultipleObject等。这就意味着,这种实现的方式不是完完全全的“异步”,最终有点“同步”的味道,因为这些等待函数可能会导致线程进入阻塞状态。

  可以如下地编码来使用这种方法:

 

// 创建或打开设备内核对象,注意使用FILE_FLAG_OVERLAPPED旗标
HANDLE hFile  =  CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
BYTE bBuffer[
100 ];      // I/O缓冲区
OVERLAPPED o  =  {  0  };      // 重叠结构,不要忘记初始化
o.Offset  =   345 ;      // 偏移量
BOOL bReadDone  =  ReadFile(hFile, bBuffer,  100 , NULL,  & o);    // 读取数据
DWORD dwError  =  GetLastError();

// ReadFile返回FLASE,但是错误码dwError表明I/O即将开始
if  ( ! bReadDone  &&  (dwError  ==  ERROR_IO_PENDING))
{
     
// 等待I/O请求完成
     WaitForSingleObject(hFile, INFINITE);
     bReadDone 
=  TRUE;
}
if  (bReadDone)
{
     
//  操作成功,可以查看OVERLAPPED结构中的各个字段和缓冲区中的数据
     
//  o.Internal 包含了I/O错误码
     
//  o.InternalHigh 包含了I/O传输字节数
     
//  缓冲区包含了读取的数据
}
else
{
     
//  错误发生,bReadDone为FLASE,且错误码dwError指明一个错误
}

 

  这种方法是十分简单的,实现起来十分容易,但是有一个明显的缺点,就是无法处理多个I/O请求。因为一旦一个I/O请求完成,等待函数就会返回,无法识别是哪个I/O请求完成了。

 

通知一个事件内核对象

  这种方法可以处理多个同时的I/O请求。

  记得OVERLAPPED结构中有一个hEvent成员吧,该成员是一个事件内核对象。使用这种方法,你必须使用CreateEvent函数来创建一个事件内核对象,并初始化那个hEvent成员。当一个异步I/O请求完成设备驱动程序查看OVERLAPPED中的hEvent是否为NULL,如果不是,驱动程序通过SetEvent通知该事件内核对象,同时也使得设备内核对象进入“已通知”状态。但是,你应该等待在该事件内核对象上。

  你可以让Windows不通知“文件内核对象”,这样可以少许提高一点性能,通过呼叫函数SetFileCompletionNotificationModes即可,传递一个设备内核对象句柄和FILE_SKIP_SET_EVENT_ON_HANDLE旗标:FILE_SKIP_COMPLETION_PORT_ON_SUCCESS

BOOL SetFileCompletionNotificationModes(HANDLE hFile, UCHAR uFlags);

  为了处理多个I/O请求,你必须为每个I/O请求创建一个独立的事件内核对象,并将之初始化OVERLAPPED结构中的hEvent。然后可以通过WaitForMultipleObject来等待这些事件内核对象。这种方法可以实现一个设备上的多个I/O请求的处理。可以如下编码:

 

// 创建或打开设备,注意使用FILE_FLAG_OVERLAPPED
HANDLE hFile  =  CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
BYTE bReadBuffer[
10 ];      // 读缓冲区
OVERLAPPED oRead  =  {  0  };      // 定义OVERLAPPED结构,并初始化之
oRead.Offset  =   0 ;
oRead.hEvent 
=  CreateEvent(...);      // 创建事件内核对象,与读操作相关
ReadFile(hFile, bReadBuffer,  10 , NULL,  & oRead);

BYTE bWriteBuffer[
10 =  {  0 1 2 3 4 5 6 7 8 9  };
OVERLAPPED oWrite 
=  {  0  };
oWrite.Offset 
=   10 ;
oWrite.hEvent 
=  CreateEvent(...);      // 另一个事件内核对象,与写操作相关
WriteFile(hFile, bWriteBuffer, _countof(bWriteBuffer), NULL,  & oWrite);

// 可在此执行其他操作
//......

HANDLE h[
2 ];
h[
0 =  oRead.hEvent;      // 与读相关的事件对象
h[ 1 =  oWrite.hEvent;     // 与写相关的事件对象
DWORD dw  =  WaitForMultipleObjects( 2 , h, FALSE, INFINITE);      // 等待
switch  (dw – WAIT_OBJECT_0)
{
     
case   0 :    // 读操作完成
           break ;

     
case   1 :    // 写操作完成
           break ;
}

 

  当然,也可以把上面代码拆分成两个线程执行,上面半段为发送I/O请求的放在一个线程中,下面处理I/O请求完成的放在另一个线程中。

 

  在I/O请求完成之后,收到通知之后,可以得到有关OVERLAPPED结构的信息,通过函数GetOverlappedResult:

BOOL GetOverlappedResult(
   HANDLE      hFile,        
// 设备对象句柄
   OVERLAPPED *  pOverlapped,   // OVERLAPPED结构指针,返回OVERLAPPED
   PDWORD      pdwNumBytes,   // 返回传输的字节数
   BOOL        bWait);        // 是否等到I/O结束才返回

 

告警I/O

  当一个线程被创建的时候,系统也创建一个与该线程关联的队列,这个队列称为“异步过程调用”(APC)队列。当发送一个I/O请求的时候,你可以告诉驱动程序在APC队列中加入一个记录。当I/O请求完成之后,如果线程处于“待命状态”,则该记录中的回调函数可以被调用。

  让I/O请求完成的通知进入线程的APC队列,即在APC队列中添加一个I/O请求完成通知的记录,可以使用如下两个函数:

BOOL ReadFileEx(
   HANDLE      hFile,        
// 设备对象句柄
   PVOID       pvBuffer,      // 数据缓冲区
   DWORD       nNumBytesToRead,   // 预期传输的数据
   OVERLAPPED *  pOverlapped,       // OVERLAPPED结构指针
   LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine); // 回调函数指针

 

BOOL WriteFileEx(
   HANDLE      hFile,
   CONST VOID  
* pvBuffer,
   DWORD       nNumBytesToWrite,
   OVERLAPPED
*  pOverlapped,
   LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine);


  注意一下函数的最后一个参数pfnCompletionRoutine,是一个函数指针,接受一个回调函数,这个函数就是被记录到APC队列的函数,函数头必须按如下格式书写:

VOID WINAPI CompletionRoutine(      // 函数名可以任意
   DWORD       dwError,      // 错误码
   DWORD       dwNumBytes,   // 传输的数据
   OVERLAPPED *  po);          // OVERLAPPED结构

 

  当使用ReadFileEx和WriteFileEx函数的时候,传递回调函数的地址,当驱动程序完成I/O请求之后,它在线程APC队列中添加一个记录,这个记录包含这个回调函数的地址和起初发送I/O请求时候的OVERLAPPED结构地址。

  当线程进入“待命状态”,系统检测线程APC队列,然后调用回调函数,并设置其3个参数。

  当I/O请求完成,系统不会马上调用记录在APC队列中的回调函数,因为线程可能没有进入“待命状态”。为了调用回调函数,你必须让线程进入“待命状态”,可以通过一些带“Ex”的等待函数来完成:

 

DWORD SleepEx(
   DWORD dwMilliseconds,
   BOOL  bAlertable);

DWORD WaitForSingleObjectEx(
   HANDLE hObject,
   DWORD  dwMilliseconds,
   BOOL   bAlertable);

DWORD WaitForMultipleObjectsEx(
   DWORD   cObjects,
   CONST HANDLE
*  phObjects,
   BOOL    bWaitAll,
   DWORD   dwMilliseconds,
   BOOL    bAlertable);

BOOL SignalObjectAndWait(
   HANDLE hObjectToSignal,
   HANDLE hObjectToWaitOn,
   DWORD  dwMilliseconds,
   BOOL   bAlertable);

BOOL GetQueuedCompletionStatusEx(
   HANDLE hCompPort,
   LPOVERLAPPED_ENTRY pCompPortEntries,
   ULONG ulCount,
   PULONG pulNumEntriesRemoved,
   DWORD dwMilliseconds,
   BOOL bAlertable);

DWORD MsgWaitForMultipleObjectsEx(
   DWORD   nCount,
   CONST HANDLE
*  pHandles,
   DWORD   dwMilliseconds,
   DWORD   dwWakeMask,
   DWORD   dwFlags);     
// 使用MWMO_ALERTABLE使线程进入“待命状态”

 

  除了MsgWaitForMultipleObjectEx函数之外,上面其余5个函数的最后一个参数bAlertalbe,指明了是否要线程进入“待命状态”,如果需要,请传递TRUE。

  当你调用上面这些等待函数,并让线程进入“待命状态”,系统首先查看线程的APC队列,如果至少有一个记录在APC队列中,系统不会让你的线程进入阻塞状态,而是调用回调函数,并提供其3个参数。当回调函数返回给系统,系统再次检查APC队列中的记录,如果存在,继续调用回调函数。否则,回调函数返回给用户(即普通的返回)。

  注意,如果APC队列中存在记录,那么调用上述等待函数,不会让你的线程进入阻塞状态。只有当APC队列中没有记录,调用这些函数的时候才会让线程进入阻塞状态,直到等待的内核对象为“已通知”状态或APC队列中出现记录。由于线程处于“待命状态”,因此一点APC队列中出现一个记录,那么系统唤醒你的线程,呼叫回调函数,清空APC队列,回调函数返回,线程继续执行。

  这6个等待函数返回的值说明了它们是因为什么原因而返回的。如果返回WAIT_IO_COMPLETION,那么说明了你的线程继续执行,因为至少一个APC记录被处理。如果返回其他的值,那么说明这些等待函数等待的内核对象为“已通知”状态(也可能是互斥内核对象被抛弃)或者等待超时。

 

  还有需要注意的是,系统调用APC回调函数,不是按FIFO的顺序,而是随意的。注意如下代码:

hFile  =  CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
ReadFileEx(hFile, ..., ReadRoutine1);   
// 第一次读,回调函数ReadRoutine1
WriteFileEx(hFile, ..., WriteRoutine1);  // 第一次写,回调函数WriteRoutine1
ReadFileEx(hFile, ..., ReadRoutine2);    // 第二次读,回调函数ReadRoutine2
SomeFunc();    // 其他一些操作
SleepEx(INFINITE, TRUE);      // 等待,线程进入“待命状态”

 

  线程发起了3次I/O请求,并给出了3个回调函数ReadRoutine1、WriteRoutine1、ReadRoutine2。然后线程执行SomeFunc函数,执行完成之后进入无限等待,当I/O请求结束,会调用3个APC队列中的回调函数。

  需要注意的是,如果3个I/O请求都在SomeFunc函数执行的时候完成,那么回调函数的调用顺序可能不是ReadRountine1、WriteRoutine1、ReadRoutine2,这个顺序是任意的。

 

  Windows提供了一个函数可以手动在一个线程的APC队列加入一个记录(即加入一个回调函数):

DWORD QueueUserAPC(
   PAPCFUNC  pfnAPC,     
// APC回调函数指针
   HANDLE    hThread,      // 线程对象句柄
   ULONG_PTR dwData);   // 传递给参数pfnAPC所对应的回调函数的参数

 

  其中第1个参数是一个函数指针,是一个回调函数,被记录到线程的APC队列,其函数头格式如下:

VOID WINAPI APCFunc(ULONG_PTR dwParam);

 

  QueueUserAPC函数的第2个参数指明了你想要设置的哪个线程的APC队列。第3个参数dwData就是传递给回调函数APCFunc的参数。QueueUserAPC可以让你的线程摆脱阻塞状态,此时上述等待函数返回码为WAIT_IO_COMPLETION。

 

  最后要讲的就是告警I/O的缺点:

  • 告警I/O的回调函数所提供的参数较少,因此处理上下文内容只能通过全局变量来实现。
  • 使用告警I/O,意味着发送I/O请求和处理I/O完成通知只能放在同一个线程中,如果发送多个I/O请求,该线程就不得不处理每个I/O完成通知,其他线程则会比较 空闲,这样会造成不平衡。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

接收I/O请求完成通知 的相关文章

随机推荐

  • python爬取boss直聘招聘信息_python学习之路-爬取boss直聘的岗位信息

    背景 想了解从事python相关岗位需要具备什么技能 于是就想从招聘网站上的职位需求入手 把信息获取下来后 生成词云 这样就能很直观的看出来哪些技能是python相关岗位需要具备的了 技术概览 scrapy request wordclou
  • 面试官:说一下单点登录的几种实现方式

    Java面试笔试面经 Java技术每天学习一点 Java面试 关注不迷路 作者 张永恒 来源 https www cnblogs com yonghengzh p 13712729 html 在 B S 系统中 登录功能通常都是基于Cook
  • centos ip 没有显示

    进入 cd etc sysconfig network scripts 编辑网卡设置 我的网卡默认是ifcfg ens33 有的网卡默认是ifcfg eth0 执行 vi ifcfg ens33 将onboot no 改为onboot ye
  • MPU6500驱动调试笔记(STM32F407+SPI)

    一 问题背景 本来最开始实验室使用MPU6050芯片 采集陀螺仪原始数据做生理信号采集 但算法发现用IIC接口采样率 200hz 达不到要求 故寻找同类型支持SPI协议的芯片去替代 发现了这块MPU6500 还便宜 就用起来 在读写寄存器费
  • ST 电机控制工作台帮助文档翻译 之 STM32F3x 的 OCP 和 OVP(使用嵌入式模拟的过压保护(仅限 STM32F3x))

    ST 电机控制工作台 使用嵌入式模拟的过压保护 仅限 STM32F3x 图5显示了可以使用 STM32F30x 的内部资源实现的过压保护网络的基本实现 图5 过压保护网络 原理类似于 过流保护
  • linux-目录相关作用

    文章来自狂神老师的笔记 同时会有自己的体会 狂神老师课程的链接 https www bilibili com video BV187411y7hF from search seid 10463862828616102628 注意 linux
  • 基于SpringBoot的实习管理系统

    末尾获取源码 开发语言 Java Java开发工具 JDK1 8 后端框架 SpringBoot 前端 Vue HTML 数据库 MySQL5 7和Navicat管理工具结合 服务器 Tomcat8 5 开发软件 IDEA Eclipse
  • 一位技术主管的十年编程经验总结 .

    一位技术主管的十年编程经验总结 有天和朋友聊天 朋友是国内一家大型互联网企业的一位技术主管 朋友把他将近十年研发工作积累的心血总结成两点 这两点朋友刚一提出来我并没有马上明白 只是大约有这么一个概念 我还没达到朋友在技术领域的那种高度 不能
  • PAT乙级1041 考试座位号 (15 分)

    1041 考试座位号 15 分 一 问题描述 每个 PAT 考生在参加考试时都会被分配两个座位号 一个是试机座位 一个是考试座位 正常情况下 考生在入场时先得到试机座位号码 入座进入试机状态后 系统会显示该考生的考试座位号码 考试时考生需要
  • 机器学习:Jupyter Notebook中numpy的使用

    一 Jupyter Notebook的魔法命令 模块 方法 或者help 模块 方法 查看模块 方法的解释文档 1 run 机械学习中主要应用两个魔法命令 run timeit 魔法命令格式 命令 run 将模块引用并在Jupyter No
  • Java设计模式——备忘录模式

    文章目录 备忘录模式 备忘录模式 主要目的是保存一个对象的某个状态 以便在适当的时候恢复对象 个人觉得叫备份模式更形象些 通俗的讲下 假设有原始类A A中有各种属性 A可以决定需要备份的属性 备忘录类B是用来存储A的一些内部状态 类C呢 就
  • PyQt5 pyqtSignal: 自定义信号传入的参数方法

    PyQt5 pyqtSignal 自定义信号传入的参数方法 在PyQt5当中 用户是可以自定义信号与槽函数的 这里想讲的是如何在pyqtSignal中传入任何的参数 一般来说 我们会在pyqtSignal 中传入不同的参数 以便完成不同类之
  • 我去公司面试,人事经理面试没有过,我却把责任链模式学会了

    设计模式在开发当中是运用特别多的 设计模式就是参照我们日常生活特性 抽象出特性 从而某种实现达到具体要求 当然这当中一定是灵活转变 责任链正式拉开序幕 我去某某互联网公司去面试 好的方向的流程大致应该是 你上招聘软件投递简历 简历筛选通过
  • 查看电脑是否开启虚拟化

    第一步 win R快捷键输入cmd 第二步 输入systeminfo命令 即可查看电脑配置信息
  • 5.1 数组

    C 为基本的数据类型 整数 浮点数 字符型和布尔型 提供了内置的支持 就像在上一章我们为复数类定义了重载的运算符那样 内置的支持也称为协助函数 helper function 支持这些数据类型完成各种允许的运算 也就是说基本数据类型也可以说
  • 【华为OD机试真题2023B卷 JAVA&JS】计算最接近的数

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 计算最接近的数 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 给定一个数组X和正整数K 请找出使表达式X i X i 1 X i K 1 结果最接近于数组中位数的下标
  • Modbus协议详解2:通信方式、地址规则、主从机通信状态

    首先我们要清楚 Modbus是一种串行链路上的主从协议 在通信线路上只能有一个主机存在 不会有多主机存在的情况 虽然主机只有一个 但是从机是可以有多个的 Modbus的通信过程都是由主机发起的 从机在接收到主机的请求后再进行响应 从机不会主
  • 30个 BeageBone 嵌入式项目

    特点 展示了如何使用 BeagleBone Black 编程和构建有趣且引人入胜的项目 学习如何将 BeagleBone Black 连接到您的计算机并对其进行编程 快速掌握 BoneScript 和其他编程工具 30 个 BeagleBo
  • 2023网站seo过时了吗?

    不完全是 虽然SEO过去的一些策略可能已经不再有效 但SEO本身并没有过时 实际上 随着搜索引擎算法的不断发展和用户对搜索结果质量的不断提高 合法 道德以及有效的SEO策略依然能够帮助网站获得搜索引擎排名并吸引大量有针对性的流量 尽管SEO
  • 接收I/O请求完成通知

    那片土在蓝天上 燃烧的翅膀 Windows via C C 学习笔记 设备I O之 接收I O请求完成通知 上一篇 讨论了如何发送I O请求 在异步的设备I O请求方式下 要考虑的问题就是当I O请求完成之后 驱动程序如何通知你的应用程序