(四)談談 IO 模型(操作系統編)

Linux 為什麼要區分內核空間與用戶空間?

Linux 操作系統的 IO 模型有哪幾種?有啥區別?

常說的阻塞現象,到底是咋回事?

網絡編程研發時,那塊到底耗時最多,代碼是否還有優化空間?


前幾期的分享,我們站在編碼視角去聊 Java IO,旨在理解與編碼,本次從 Linux 操作系統層面瞭解一下 IO 模型,這樣方能做到知其然,知其所以然。


01. 內核空間、用戶空間

萬事萬物我們看到的皆是表象,操作系統也不例外。我們經常打交道的用戶界面,是操作系統的外在表象,內核才是操作系統的內在核心。


內核,可以訪問受保護的內存空間,擁有訪問底層硬件設備的所有權限(比如讀寫磁盤文件,分配回收內存,從網絡接口讀寫數據等等)。


(四)談談 IO 模型(操作系統編)


為了內核的安全,操作系統將虛擬空間劃分為內核空間(內核代碼運行的地方)和用戶空間(用戶程序代碼運行的地方)。內核空間和用戶空間是隔離的,這樣即使用戶的程序崩潰了,內核也不受影響。


另外,用戶程序不能直接操作內核,需要通過系統調用來與內核進行通信(應用程序通過內核提供的接口來完成訪問)。


02. Socket 通信流程

(四)談談 IO 模型(操作系統編)


Socket 通信流程應該不再陌生,本次分享著重剖析圖中圈住的部分。


(四)談談 IO 模型(操作系統編)


站在服務端的視角,對於一次 Socket 的數據讀取操作流程,如圖示意,網絡數據到達網卡,數據先被拷貝到內核緩衝區中,然後從內核緩衝區拷貝到進程用戶空間。


站在服務端的視角,當一個讀操作發生時,稍微再細化一下,其實會經歷兩個階段。


第一階段:等待數據準備。

例如:recv() 等待數據,需要等待網絡上的數據分組到達,然後被複制到內核的緩衝區。


第二階段:將數據從內核緩衝區拷貝到用戶空間。

例如:recv() 接收連接發送的數據後,需要把數據複製到內核緩衝區,再從內核緩衝區複製到進程用戶空間。


一定記住這兩個階段,也正因為存在這兩個階段,Linux系統升級迭代中出現了五種網絡 IO 模型。


03. Linux 網絡 IO 模型

(一)阻塞 IO 模型 - Blocking IO


(四)談談 IO 模型(操作系統編)


圖解:當應用進程調用了 recv() 這個系統調用,內核就開始了 IO 的第一個階段:準備數據。這個過程需要等待,也就是說數據被拷貝到操作系統內核的緩衝區中是需要一個過程的。而在用戶進程這邊,整個進程會被阻塞。當內核一直等到數據準備好了,它就會將數據從內核中拷貝到用戶內存,然後內核返回結果,用戶進程才解除阻塞的狀態。


特點:在 IO 執行的兩個階段都被阻塞了。


場景:阻塞 Socket、Java BIO適用併發較小的網絡應用,併發較大的不適用,因為一個請求 IO 阻塞進程,就要為每個請求分配一個進程(線程)來響應,開銷大。


(二)非阻塞 I/O 模型 - Non-Blocking IO


(四)談談 IO 模型(操作系統編)


圖解:當用戶進程發出 recv() 操作時,如果內核中的數據還沒有準備好,那麼它並不會阻塞用戶進程,而是立刻返回一個錯誤碼。一旦內核中的數據準備好了,並且又再次收到了用戶進程的系統調用,那麼它馬上就將數據拷貝到了用戶內存,然後返回。


特點:用戶進程需要不斷的主動詢問內核數據好了沒有,進程輪詢調用,消耗 CPU 資源。


場景:SOCKET 設置 NON BLOCKING 屬性。支持併發量小,不用及時響應的網絡應用。


(三)I/O多路複用 - IO multiplexing


在 Linux 內核代碼迭代過程中,依次支持了 SELECT、POLL、EPOLL 三種多路複用的網絡 I/O 模型。


IO 多路複用的的基本原理是指單個線程就可以同時處理多個網絡連接。 具體實現是 SELECT、POLL、EPOLL這些函數,會不斷的輪詢所負責的所有 socket,當某個 socket 有數據到達了,就通知用戶進程。


(四)談談 IO 模型(操作系統編)


圖解:以 select 為例,當用戶進程調用了 select,那麼整個進程會被阻塞,而此時,內核會監視所有 select 負責的 socket,當任何一個 socket 中的數據準備好了,select 就會返回。這個時候用戶進程再調用 recv 操作,將數據從內核拷貝到用戶進程。


特點:IO 多路複用是阻塞在 select,poll,epoll 這樣的系統調用之上,而沒有阻塞在真正的I/O系統調用(如recv());專一進程解決多個進程 IO 的阻塞問題,性能好,Reactor模式。


場景:Java NIO,Nginx。適用高併發服務應用開發,一個進程/線程響應多個請求。


(四)信號驅動 I/O - Signal driven IO


(四)談談 IO 模型(操作系統編)


我們也可以用信號,讓內核在描述字就緒時發送 SIGIO 信號通知我們,稱這種模型為信號驅動I/O(signal-driven I/O)。


應用進程使用 sigaction 系統調用,預先告知內核,向內核註冊這樣一個函數,內核立即返回,應用進程可以繼續執行,也就是說等待數據階段應用進程是非阻塞的。內核在數據到達時嚮應用進程發送 SIGIO 信號,應用進程收到之後在信號處理程序中調用 recv() 將數據從內核複製到應用進程中。


(五)異步 I/O 模型 - Asynchronous IO


(四)談談 IO 模型(操作系統編)


圖解:應用進程執行 aio_read() 系統調用會立即返回,應用進程可以繼續執行,不會被阻塞,內核會在所有操作完成之後嚮應用進程發送信號。


特點:不阻塞,一步到位。


場景: Java 7 AIO、高性能服務器,高性能高併發。


異步 IO 模型,要求等待數據和數據拷貝操作的兩個處理階段上都不能等待(blocking),內核自行去準備好數據並將數據從內核緩衝區中複製到應用進程的緩衝區,再通知應用進程讀操作完成了,然後應用進程再去處理。


遺憾的是,Linux 的網絡 IO 模型中是不存在異步 IO 的,Linux 的網絡 IO 處理的第二階段總是阻塞等待數據 copy 完成的。


04. Linux 網絡 IO 模型比較


(四)談談 IO 模型(操作系統編)


上圖已經把 Linux 中的 IO 模型歸檔的很到位,還是稍微總結一下。前四種 IO 模型都是同步 IO 模型,主要區別在於第一階段的處理不同,第二階段的處理是相同的,都是在數據從內核複製到用戶空間時,進程阻塞於 recv() 調用。而異步 IO 模型的處理都是非阻塞的,用戶進程將整個 IO 操作交由內核去完成,內核完成後會發送通知。


分享到:


相關文章: