重疊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）中，定義了數據結構：
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標誌：
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);

**參考文獻**
 MSDN Reference: ReadFile() date=20170317225710 - MSDN Reference: WriteFile() date=20171002085213 - MSDN Reference: OVERLAPPED Data structure date=20150112071741 - I/O Completion Port date=20140320180058