跳转到内容

重叠I/O

维基百科,自由的百科全书
(重定向自Overlapped I/O)

重叠I/O(Overlapped I/O)是Windows操作系统对异步 I/O 的实现。自 Windows NT 引入。重叠 I/O 对应于 Unix 的 POSIX 异步I/O 的 API (AIO)。在 Windows API 中,这一机制通过 OVERLAPPED 结构及相关文件标志位完成操作与通知。

重叠 I/O 特别适合于大量文件或者 socket 通信、管道通信等场合。

原理

[编辑]

线程请求一个重叠操作的 Windows API 函数返回时,该重叠操作可能已经执行完,也可能未执行完(处于 pending 状态)。发起重叠操作的操作系统 API 函数返回 TRUE,说明该操作已经完成;如果返回 FALSE 且调用 GetLastError 函数获取的错误原因是 ERROR_IO_PENDING,说明该重叠操作处于 pending 状态。

下述情形将强制同步完成 I/O 操作:[1]

  • NTFS 文件系统已压缩/加密文件的读写
  • 写文件时如需扩展文件长度(除非先用 SetFileValidData 函数明确修改了文件长度,适合于 Windows XP 以后版本并且有管理员权限)
  • 数据已经放入了 CPU 缓存
  • 操作系统的 Diskette Cache Manager(磁盘操作缓存管理)面对着大量的文件读写请求,超过了它的线程池的能力。这时新增的文件读写请求将被以同步方式执行。

操作系统在重叠 I/O 操作执行完毕后,会通知发起重叠 I/O 请求的线程。有多种可选的监听方式:

  • 设备内核对象:重叠 I/O 函数参数 OVERLAPPED 中的 hEvent 为空时,使用 WaitForSingleObject 阻塞并等待此句柄完成。该方法不被推荐,因为在同一个设备句柄上同时进行多个重叠操作时将无法区分是哪个同步操作占用了句柄(signaled)。
  • 事件内核对象:在重叠 I/O 函数参数 OVERLAPPED 中的 hEvent 设置一个事件句柄,重叠 I/O 函数把这个事件重置为 nonsignaled。发起重叠I/O请求的线程可以在这个事件上等待(wait)。重叠I/O操作完成后,操作系统把这个事件标记为signaled。WaitForSingleObject,GetOverlappedResult或GetOverlappedResultEx也可以在这个事件上阻塞,等待其完成。hEvent对象必须是手动重置;如果使用自动重置,WaitForSingleObject()和 WaitForMultipleObjects()函数永不返回。
  • APC调用:使用 ReadFileEx(), WriteFileEx() 等函数发起重叠I/O请求时,在函数参数给出完成过程(completion routine,此时也可称为回调(callback)),重叠I/O执行完毕后操作系统把这些完成过程加入到发起重叠I/O请求的线程的异步过程调用(APC)中,当该线程处于alertable状态时这些APC会被操作系统调度该线程执行。
  • I/O 完成端口调用:进程创建 I/O 完成端口(I/O completion port)对象。使用 CreateIoCompletionPort 函数把文件句柄与完成端口关联起来。当一个重叠操作请求完成之后,操作系统会向该操作的设备句柄完成端口(若有)的 I/O 完成队列中投递一个完成包。线程池可以使用 GetQueuedCompletionStatus 函数在完成端口上等待 I/O 完成包。这突破了只能在发起操作的线程上异步调用完成过程的限制。
  • 线程池 I/O 完成对象:创建一个 I/O 线程池,把一个 I/O 设备句柄与一个完成函数关联到线程池 I/O 完成对象。每当执行异步I/O操作之前,调用 StartThreadpoolIo 函数。当该 I/O 操作完成时,会自动使用一个线程执行该完成函数。

OVERLAPPED 数据结构

[编辑]

OVERLAPPED 数据结构是 Windows 用于区分重叠操作的对象。每个重叠操作使用自己的 OVERLAPPED 对象。[2]

Windows 操作系统 API 的头文件 minwinbase.h(包含在 windows.h)中,定义了数据结构:

