《Windows核心编程》之“完成端口”

原创 2016年09月02日 14:53:42

    《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进行了扩展(继承),如下:

// Each I/O Request needs an OVERLAPPED structure and a data buffer
class CIOReq : public OVERLAPPED {
public:
   CIOReq() {
      Internal = InternalHigh = 0;   
      Offset = OffsetHigh = 0;   
      hEvent = NULL;
      m_nBuffSize = 0;
      m_pvData = NULL;
   }
    ......
private:
   SIZE_T m_nBuffSize;
   PVOID  m_pvData;
};

    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核心编程》关于完成端口的知识点,如下:


《windows核心编程》读书笔记——完成端口

完成端口是windows为了处理不同设备之间并发运行时运行速度差异而设计的一种异步数据处理的机制。完成端口是《windows核心编程》中的一个重点内容,最近我花了三天时间读完核心编程里面关于完成端口的...
  • eagleatustb
  • eagleatustb
  • 2012年10月31日 17:19
  • 1396

《Windows核心编程》之“Windows挂钩”(三)

本文主要结合之前两篇介绍的“Windows挂钩”知识,讲解《Windows核心编程》一书中的“DIPS"示例程序和我的调试经验。 一、程序功能介绍 1,功能需求     DIPS.exe应用程序使...
  • Sagittarius_Warrior
  • Sagittarius_Warrior
  • 2016年08月12日 14:56
  • 570

《Windows核心编程》之“Windows挂钩”(二)

本文接上篇,继续探讨“Windows挂钩”,包括:跨进程的“窗口子类化”,使用Windows Hook注入DLL,跨进程窗口通讯,进程间共享内存和示例程序调试总结。...
  • Sagittarius_Warrior
  • Sagittarius_Warrior
  • 2016年08月11日 16:54
  • 410

<windows核心编程>动态链接库(一)

自出版Windows操作系统以来,动态链接库(DLL)一直是这个操作系统的基础。Windows API中所有的函数都包含在DLL中,3个最重要的DLL是 Kernel32.dll:它包含用于管理内存、...
  • u012307430
  • u012307430
  • 2014年02月12日 00:34
  • 610

windows Socket编程之完成端口模型

上篇介绍了重叠IO模型,它已经把我们的等待数据到来和拷贝数据到我们程序的缓冲区这个时间全部交给了操作系统去完成了,它已经很完善了。但是,如果我们想要把服务端的性能做的更好一点的话,它还是有点不足的,比...
  • Timmiy
  • Timmiy
  • 2016年08月17日 15:09
  • 1268

《Windows核心编程》之“Windows挂钩”(一)

《Windows核心编程》一书中介绍了一种针对带窗口的Windows应用程序的“DLL注入”的方法——Windows Hook(窗口挂钩)。本系列文章将探讨这种技术的原理和分享我的实验心得。...
  • Sagittarius_Warrior
  • Sagittarius_Warrior
  • 2016年08月11日 15:27
  • 897

Windows核心编程(十八)DLL基础

DLL全称dynamic linking library。即动态链接库。广泛应用与windows及其他系统中。   windows中所有API都包含在DLL中。三个最重要的DLL是Kernel3...
  • woshibendangao
  • woshibendangao
  • 2014年04月06日 12:18
  • 880

C++完成端口组件实现

在windows平台上,处理众多的TCP连接的机制中效率最高的是完成端口模型,关于完成端口模型的介绍可参照《Win32 多线程程序设计》(侯捷翻译)和《windows网络编程》。异步机制是完成端口的基...
  • zhuweisky
  • zhuweisky
  • 2005年07月01日 15:59
  • 3938

《Windows核心编程》读书笔记二十章 DLL高级技术

第二十章 DLL高级技术 本章内容 20.1 DLL模块的显示载入和符号链接 20.2 DLL的入口点函数 20.3 延迟载入DLL 20.4 函数转发器 20.5 已知的DLL 20.6 DLL重定...
  • sesiria
  • sesiria
  • 2017年12月18日 10:29
  • 125

socket通信之八:完成端口模型实现的客户/服务器模型

完成端口的详细说明可以参考下面这两篇文章,理论讲的很好。 手把手教你玩转SOCKET模型:完成端口(Completion Port)详解  完成端口(I/O completion)原理 收藏 ...
  • u012501459
  • u012501459
  • 2015年09月03日 16:10
  • 1174
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《Windows核心编程》之“完成端口”
举报原因:
原因补充:

(最多只允许输入30个字)