重疊I/O(Overlapped I/O)是Windows作業系統異步I/O的實現。自Windows NT引入. 重疊I/O對應於UnixPOSIX異步I/O的API (AIO).

重疊I/O特別適合於大量文件或者socket通信、pipe等場合. Windows 9x不支持重疊I/O.

原理

執行緒請求一個重疊操作的Windows API函數返回時,該重疊操作可能已經執行完,也可能未執行完還處於pending狀態。發起重疊操作的作業系統API函數返回TRUE,說明該操作已經完成;如果該函數返回FALSE且調用GetLastError函數返回ERROR_IO_PENDING,說明該重疊操作處於pending狀態。例如下述情形將同步完成IO操作:[1]

  • 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(), WinsockWSASend()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).