刘收获

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

IOCP IO完成端口

一. IO完成端口概念

  IO完成端口的出现是为了解决并发模型中可运行线程上下文切换开销过大而出现的。

  在《Windows核心编程》的描述中,IO完成端口是Wnidows系统提供的最复杂的内核对象,是一种解决并发IO请求的最佳模型,是用来实现高容量网路服务器的最佳方法。既然是一个对象,那么就直接分析一下操作系统眼中的完成端口的具体定义吧。Windows中利用CreateIoCompletionPort命令创建完成端口对象时,系统内部自动创建了5个相应的数据结构,分别是:设备列表(Device List)IO完成请求队列(I/O Completion Queue-FIFO)、等待线程队列(WaitingThread List-LIFO)、释放线程队列(Released Thread List)和暂停线程队列(Paused Thread List)

 

设备列表

ADD

每当调用CreateIoCompletionPort绑定到某个设备时,系统会将该设备句柄添加到设备列表中;(与完成端口相关联的<设备列表,完成键>对

REMOVE

每当调用CloseHandle关闭了某个设备句柄时,系统会将该设句柄从设备列表中删除;

I/O

完成队列

ADD

当I/O请求操作完成时,或者调用了PostQueuedCompeltionStatus函数时,系统会将I/O请求完成状态添加到I/O完成队列中,该队列是FIFO

REMOVE

当完成端口从等待线程队列中取出某一个工作线程时,系统会同时从I/O完成队列中取出一个元素。

等待

线程

队列

ADD

当线程中调用GetQueuedCompletionStatus函数时,系统会将该线程压入到等待线程队列中,该队列是LIFO(为了减少线程切换)

REMOVE

当I/O完成队列非空,且工作线程并未超出总的并发数时,系统从等待线程队列中取出线程,该线程从GetQueuedCompletoinStatus函数返回开始工作。

释放

线程

队列

ADD

1)当系统从等待线程队列中激活了一个工作线程时,或者挂起的线程重新被激活时,该线程被压入释放线程队列中

2)当线程重新调用GetQueuedCompeltionStatus函数时,线程被添加到等待线程队列中

REMOVE

当线程调用其他函数使得线程挂起时,该线程被添加到挂起线程队列中。

挂起

线程

队列

ADD

释放线程队列中的线程被挂起的时候,线程被压入到挂起线程队列中;

REMOVE

当挂起的线程重新被唤醒时,从挂起线程队列中取出。

 

 

 

                            

  1.创建IO完成端口 

  HANDLE WINAPI CreateIoCompletionPort(
    __in      HANDLE FileHandle,               //文件 设备句柄
    __in_opt  HANDLE ExistingCompletionPort,   //与设备关联的IO完成端口句柄,为NULL时,系统会创建新的完成端口
    __in      ULONG_PTR CompletionKey,         //完成键,用它来区分各个设备。
    __in      DWORD NumberOfConcurrentThreads  //允许运行的最大线程数量,如果传0表示允许并发执行的线程数量等于CPU主机数量(我的本机是4核8线程,计算机将CPU主机数量当作了8)
  );

  

  这个函数会完成两个任务: 一是创建一个IO完成端口对象,二是将一个设备与一个IO完成端口关联起来

  

  2.将已完成的IO请求投递到IO完成端口的队列   

  PostQueuedCompletionStatus(
    _In_ HANDLE CompletionPort,                             //完成端口的句柄
    _In_ DWORD dwNumberOfBytesTransferred,      
    _In_ ULONG_PTR dwCompletionKey,
    _In_opt_ LPOVERLAPPED lpOverlapped
  );

  

  后三个参数是为调用了那个GetQueuedCompletionStatus的线程而准备的

 

  3.GetQueuedCompletionStatus函数在检查IO完成队列里是否有已经完成的IO请求。  

  BOOL
  WINAPI
  GetQueuedCompletionStatus(
    _In_ HANDLE CompletionPort,                                     //完成端口句柄
    _Out_ LPDWORD lpNumberOfBytesTransferred,
    _Out_ PULONG_PTR lpCompletionKey,
    _Out_ LPOVERLAPPED * lpOverlapped,
    _In_ DWORD dwMilliseconds                                      //等待时间
  );

 

  (1)在等待线程队列中的线程调用GetQueuedCompletionStatus检查IO完成队列里是否有已经完成的IO请求时,如果IO完成队列中存在已完成的IO请求,则GetQueuedCompletionStatus先删除IO完成队列中这个对应的项,然后将线程ID转移到已释放线程列表中(即当前线程属于已释放列表中的一员了)

  (2)在已释放列表中的线程调用GetQueuedCompletionStatus检查IO完成队列里是否有已经完成的IO请求时,如果IO完成队列中不再存在已完成的IO请求,则线程ID再次回到等待线程队列中中。(即当前线程属于等待线程队列中的一员了)

 

 

 

二. IO完成端口实现文件拷贝流程。

      1.创建IO完成端口,并将源文件,目标文件和端口相关联

   2.将一个已完成的IO通知追加到IO完成队列中,(并非真的写只是让下面的代码从 【读操作】开始,执行序列为: 读-写, 读-写, ... ,读-写

  

  这里的代码是在《windows核心编程》中精简出来的,我也发现了《windows核心编程》源代码中的一个问题——更新OVERLAPPED结构成员,低32位偏移值和高32位偏移值Offset和OffsetHigh时,最后一次更新会无法更新上:

  

1
2
3
4
5
6
7
8
9
10
11
12
13
case CK_WRITE:
    //写入IO操作已经完成,下一步进行读取操作 
    WritesInProgress--;
    //当前文件偏移不能超过文件大小                   
    //超过文件大小就break,之后不再进入while循环,ReadsInProgress无法自加一,推出循环
    if (ReadOffset.QuadPart < SourceFileDataLength.QuadPart) {
        // Not EOF, read the next block of data from the source file.
        v1->Read(SourceFileHandle, &ReadOffset);
        ReadsInProgress++;
        ReadOffset.QuadPart += BUFFER_LENGTH;
    }
    break;
}

 

  每次写入IO操作已经完成,下一步进行读取操作时,read函数中更新了一次OVERLAPPED结构的Offset和OffsetHigh,但实际上!他们的更新值是上一次操作的旧偏移值了!因为ReadOffset.QuadPart += BUFFER_LENGTH;这一句在最后才执行,

  文件偏移ReadOffset.QuadPart 的刷新并没有赋值给Offset和OffsetHigh,而是到了下一次循环进来,才赋值上去,已经是陈旧的刷新值了,到了最后一次文件偏移不再小于源文件大小的时候,Read不再被调用,也就无法最后一次更新Offset和OffsetHigh。

 

IOCompletionPort.h

  

 

.cpp

  

 

posted on   沉疴  阅读(875)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示