《Windows核心编程》第10章开头部分一再强调:“IO Completion Port”是“构建高性能、可升缩的应用程序”的最佳设施之一,它不仅适用于处理设备IO,也适用于其它越来越多的应用场景,比如:Job内核对象、Socket编程等。故此,我单独用一篇博客来讲述“IO Completion Port”的应用场景、原理、用法和示例。

一、缘起

1,线程通信

对于一个服务应用程序(service application)来说,典型的操作是对客户请求进行处理,我们无法预见这些客户请求会在何时到达,也无法预见处理这些客户请求需要消耗多少处理器资源。这些操作常常来自诸如网卡、适配器之类的I/O设备,而对这些请求进行处理又经常会用到额外的I/O设备,比如磁盘文件。

在Windows应用程序中,线程是我们最好的工具,可以用来对工作进行划分。我们可以给每个线程指定 一个处理器,这样在多处理器的机器上就可以同时执行多个操作,从而提高吞吐量。当线程发出一个同步设备I/O请求的时候,它会被临时挂起,直到设备完成I/O请求为止。此类挂起会损害性能,这是因为线程无法进行有用的工作,比如开始对另一个客户请求进行处理。因此,简而言之,我们希望线程不会被阻塞住,这样它们就能始终进行有用的工作。

为了不让线程闲下来,我们需要让各个线程就它们正在执行的操作互相通信。这类通信机制可以帮助我们创建高性能的应用程序。

异步I/O实际上也是基于线程通信来实现的,如上图。左边的线程执行正常流程,右边的线程处理缓慢耗时的I/O过程;右边的线程处理完I/O后,会通知左边的线程,而左边的线程在运行到一定时候,也会停下来等待和检查是否有属于它的“完成通知”。

如何管理线程间的通信(或同步),以降低线程的阻塞时间(或等待时间),是设计高性能应用程序的关键。

2,经典服务器模型

回顾历史,我们能够采用以下两种模型之一来构架一个服务应用程序。

1)串行模型(serial model)

一个线程等待一个客户(通常是通过网络)发出请求。当请求到的时候,线程会被唤醒并对客户请求进行处理。

优点:简单,非异步。

缺点:不能同时处理多个请求。

2)并发模型(concurrent model)

一个线程等待一个客户请求,并创建一个新的线程来处理请求。当新线程正在处理客户请求的时候,原来的线程会进入下一个循环并等待另一个客户请求。当处理客户请求的线程完成整个处理过程的时候,该线程就会终止。

优点:能够同时接收多个请求。它相对于“一个等待线程 + 多个处理线程”,等待线程循环运行,随时可以接收客户请求。

比较串行模型和并发模型,可以它们最大的不同是:串行模型服务应用程序只有一线程(Single thread),而并发模型服务应用程序则是有多少个客户请求,就创建多少个线程(one-thread-per-client)。

缺点:一是,并发线程过多时,会增加系统的线程切换时间(context switching),降低CPU的有效工作时间。二是,创建和销毁线程需要一定时间开销。

针对并发模型的两个缺点,微软分别设计了IO完成端口和线程池两种设施,组合使用可以有效替代经典的并发模型。

二、理论

IO完成端口与线程池结合,主要用于创建高效的异步IO应用程序。为了理解IO完成端口的用法,我们需要先了解一些相关知识。

1,重叠结构(OVERLAPPED)

OVERLAPPED的是异步IO必备的一个数据结构。在《Windows核心编程》10.4.1节专门描述它,如下:

When performing asynchronous device I/O, you must pass the address to an initialized OVERLAPPED
structure via the pOverlapped parameter. The word "overlapped" in this context means that the time
spent performing the I/O request overlaps the time your thread spends performing other tasks.

通俗点说就是:

1)OVERLAPPED是ReadFile/WriteFile等异步操作APIs的一个必须的实参(传址方式)。

2)“overlapped”的意思是执行IO请求的时间与线程执行其他任务的时间是重叠的(overlapped)。

再来看MSDN的说明:

Contains information used in asynchronous (or overlapped) input and output (I/O).

链接:https://msdn.microsoft.com/en-us/library/windows/desktop/ms684342(v=vs.85).aspx

我们可以将OVERLAPPED理解为“与某个I/O请求关联的附加信息”。也有人将它理解为I/O请求的ID号,但我觉得“附加信息”更正确一点。事实上,在Jeffrey给的FileCopy.exe示例中,他对OVERLAPPED进行了扩展(继承),如下:

  1. // Each I/O Request needs an OVERLAPPED structure and a data buffer
  2. class CIOReq : public OVERLAPPED {
  3. public:
  4. CIOReq() {
  5. Internal = InternalHigh = 0;
  6. Offset = OffsetHigh = 0;
  7. hEvent = NULL;
  8. m_nBuffSize = 0;
  9. m_pvData = NULL;
  10. }
  11. ......
  12. private:
  13. SIZE_T m_nBuffSize;
  14. PVOID  m_pvData;
  15. };

Jeffrey的这个扩展揭示了“异步IO请求”的本质 —— an OVERLAPPED structure and a data buffer。我们真正需要传输的就是这两个东西,其中OVERLAPPED作为标识,buffer才是真实的信息载体。当然,如果是同步IO,一次只能接收一个请求,就无需OVERLAPPED这个标识物啦,直接传NULL即可。

注:ReadFile/WriteFile只有一个buffer,但是DeviceIoControl有两个buffer —— 输入buffer和输出buffer。这个buffer是信息的载体,而不是信息,事实上,ReadFile的发出请求的时候,buffer为空,待请求处理完了,系统会将信息填充到该buffer上。

2,IO完成端口

IO完成端口是一个内核对象,它的作用是管理关联设备(Associated Devices)的异步IO请求的完成通知。这个话比较拗口,通俗点说,IO完成端口会监视与它管理的设备,如果该设备的某次异步IO请求被处理完了,系统会先把完成通知发给IO完成端口,IO完成端口再将收到的完成通知排队,然后去调度等待列表中的线程来最终收尾一个IO请求。

要理解IO完成端口的工作原理,就需要先搞清楚它的3个列表和2个队列,见《Windows核心编程》图10-4。

1)设备列表

包含所有与该IO完成端口关联的设备。

2)完成队列(先入先出)

IO完成端口将收到的系统发给他的IO完成通知存储在完成队列中。

3)等待线程队列(后入先出)

存储可供IO完成端口调度(选择)的线程。IO完成端口会在该队列中选择一个线程了处理一项完成通知。

4)已释放线程列表

被IO完成端口选中并分派了一项IO完成通知的线程。

5)已暂停线程列表

已释放的线程调用了一个函数将自己挂起。也就是说“4)”中某个线程接到了完成通知,并进行了收尾工作,在这个收尾工作中,调用了诸如Sleep函数,将自己挂起了。

从以上的描述可知,IO完成端口维护了多项线程相关的信息,并协调线程共同高效地完成“并发IO请求”。换句话说,IO完成端口是线程通信的调度者。

3,完成通知(Completion Notification)

完成通知与IO请求是相对的。IO请求包含的是发送的信息(OVERLAPPED + buffer),完成通知包含的是返回信息。完成通知具体包含的信息有4项:

1)已传输字节数;

2)完成键值;

3)OVERLAPPED指针;

4)错误码;

三、流程

介绍完了IO完成端口的背景知识和基本原理后,下面开始介绍怎么使用IO完成端口。本文按照Jeffrey的思路来介绍IO完成端口的使用流程,另外还有其他使用流程,可以参考我的博客:EchoAPP 和Socket编程之IO完成端口,都配了流程图,它们的使用相对复杂一点。

Jeffrey为了方便使用,将IO完成端口封装为一个类—— class CIOCP ,它将创建完成端口和绑定设备分为两个步骤,见FileCopy.exe示例。我总结其流程大致如下:

注:

1)新开工作线程的数量一般设计为CPU数量的2倍。可以通过Windows API,“GetSystemInfo”来获取CPU信息,参考《Windows核心编程》第14章的“SysInfo.exe”示例。

2)在线程中开启循环,不让线程函数直接返回,这样可以避免反“复创建和销毁线程”。

四、示例

参考我的博客:EchoAPP 和Socket编程之IO完成端口,前者相对要简单一点,是普通设备IO,后者是Socket编程。

五,知识图片

我用一张思维导出总结了《Windows核心编程》关于完成端口的知识点,如下:

http://blog.csdn.net/Sagittarius_Warrior/article/details/52368970