typedef struct _OVERLAPPED { // o 
    DWORD  Internal;        //通常被保留。当GetOverlappedResult()的参数bWait为真且字段Internal为STATUS_PENDING,GetOverlappedResult()被阻塞直至重叠操作完成。当GetOverlappedResult()返回False并且GatLastError()返回ERROR_IO_INCOMPLETE时,重叠操作尚未完成。
    DWORD  InternalHigh;    //通常被保留,当GetOverlappedResult()传回False时,为被传输数据的长度。
    DWORD  Offset;            //指定文件的位置,从该位置传送数据,文件位置是相对文件开始处的字节偏移量。调用 ReadFile或WriteFile函数之前调用进程设置这个成员,读写命名管道及通信设备时调用进程忽略这个成员;
    DWORD  OffsetHigh;      //指定开始传送数据的字节偏移量的高位字,读写命名管道及通信设备时调用进程忽略这个成员;
    HANDLE hEvent;            //发起同步操作的函数应把该事件设为nonsignaled状态;pending操作完成时,操作系统把该事件设为signaled状态    
} OVERLAPPED, *LPOVERLAPPED;

应用程序往往将一块内存的前部用作 OVERLAPPED 数据结构,紧随其后的是与具体重叠操作有关的输入输出数据。

OVERLAPPED 和数据缓冲区只有在重叠操作完成之后,才可以释放。否则会造成进程崩溃。

示例

[编辑]

按照重叠 I/O 模式打开一个文件(或其他 I/O 控制端,如命名管道),使用 FILE_FLAG_OVERLAPPED 标志:

HANDLE hFile;
hFile = CreateFile(szFileName,
                  GENERIC_READ,
                  0,
                  NULL,
                  OPEN_EXISTING,
                  FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
                  NULL);
if (hFile == INVALID_HANDLE_VALUE)
  ErrorOpeningFile();

使用重叠 I/O 的 Windows API 函数(如 ReadFile(), WriteFile(), Winsock 的 WSASend() 与 WSARecv())需要传递一个 OVERLAPPED 结构作为函数参数,函数调用后立即返回,由操作系统在后台完成 I/O 操作。

需要注意的是,一个 OVERLAPPED 结构对象在它绑定的重叠 I/O 还没有完成前,绝对不能被用户程序修改其值或者重用,也不能释放其所用内存(如果它是函数局部变量,那么这个函数不能退出,以免局部变量所用的运行栈被卷回(unwinding))。因为操作系统使 OVERLAPPED 来区别同一个文件句柄下的可能多个未完成的不同的重叠 I/O。同样,重叠 I/O完成之前,它的数据缓冲区也决不能被程序读写。

if (!ReadFile(hFile,
                 pDataBuf,
                 dwSizeOfBuffer,
                 &NumberOfBytesRead,  //接收I/O操作完成的字节数,对于重叠I/O这个输出值无意义
                 &osReadOperation )   //OVERLAPPED结构
   {//如果函数返回false,表示IO操作没有立即全部执行完
      if (GetLastError() != ERROR_IO_PENDING)
      {
         // Some other error occurred while reading the file.
         ErrorReadingFile();
         ExitProcess(0);
      }
      else
         // Operation has been queued and
         // will complete in the future.
         fOverlapped = TRUE;
   }
   else
      // Operation has completed immediately.
	  // 即使作为重叠I/O提交的请求,仍然有可能被操作系统按照同步方式执行完IO操作,这时不应该再去GetOverlappedResult或WaitForSingleObject
      fOverlapped = FALSE;

   if (fOverlapped)
   {
      // Wait for the operation to complete before continuing.
      // You could do some background work if you wanted to.
      if (GetOverlappedResult( hFile,
                               &osReadOperation,
                               &NumberOfBytesTransferred, //接收I/O操作完成的字节数
                               TRUE))//该参数为true将阻塞该线程等待IO操作完成
         ReadHasCompleted(NumberOfBytesTransferred);
      else
         // Operation has completed, but it failed.
         ErrorReadingFile();
   }
   else
      ReadHasCompleted(NumberOfBytesRead);

参考文献

[编辑]
  1. ^ MSDN: Asynchronous Disk I/O Appears as Synchronous on Windows. [2016-09-07]. (原始内容存档于2016-10-19). 
  2. ^ GrantMeStrength. OVERLAPPED (minwinbase.h) - Win32 apps. learn.microsoft.com. [2026-10-20] (中文(中国大陆)).