Windows核心原理-同步IO與非同步IO
目錄
- Windows核心原理-同步IO與非同步IO
- 背景
- 目的
- I/O
- 同步I/O
- 非同步I/O
- I/O完成通知
- 總結
- 參考文件
Windows核心原理-同步IO與非同步IO
背景
在前段時間檢查異常連線導致的記憶體洩漏排查的過程中,主要涉及到了windows非同步I/O相關的知識,看了許多包括重疊I/O、完成埠、IRP、裝置驅動程式等Windows下I/O相關的知識,雖然學習到了很多東西,但是仍然需要自頂而下的將所有知識進行梳理。
目的
本片文章主要講解同步I/O與非同步I/O相關知識,希望通過編寫本篇文章為起點,對windows核心原理知識進行學習與梳理。發現並彌補遺漏的知識點並加以學習。同時通過理解windows核心原理,設計出更好、更合理的應用程式。
I/O
I/O即輸入輸出。在現在作業系統,輸入輸出是計算機完整功能必不可少的一部分。處理器負責各種計算任務,然後通過各種輸入輸出裝置與外界進行互動。常見的輸入輸出裝置包括鍵盤、滑鼠、顯示器、硬碟、網路介面卡介面等。有了硬體裝置,在軟體層面上,使得作業系統通過以一致的方式與裝置驅動互動從而的操控硬體裝置。而應用程式通過統一的介面與系統核心進行互動。
Windows從一開始就設計了可擴充套件的I/O介面。在應用層通過統一的Win32 API
例程就是系統提供的API或服務。
在Windows下分為核心模式和使用者模式。應用程式執行在使用者模式下,作業系統和驅動程式執行在核心模式下。應用程式通過呼叫Win32 API
與Windows核心互動。
Windows核心則通過裝置驅動程式與裝置控制器進行通訊,而裝置控制器則直接操控硬體裝置。
裝置驅動程式分為即插即用驅動程式、核心擴充套件驅動程式和檔案系統驅動程式。其中檔案系統驅動程式用於接收I/O請求,然後將請求轉換為真正的儲存裝置或網路裝置的I/O請求。
裝置控制器可以通過記憶體對映I/O的方式將裝置的記憶體與主存對映,通過記憶體對映I/O後,處理器訪問的就不是主存而是裝置控制器的暫存器記憶體。但是這種方式的訪問效率並不高,不適合大資料量I/O讀寫。通常硬碟和網路驅動器採用直接訪問記憶體(DMA)的方式進行大量資料的I/O操作。DMA需要硬體支援,硬體會有DMA控制器,在硬體執行I/O操作的時候,不會佔用CPU的指令週期,DMA控制器會和裝置進行I/O操作。當資料傳輸完成後,DMA則會通知處理器I/O操作完成。
同步I/O
當我們要把檔案從硬碟讀取到記憶體時,硬碟的讀取速度是遠小於記憶體的寫入速度的。因此當我們使用一個執行緒從硬碟讀取檔案到記憶體中時。通常需要等待硬碟將資料從硬碟讀取到記憶體中,此時執行緒將被阻塞,但是不會消耗指令週期。當讀取完畢時,執行緒繼續執行後續操作。
雖然DMA執行的時候當前執行緒被阻塞,此時處理器可以獲取另一個執行緒核心執行其他操作,由於執行緒是非常昂貴的資源,因此使用同步I/O的方式若需要併發執行時,需要大量的建立執行緒資源,這就產生了大量的執行緒上下文切換。
在大多數x86和x64的多處理器,執行緒上下文切換時間間隔大約為15ms。
CPU每過大約15ms將CPU暫存器當前的執行緒上下文存回到該執行緒的上下文,然後該執行緒不在執行。然後系統檢查剩下的可排程執行緒核心物件,選擇一個執行緒的核心物件,將其上下文載入導CPU暫存器中。
關於Windows執行緒相關內容可以查閱《Windows via C/C++ 第五版》的第七章
非同步I/O
前面提到了當硬體進行I/O傳輸時,實際上通常使用DMA技術執行I/O操作,不會佔用CPU的指令週期。因此只要作業系統支援非同步I/O,則可以極大的提升系統性能,最大程度的降低執行緒數量,減少執行緒上下文切換產生的效能損失。
在Windows下的非同步I/O我們也可以稱之為重疊(overlapped)I/O。重疊的意思是執行I/O請求的時間與執行緒執行其他任務的時間是重疊的,即執行真正I/O請求的時候,我們的工作執行緒可以執行其他請求,而不會阻塞等待I/O請求執行完畢。
當使用一個執行緒向裝置發出一個非同步I/O請求時,該請求被傳給裝置驅動程式,裝置驅動程式處理I/O請求時並不會等待I/O請求完成,而是將I/O請求加入到裝置驅動程式的佇列中,然後返回一個I/O處理中的訊號。而實際的I/O操作則由裝置驅動程式將I/O請求傳給指定的硬體裝置執行I/O操作。應用程式的執行緒並不需要掛起等待I/O請求的完成,從而可以繼續執行其他任務。當某一時刻裝置驅動程式完成了該I/O請求處理,裝置控制器通過中斷指令通知I/O請求完成,處理器則將通知I/O請求已完成。
I/O完成通知
在Windows中一共支援四種接收完成通知的方式。分別為觸發裝置核心物件、觸發時間核心物件、可提醒I/O以及I/O完成埠。
觸發裝置核心
當裝置驅動載入時會建立一個裝置驅動物件,裝置驅動程式還會為裝置建立對應的裝置物件。裝置物件代表的是每一個物理裝置或邏輯裝置。裝置物件描述了一個特定裝置的狀態資訊,包括I/O請求的狀態。在通過非同步I/O將I/O請求新增到佇列之前,會將裝置核心物件設定為未觸發,此時就可以使用該裝置核心物件進行同步操作,當I/O請求完成後則會將裝置核心物件設定為觸發狀態。使用裝置核心物件進行執行緒同步時,無法區分當前完成通知的I/O是讀操作還是寫操作,因此無論是讀還是寫都會將其狀態設定為觸發狀態。
事件核心物件
通過裝置核心物件進行I/O通知由於無法區分讀寫操作,因此並沒有什麼用。通過事件核心物件我們可以將讀寫事件分離。在呼叫讀寫操作的時候會返回對應的讀寫事件核心物件。這樣我們就可以等待對應的事件核心物件知道是什麼I/O操作完成。我們可以通過等待多個事件核心物件。但是一次性最多隻能等待64個事件核心物件,即一個執行緒最多隻能建立64個事件核心物件進行等待。若需要監控上萬個連線,則需要建立上百個執行緒進行監控。
可提醒I/O
在系統建立執行緒的時候會建立一個與執行緒相關的佇列,該對壘被稱為非同步呼叫(APC)佇列,當發出一個I/O請求時,我們可以告訴裝置驅動程式在呼叫執行緒的APC佇列中新增一項完成函式,在I/O完成通知時呼叫完成函式進行回撥。I/O完成通知最大的問題是,請求時哪個執行緒呼叫的,必須由哪個執行緒回撥。它不支援負載均衡機制。
完成埠
I/O完成埠的設計理論依據是併發程式設計的執行緒數必須有一個上限,即最佳併發執行緒數為CPU的邏輯執行緒數。I/O完成埠充分的發揮了併發程式設計的優勢的同時又避免了執行緒上下文切換帶來的效能損失。
完成埠可能是最複雜的核心對現象,但是它又是Windows下效能最佳的I/O通知方式。
首先我們需要建立一個I/O完成埠,建立完成埠的時候可以指定執行緒數量。通過將裝置與I/O完成埠進行關聯。此使我們發出的I/O請求時,系統核心返回IO_PENDDING
狀態,然後執行緒就可以繼續處理其他事情。而DMA繼續執行I/O操作,將資料從裝置讀取到裝置控制器的緩衝區中,並對其進行必要的校驗後,將資料通過系統匯流排傳輸到記憶體中。當資料傳輸完成後,DMA發出中斷指令通知資料傳輸完畢,系統則會通過前面建立的I/O執行緒將I/O完成請求加入到I/O完成佇列中。
然後我們通過呼叫Win32 API
就可以獲取到對應的裝置I/O完成請求通知,通知會將I/O完成請求從完成佇列移除。
總結
- 同步I/O會阻塞執行緒,想要提高執行速度必須增加執行緒,但是會由於執行緒上下文切換造成效能損失。
- Windows下大約每15ms會進行一次執行緒排程。減少windows執行緒能降低記憶體佔用(預設執行緒棧大小為1M),降低執行緒上下文切換造成的效能損失。
- Windows支援原生的非同步I/O。非同步I/O也可以稱為重疊I/O。使用非同步I/O時執行緒不會阻塞,系統底層將每個I/O請求生成I/O請求包(IRP)加入到裝置驅動程式的請求佇列中,然後直接返回IOPENDDING狀態表示請求受理成功,當底層裝置完成了真實的I/O請求後會通過中斷控制器通過中斷操作通知CPU,CPU會排程一個執行緒通知上層裝置驅動程式,將完成通知加入到完成佇列中。此時上層應用即可獲取到完成通知。
- 完成埠是windows下效能最佳的完成通知方式。它最大程度的減少執行緒上下文切換。
- 使用非同步I/O和完成埠實現高效能I/O操作的主要原因有三點。一是減少I/O上下文切換;二是非同步不阻塞執行緒,預先提供一個socket用於連線,而不是接受到時再建立socket(socket建立也是比較耗資源的);三是避免了記憶體複製。
- 如何減少執行緒,如何避免記憶體複製,如何提高執行緒利用率,避免執行緒阻塞。以上幾點是所有高效能框架或高效能應用程式必備的條件。
參考文件
- cpu記憶體訪問速度,磁碟和網路速度
- 手把手教你玩轉SOCKET模型:完成埠(Completion Port)詳解
- Reactor與Proactor的概念
- 如何深刻理解reactor和proactor?
- I/O Completion Ports
- 《Windows via C/C++ 第五版》
- 《Windows核心原理與實現》
本文地址:https://www.cnblogs.com/Jack-Blog/p/11385686.html
作者部落格:傑哥很忙
歡迎轉載,請在明顯位置給出出處及連結