在前段時間檢查異常鏈接致使的內存泄漏排查的過程當中,主要涉及到了windows異步I/O相關的知識,看了許多包括重疊I/O、完成端口、IRP、設備驅動程序等Windows下I/O相關的知識,雖然學習到了不少東西,可是仍然須要自頂而下的將全部知識進行梳理。react
本片文章主要講解同步I/O與異步I/O相關知識,但願經過編寫本篇文章爲起點,對windows內核原理知識進行學習與梳理。發現並彌補遺漏的知識點並加以學習。同時經過理解windows內核原理,設計出更好、更合理的應用程序。編程
I/O即輸入輸出。在如今操做系統,輸入輸出是計算機完整功能必不可少的一部分。處理器負責各類計算任務,而後經過各類輸入輸出設備與外界進行交互。常見的輸入輸出設備包括鍵盤、鼠標、顯示器、硬盤、網絡適配器接口等。有了硬件設備,在軟件層面上,使得操做系統經過以一致的方式與設備驅動交互從而的操控硬件設備。而應用程序經過統一的接口與系統內核進行交互。
Windows從一開始就設計了可擴展的I/O接口。在應用層經過統一的Win32 API
,將I/O請求分配給正確的設備驅動程序。設備驅動程序調用設備控制器來操控硬件。而內核經過硬件抽象層與硬件進行交互。硬件抽象層提供了供內核和驅動調用的例程。windows
例程就是系統提供的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操做完成。app
當咱們要把文件從硬盤讀取到內存時,硬盤的讀取速度是遠小於內存的寫入速度的。所以當咱們使用一個線程從硬盤讀取文件到內存中時。一般須要等待硬盤將數據從硬盤讀取到內存中,此時線程將被阻塞,可是不會消耗指令週期。當讀取完畢時,線程繼續執行後續操做。
雖然DMA執行的時候當前線程被阻塞,此時處理器能夠獲取另外一個線程內核執行其餘操做,因爲線程是很是昂貴的資源,所以使用同步I/O的方式若須要併發執行時,須要大量的建立線程資源,這就產生了大量的線程上下文切換。負載均衡
在大多數x86和x64的多處理器,線程上下文切換時間間隔大約爲15ms。
CPU每過大約15ms將CPU寄存器當前的線程上下文存回到該線程的上下文,而後該線程不在運行。而後系統檢查剩下的可調度線程內核對象,選擇一個線程的內核對象,將其上下文載入導CPU寄存器中。
關於Windows線程相關內容能夠查閱《Windows via C/C++ 第五版》的第七章框架
前面提到了當硬件進行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請求已完成。
在Windows中一共支持四種接收完成通知的方式。分別爲觸發設備內核對象、觸發時間內核對象、可提醒I/O以及I/O完成端口。
當設備驅動加載時會建立一個設備驅動對象,設備驅動程序還會爲設備建立對應的設備對象。設備對象表明的是每個物理設備或邏輯設備。設備對象描述了一個特定設備的狀態信息,包括I/O請求的狀態。在經過異步I/O將I/O請求添加到隊列以前,會將設備內核對象設置爲未觸發,此時就可使用該設備內核對象進行同步操做,當I/O請求完成後則會將設備內核對象設置爲觸發狀態。使用設備內核對象進行線程同步時,沒法區分當前完成通知的I/O是讀操做仍是寫操做,所以不管是讀仍是寫都會將其狀態設置爲觸發狀態。
經過設備內核對象進行I/O通知因爲沒法區分讀寫操做,所以並無什麼用。經過事件內核對象咱們能夠將讀寫事件分離。在調用讀寫操做的時候會返回對應的讀寫事件內核對象。這樣咱們就能夠等待對應的事件內核對象知道是什麼I/O操做完成。咱們能夠經過等待多個事件內核對象,可是一次性最多隻能等待64個事件內核對象,即一個線程最多隻能建立64個事件內核對象進行等待。若須要監控上萬個鏈接,則須要建立上百個線程進行監控。
在系統建立線程的時候會建立一個與線程相關的隊列,該隊列被稱爲異步調用(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完成請求從完成隊列移除。
IO_PENDDING
狀態表示請求受理成功,當底層設備完成了真實的I/O請求後會經過中斷控制器經過中斷操做通知CPU,CPU會調度一個線程通知上層設備驅動程序,將完成通知加入到完成隊列中。此時上層應用便可獲取到完成通知。本文地址:http://www.javashuo.com/article/p-onjikisg-kw.html 做者博客:傑哥很忙 歡迎轉載,請在明顯位置給出出處及連接