Java IO:網路IO模型
網路IO模型有5種,分別為:阻塞式IO,非阻塞式IO,IO複用,訊號驅動式IO 和 非同步IO。
一. 作業系統如何處理IO
Linux 會把所有的外部裝置都看成一個檔案來操作,對外部裝置的操作可以看成是對檔案的操作。我們對一個檔案的讀寫,都會通過核心提供的系統呼叫,核心會給我們返回一個 File Descriptor,這個描述符是一個數字,指向核心的一個結構體,我們應用程式對檔案的讀寫就是對描述符指向的結構體的讀寫。
系統呼叫是如何完成IO操作?Linux 會把記憶體分為 核心區和使用者區。Linux 的核心區會幫我們管理所有的硬體資源,並且會提供系統呼叫,我們應用程式的讀操作,就會通過系統呼叫 read 發起一個讀操作,這個時候,核心就會建立一個檔案描述符,通過驅動向硬體傳送讀指令,並且把讀的資料放在描述符指向的結構體的緩衝區中
Linux 系統呼叫的 read,是一個阻塞函式。這個我們應用程式在發起read系統呼叫的時候,就必須要阻塞,程序掛起,等待檔案描述符的讀就緒。
從上面我們可以知道,應用程式的一個 read系統呼叫,需要經過:
- 硬體讀取檔案資料到檔案描述符指向的結構體的緩衝區。
- 結構體的緩衝區的資料 傳輸到 使用者區。
二. 阻塞式 IO
在進行網路IO的時候,程序發起一個 recvform 系統呼叫,然後程序就會被阻塞,什麼也不幹,等待上述 所說的 1 和 2步驟完成(也就是硬體讀取內容到核心的結構體緩衝區,結構體緩衝區的內容到使用者區),最後程序再被處理。大致如下圖1:
圖1
二. 非阻塞式 IO
在上述的 阻塞式 IO中,可以看到,程序在發起了 recvform 系統呼叫的時候,就會阻塞,那麼非阻塞式 IO 和 阻塞式 IO 的一個區別就在這裡,它並不會阻塞,而是馬上返回一個錯誤碼,程序在得到錯誤碼之後,可以乾點別的事情,然後再重複上述的步驟,也就是又發起一個 recvform 系統呼叫。這個過程就成為輪詢。
需要注意的是,上述的輪詢只是針對作業系統處理IO的第一步,也就是 硬體讀取資料到核心的結構體緩衝區,不針對第二步,也就是說,核心的結構體緩衝區的資料複製到使用者區還是需要經過阻塞。如下圖2:
圖2
三. IO複用
由上面的非阻塞式IO可以看出,輪詢佔了很大一部分過程,這個過程會消耗很多CPU時間。這個輪詢是由使用者區發起的,但是,這個輪詢如果是由核心區發起的,那麼效率就有提升。IO複用提供了兩個 系統呼叫 :select 和 poll
圖3
Ps:實際上 select/poll 是落後的,因為 select 的控制代碼數有限,為1024 個,也就是說,同次檢測 1025 個控制代碼死 是不可能。另外,核心的select是採用輪詢的方法,select檢測的控制代碼數越大就會越耗時,這些都是問題。那麼epoll就能夠解決這些問題,epoll 實際上也是 slect/poll 的增強版。
四. 訊號驅動式IO
也就是說,資料未準備就緒的時候,那麼就進行等待,但是這個等待不會進行輪詢,也不會阻塞。而是在某一時刻如果資料準備好了,那麼核心會通知程序啟動IO操作,將資料從核心區複製到使用者區。如下圖4 所示:
圖4
五. 非同步IO
非同步 IO 是指 相當於 訊號式驅動 IO 的一個升級版本。非同步 IO 是等 核心完成整個操作之後再通過應用程式,這整個過程包括了 硬體讀取資料到 核心結構體的緩衝區中,以及緩衝區的資料複製到 使用者區中,這整個過程程序都不會阻塞。如下圖5:
圖5
總結:以上就是 作業系統如何處理IO,以及常用的 5種 網路 IO模型。包括(阻塞式IO(java的傳統IO),非阻塞式IO(NIO),多路複用IO,訊號驅動IO,非同步IO)。
參考: