Unix 和 Java IO 模型簡析
Unix 和 Java IO 模型簡析
從計算機結構的視角來看的話, I/O 描述了計算機系統與外部裝置之間通訊的過程。
為了保證作業系統的穩定性和安全性,記憶體的地址空間劃分為 使用者空間(User space) 和 核心空間(Kernel space ) 。
像我們平常執行的應用程式都是執行在使用者空間,只有核心空間才能進行系統態級別的資源有關的操作,比如檔案管理、程序通訊、記憶體管理等等。並且,使用者空間的程式不能直接訪問核心空間。
因此,使用者程序想要執行 IO 操作的話,必須通過 系統呼叫 來間接訪問核心空間。我們在平常開發過程中接觸最多的就是 磁碟 IO(讀寫檔案) 和 網路 IO(網路請求和響應)
也就是說,我們的應用程式實際上只是發起了 IO 操作的呼叫而已,具體 IO 的執行是由作業系統的核心來完成的。
當應用程式發起 I/O 呼叫後,會經歷兩個步驟:
- 核心等待 I/O 裝置準備好資料
- 核心將資料從核心空間拷貝到使用者空間
Unix IO 模型簡介
同步阻塞式 I/O
應用程序被阻塞,直到資料複製到應用程序緩衝區中才返回。
應該注意到,在阻塞的過程中,其它程式還可以執行,因此阻塞不意味著整個作業系統都被阻塞。因為其他程式還可以執行,因此不消耗 CPU 時間,這種模型的執行效率會比較高。
同步非阻塞式 I/O
應用程序執行系統呼叫之後,核心返回一個錯誤碼。應用程序可以繼續執行,但是需要不斷的執行系統呼叫來獲知 I/O 是否完成,這種方式稱為輪詢(polling)
由於 CPU 要處理更多的系統呼叫,因此這種模型是比較低效的。幾乎跟阻塞使用者程序無異。
I/O 多路複用
使用 select 或者 poll 等待資料,並且可以等待多個套接字中的任何一個變為可讀,這一過程會被阻塞,當某一個套接字可讀時返回。之後再使用 recvfrom 把資料從核心複製到程序中。
它可以讓單個程序具有處理多個 I/O 事件的能力。又被稱為 Event Driven I/O,即事件驅動 I/O。
如果一個 Web 伺服器沒有 I/O 複用,那麼每一個 Socket 連線都需要建立一個執行緒去處理。如果同時有幾萬個連線,那麼就需要建立相同數量的執行緒。並且相比於多程序和多執行緒技術,I/O 複用不需要程序執行緒建立和切換的開銷,系統開銷更小。
訊號驅動 I/O
應用程序使用 sigaction 系統呼叫,核心立即返回,應用程序可以繼續執行,也就是說等待資料階段應用程序是非阻塞的。核心在資料到達時嚮應用程序傳送 SIGIO 訊號,應用程序收到之後在訊號處理程式中呼叫 recvfrom 將資料從核心複製到應用程序中。
相比於非阻塞式 I/O 的輪詢方式,訊號驅動 I/O 的 CPU 利用率更高。
非同步 I/O
進行 aio_read 系統呼叫會立即返回,應用程序繼續執行,不會被阻塞,核心會在所有操作完成之後嚮應用程序傳送訊號。
非同步 I/O 與訊號驅動 I/O 的區別在於,非同步 I/O 的訊號是通知應用程序 I/O 完成,而訊號驅動 I/O 的訊號是通知應用程序可以開始 I/O。
Java IO 模型
BIO (Blocking I/O)
同步阻塞 IO 模型中,應用程式發起 read 呼叫後,會一直阻塞,直到核心把資料拷貝到使用者空間。
NIO (Non-blocking/New I/O)
Java 中的 NIO 可以看作是 I/O 多路複用模型。
IO 多路複用模型中,執行緒首先發起 select 呼叫,詢問核心資料是否準備就緒,等核心把資料準備好了,使用者執行緒再發起 read 呼叫。read 呼叫的過程(資料從核心空間 -> 使用者空間)還是阻塞的。
目前支援 IO 多路複用的系統呼叫,有 select,epoll 等等。select 系統呼叫,目前幾乎在所有的作業系統上都有支援。
- select 呼叫 :核心提供的系統呼叫,它支援一次查詢多個系統呼叫的可用狀態。幾乎所有的作業系統都支援。
- epoll 呼叫 :linux 2.6 核心,屬於 select 呼叫的增強版本,優化了 IO 的執行效率。
IO 多路複用模型,通過減少無效的系統呼叫,減少了對 CPU 資源的消耗。
AIO (Asynchronous I/O)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進版 NIO 2,它是非同步 IO 模型。
非同步 IO 是基於事件和回撥機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裡,當後臺處理完成,作業系統會通知相應的執行緒進行後續的操作。
目前來說 AIO 的應用還不是很廣泛。Netty 之前也嘗試使用過 AIO,不過又放棄了。這是因為,Netty 使用了 AIO 之後,在 Linux 系統上的效能並沒有多少提升。
IO多路複用模型
Java NIO 就是按照 IO 多路複用模型來實現的。
應用場景
很容易產生一種錯覺認為只要用 epoll 就可以了,select 和 poll 都已經過時了,其實它們都有各自的使用場景。
- select
- select 的 timeout 引數精度為 1ns,而 poll 和 epoll 為 1ms,因此 select 更加適用於實時要求更高的場景,比如核反應堆的控制。
- select 可移植性更好,幾乎被所有主流平臺所支援
- poll
- poll 沒有最大描述符數量的限制,如果平臺支援並且對實時性要求不高,應該使用 poll 而不是 select。
- 需要同時監控小於 1000 個描述符,就沒有必要使用 epoll,因為這個應用場景下並不能體現 epoll 的優勢。
- 需要監控的描述符狀態變化多,而且都是非常短暫的,也沒有必要使用 epoll。因為 epoll 中的所有描述符都儲存在核心中,造成每次需要對描述符的狀態改變都需要通過
epoll_ctl()
進行系統呼叫,頻繁系統呼叫降低效率。並且 epoll 的描述符儲存在核心,不容易除錯
- epoll
- 只需要執行在 Linux 平臺上,並且有非常大量的描述符需要同時輪詢,而且這些連線最好是長連線。
參考資料: