重叠I/O

重叠I/O（Overlapped I/O）是Windows操作系统对异步I/O的实现。自Windows NT引入. 重叠I/O对应于Unix的POSIX异步I/O的API (AIO).

重叠I/O特别适合于大量文件或者socket通信、pipe等场合. Windows 9x不支持重叠I/O.

## 原理

线程请求一个重叠操作的Windows API函数返回时，该重叠操作可能已经执行完，也可能未执行完还处于pending状态。发起重叠操作的操作系统API函数返回TRUE，说明该操作已经完成；如果该函数返回FALSE且调用GetLastError函数返回ERROR_IO_PENDING，说明该重叠操作处于pending状态。例如下述情形将同步完成IO操作：

- NTFS文件系统已压缩文件的读写
- NTFS文件系统加密文件的读写
- 写文件时如果要扩展文件长度（除非先用SetFileValidData函数明确修改了文件长度，适合于Windows XP以后版本并且有管理员权限）
- 数据已经放入了CPU缓存(cache)
- 操作系统的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函数永不返回。
- 使用ReadFileEx, WriteFileEx等函数发起重叠I/O请求时，在函数参数给出完成过程（completion routine），重叠I/O执行完毕后操作系统把这些完成过程加入到发起重叠I/O请求的线程的异步过程调用（APC）中，当该线程处于alertable状态时这些APC会被操作系统调度该线程执行。
- 进程创建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数据结构

Windows操作系统API的头文件minwinbase.h（包含在windows.h）中，定义了数据结构：

```c
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数据结构对象，可用于区别这些重叠操作。应用程序往往在一块内存的前部用作OVERLAPPED数据结构，紧随其后的是与具体重叠操作有关的输入输出数据。OVERLAPPED和数据缓冲区，只有在重叠操作完成之后，才可以释放；否则会造成进程崩溃。

## 示例

按照重叠I/O模式打开一个文件（或其他I/O控制端，如命名管道），注意必须用`FILE_FLAG_OVERLAPPED`标志：

```c
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完成之前，它的数据缓冲区也决不能被程序读写。

```c
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);
```

## 参考文献

- MSDN Reference: ReadFile
- MSDN Reference: WriteFile
- MSDN Reference: OVERLAPPED Data structure
- I/O Completion Port