《Windows核心编程》之“完成端口”(对所有IO都是如此,不仅仅是对socket)的更多相关文章

  1. windows核心编程---第九章 同步设备IO与异步设备IO之同步IO

    同步设备IO 所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行. 异步IO是指:线程发起IO请求后并不会挂起而是继续执行.IO完毕后会得到设备的通知.而IO完成端口就是实现这种通知的很 ...

  2. 《Windows核心编程》读书笔记 上

    [C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...

  3. 【Windows】windows核心编程整理(上)

    小续 这是我11年看<windows核心编程>时所作的一些笔记,现整理出来共享给大家 windows核心编程整理(上) windows核心编程整理(下) 线程的基础知识 进程是不活泼的,进 ...

  4. C++Windows核心编程读书笔记

    转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...

  5. 【转】《windows核心编程》读书笔记

    这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...

  6. 使用同步或异步的方式完成 I/O 访问和操作(Windows核心编程)

    0x01 Windows 中对文件的底层操作 Windows 为了方便开发人员操作 I/O 设备(这些设备包括套接字.管道.文件.串口.目录等),对这些设备的差异进行了隐藏,所以开发人员在使用这些设备 ...

  7. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

  8. windows核心编程---第八章 使用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  9. windows核心编程---第二章 字符和字符串处理

        使用vc编程时项目-->属性-->常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集.一般情况下说Unicode都是指UTF-16.也 ...

随机推荐

  1. 【Codeforces Round #446 (Div. 2) C】Pride

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 想一下,感觉最后的结果肯定是从某一段开始,这一段的gcd为1,然后向左和向右扩散的. 则枚举那一段在哪个地方. 我们设这一段中所有的 ...

  2. 【COGS1672】【SPOJ375】QTREE

    这是我的第一个边权链剖 COGS上和SPOJ有点不一样就是没有多组数据了本质还是一样的 我写的是COGS那个事实上改一改就能够去SPOJ AC了= -= (但是我如今上不去SPOJ卧槽(╯‵□′)╯︵ ...

  3. Android 撕衣服(刮刮乐游戏)

    项目简单介绍: 该项目为撕衣服,相似刮刮乐游戏 具体介绍: 用户启动项目后.载入一张图片,当用户点击图片的时候,点击的一片区域就会消失.从而显示出在这张图片以下的图片 这个小游戏相似与刮奖一样,刮开涂 ...

  4. 主定理(Master Theorem)与时间复杂度

    1. 问题 Karatsuba 大整数的快速乘积算法的运行时间(时间复杂度的递推关系式)为 T(n)=O(n)+4⋅T(n/2),求其最终的时间复杂度. 2. 主定理的内容 3. 分析 所以根据主定理 ...

  5. python基础-合并列表

    1.append()  向列表尾部追加一个新元素,列表只占一个索引位,在原有列表上增加 2.extend() 向列表尾部追加一个列表,将列表中的每个元素都追加进来,在原有列表上增加 3.+  直接用+ ...

  6. HDU - 4552 怪盗基德的挑战书 (后缀数组)

    Description "在树最漂亮的那天,当时间老人再次把大钟平均分开时,我会降临在灯火之城的金字塔前.带走那最珍贵的笑容."这是怪盗基德盗取巴黎卢浮宫的<蒙娜丽莎的微笑& ...

  7. Oracle数据库(三)

    专题一:oracle查询 1.where查询 查询部门编号是1的部门信息 ; 查询姓名是kw的员工,字符串使用‘’,内容大小写敏感 select *from emp where name='kw' 查 ...

  8. Linux平台Makefile文件的编写基础篇

    目的:        基本掌握了 make 的用法,能在Linux系统上编程. 环境:        Linux系统,或者有一台Linux服务器,通过终端连接.一句话:有Linux编译环境. 准备: ...

  9. 清除浮动.md

    清除浮动的三种方法 1 加空div层(.clear) 2 overflow属性设置(.clearo) 3 :after伪元素(.clearfix) <!DOCTYPE html> < ...

  10. 7.1 基础知识Android消息处理机制

    1. Android消息处理机制: Handler, MessageQueue, Looper, Thread 线程概念 : 一个应用程序运行时它的主体被称为进程, 一个进程内部可以有多个线程, 线程 ...