天天看點

IOCP Internals

Buffer Type

Buffer I/O

針對Buffer I/O的請求,系統會為其配置設定一個非換頁記憶體作為緩存區,其大小等同于I/O請求的緩存區大小。對于寫操作,I/O管理器在建立IRP時,将請求者的緩存區資料拷貝到申請得到的非換頁緩存區中。對于讀操作,I/O管理器将會在I/O完成時,将資料從非換頁記憶體緩存區中拷貝到請求者的緩存區中。在I/O完成後,會釋放掉該次申請到的非換頁緩存區。

Direct I/O

I/O管理器在處理Direct I/O類型的請求時,會将請求者的緩存區鎖定在記憶體中,使其變成不可換頁的記憶體。當該I/O處理完成時,I/O管理器會解除請求者緩存區的鎖定。I/O管理器用MDL機制鎖定請求的緩存區,在需要操作緩存區的時候,可以使用相應的API映射請求者的緩存區到系統的位址空間。

Neither I/O

對于此種I/O請求,I/O管理器不會執行任何緩存區管理,對應的緩存管理交給相應的裝置驅動程式自己處理。但是不管裝置驅動程式如何處理,都不會超過上面兩種方式的處理。

I/O Type

Synchronous I/O

應用程式發出的大部分I/O操作是同步的。在裝置處理I/O請求時,應用程式需要一直等待,直到I/O完成時,裝置傳回結果,最終程式才可以繼續執行。并且可以立即通路這些被傳輸的資料。

Asynchronous I/O

異步I/O是程式先發出一個I/O請求,交由裝置處理,程式可以繼續執行。該程式可以在處理其他事務後檢查I/O是否完成,或者當I/O完成時,I/O管理器主動告訴程式I/O已完成。  為了在WINDOWS使用異步I/O,打開檔案或建立SOCKET時,需要指定FILE_FLAG_OVERLAPPED标志。

在發起一個異步I/O請求後,必須十分小心:在I/O未完成時,不能通路該I/O操作中任何資料,要確定緩存區有效。發起異步I/O後,為了拿到最終有效的資料,需要做同步操作,WINDOWS下支援OVERLAPPED,也就是基于EVENT核心對象同步,APC(異步過程調用)還有IOCP(I/O Completion Port)。

I/O Completion Process

當一個裝置處理完I/O請求時,會調用核心API IoCompleteRequest,通知I/O管理器自己完成了該I/O請求。為了安全操作使用者的緩存區,I/O管理器會根據I/O請求類型會執行相應的動作:如果是同步I/O請求,那麼可以直接操作請求的緩存區;如果是異步I/O請求,I/O管理器會延遲該I/O完成動作直到請求者對應的位址空間為止。完成該動作的方式是向該請求的線程投遞一個APC。在切換到該請求的I/O請求所在的線程時,該APC會得到執行。會把已完成的I/O請求中得到的資料拷貝到對應的緩存區中。這個時候,對于異步I/O請求,像ReadFileEx/WriteFileEx可以投遞一個APC例程,I/O管理器在執行I/O完成最後一步後,檢測到有APC,就把該APC例程投遞到請求線程内,當線程處于警告狀态時(SleepEx, WaitForSingleObjectEx)會得到執行。如果該異步I/O請求對應的檔案對象或者SOCKET綁定了IOCP,則會在處理完成時,把對應的OVERLAPPED結構投遞到IOCP隊列中。  這也就是當處理WSARecv時(假設該請求的SOCKET已經綁定了IOCP),如果這個時候系統中已經緩存了資料,這個時候不需要判斷是否接收到資料,而隻需要判斷LastError是否是ERROR_IO_PENDING的原因。 

IOCP

在實作服務端程式時,常用的一個簡單處理網絡請求的方式是針對連接配接配置設定一個線程單獨處理。太少或太多都會帶來性能問題。線程太少,對于用戶端的請求無法及時處理;太多,會導緻線程頻繁輪換。理想的情形是,在伺服器的每個處理器上都有一個線程在積極的處理一個使用者的請求,如果有其他的請求在等待的話,會立刻有線程來處理這些請求。為了達到這一最優過程得以最終正确的進行,需要有一個辦法,能夠在處理客戶請求的線程在I/O阻塞時,能激活另一個線程處理其他請求。 

WINDOWS中引入了IOCP機制來實作這一最優過程。IOCP的内部是依靠一個叫隊列的核心對象完成這一過程。當建立一個檔案句柄或者SOCKET時,可以把句柄與IOCP句柄綁定在一塊,任何以此句柄發起的異步I/O請求操作在完成時都會生成一個CompletionPackage,并把該CompletionPackage排隊到該完成端口上,也就是排隊到内部的隊列核心對象上。應用程式可以通過API GetQueuedCompletionStatus擷取已完成的CompletionPackage,處理已完成的I/O資料。 

WINDOWS API中的WaitForMultipleObjects函數提供了類似的功能,但是,完成端口的好處是,在系統協助下并發是可控的。處理方式是應用程式主動處理用戶端請求的線程的數量。前面提到,理想情況是每個處理器都由一個線程在處理用戶端請求。在建立IOCP時,會制定一個值,這個值為關注IOCP的線程數。因為IOCP的内部機制依賴于隊列核心對象,在WINDOWS中,核心對象都是可等待的,也就是用類似WaitForSingleObject類似的機制。而IOCP在建立時制定的值會傳給内部的隊列核心對象作為一個并發值。如果與IOCP關聯的活動線程的數量超過了這個并發值,那麼正在該IOCP上等待的線程将不允許運作。當有異步I/O完成時,會投遞CompletionPackage到綁定的IOCP中的隊列對象中,如果此時有線程正在等待,則會直接把這個CompletionPackage交給這個正在等待的線程處理。這樣的過程中,沒有線程環境切換,更好的把CPU的能力利用起來。 

關于IOCP的并發值,Microsoft的指導原則是,将并發值設定成大約等于該系統中處理器的數目。

參考文檔:

  1. 《深入解析Windows作業系統第四版》
  2. Windows Research Kernel

繼續閱讀