Java NIO学习系列五:I/O模型
前面總結了很多IO、NIO相關的基礎知識點,還總結了IO和NIO之間的區別及各自適用場景,本文會從另一個視角來學習一下IO,即IO模型。什么是IO模型?對于不同人、在不同場景下給出的答案是不同的,所以先限定一下本文的上下文:Linux環境下的network IO。
本文會從如下幾個方面展開:
一些基礎概念
I/O模型
總結
?
1.?一些基礎概念
IO模型這個概念屬于比較基礎的底層概念,在此之前容我再先簡單介紹一些涉及到的更底層的概念,幫助對I/O模型的理解:
1.1 用戶空間與內核空間
現在操作系統都是采用虛擬存儲器,對于32位操作系統而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。操作系統的核心是內核,獨立于普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。
1.2 文件描述符
對于內核而言,所有打開文件都由文件描述符引用。文件描述符是一個非負整數。當打開一個現存文件或創建一個新文件時,內核向進程返回一個文件描述符。當讀、寫一個文件時,用open或create返回的文件描述符標識該文件,將其作為參數傳送給read或write。文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統。
1.3 緩存 I/O
緩存I/O又被稱作標準I/O,大多數文件系統的默認I/O操作都屬于緩存I/O。在Linux的緩存I/O機制中,操作系統會將I/O的數據緩存在文件系統的頁緩存(page cache)中,也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。
因為數據在傳輸過程中需要在應用程序地址空間和內核進行多次數據拷貝操作,所以這些數據拷貝操作所帶來的CPU以及內存開銷是非常大的,這也是緩存I/O所帶來的缺點。
?
2.?I/O模型
上面有提到,對于一次IO訪問(比如read),數據會先被復制到操作系統內核緩沖區中,然后再從操作系統內核的緩沖區復制到應用程序的地址空間。也就是說,當一個IO操作發生時,會經歷兩個階段:
這是因為存在上面兩個階段,linux系統產生了下面5種I/O模型:
- 阻塞I/O(blocking IO)
- 非阻塞I/O(nonblocking IO)
- I/O多路復用(IO multiplexing)
- 信號驅動I/O(signal driven IO)
- 異步I/O(asynchronous IO)
下面我們來一一介紹(本文暫介紹除信號驅動I/O外其余4種IO模型)。
2.1 阻塞I/O(blocking IO)
在linux中,默認情況下socket都是阻塞式的,一個典型的讀操作流程大概是這樣的:
當用戶進程調用recvfrom這個系統調用時,kernel就開始了IO的第一個階段:數準備階段(對于網絡IO來說,很多時候數據在一開始還沒有到達。比如,還沒有收到一個完整的TCP包。這個時候kernel就要等待直到所有數據到達),這個過程相當于將數據被復制到操作系統內核的緩沖區,是需要一個過程,需要等待的。而在用戶進程這邊,整個進程會被阻塞(當然,是進程自己選擇的阻塞)。當kernel一直等到數據準備好了,它就會將數據從kernel中復制到用戶內存,然后kernel返回結果,用戶進程才會解除block的狀態,重新運行起來。
Blocking IO最大的特點就是在IO執行的兩個階段都被會阻塞。
2.2 非阻塞I/O(nonblocking IO)?
可以將設置socket設置為non-blocking模式,對于此時的讀操作,流程大概是這個樣子的:
當用戶進程發起recvfrom這個系統調用時,如果kernel中的數據還沒有準備好,那么它并不會block用戶進程,而是立刻返回一個error。從用戶進程角度講 ,它發起一個recvfrom調用,馬上就得到了一個結果,不管有沒有數據。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,于是它可以再次發起recvfrom操作。一旦kernel中的數據準備好了,并且又再次收到了用戶進程的system call,那么它接下來就將數據復制到用戶內存,然后返回。
Nonblocking IO的特點是用戶進程需要不斷的主動詢問kernel數據好了沒有,這個過程不阻塞,但是在IO的第二個階段還是需要等待內核將數據復制到用戶進程的,也就是這部分還是會阻塞的。
2.3 I/O多路復用(IO multiplexing)
IO multiplexing就是我們說的IO多路復用,有些地方也稱這種IO方式為event driven IO。主要是通過select/epoll來實現單個process同時處理多個網絡連接的IO,它的基本原理是依賴select,poll,epoll這些function來不斷的輪詢負責的所有socket,當某個socket有數據到達了,就通知用戶進程。
當用戶進程調用了select時整個進程會被block,同時kernel會“監視”所有select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel復制到用戶進程。
所以,I/O多路復用的特點是通過一種機制使得一個進程能同時等待多個文件描述符(至于為什么是文件描述符,因為對于kernel而言,所有打開文件都由文件描述符引用,而一次IO連接也相當于打開文件),而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。
IO多路復用是建立在Nonblocking IO之上的,即通過單個線程來“監視”多個IO連接,誰準備就緒了就處理誰,處理的過程和Nonblocking的過程是一樣的。
2.4 異步I/O(asynchronous IO)
Asynchronous IO不同于上面三種模型,先看一下它的流程:
用戶進程發起read操作之后,立刻就可以開始去做其它的事情了。另一方面,從kernel的角度,當它收到一個asynchronous read調用之后,首先它會立刻返回,所以不會對用戶進程產生任何block。然后,kernel會等待數據準備完成,并且數據準備好之后再將數據復制到用戶內存,當這一切都完成之后,kernel會給用戶進程發送一個signal,告訴它read操作完成了。
其實,從這里可以看出異步I/O最大的特點就是,將前面提到的兩步IO操作全部異步化了,整個過程完全不阻塞,而Nonblocking IO在復制數據到用戶進程這一步其實還是阻塞的,只是數據準備階段不阻塞而已。
?
?3.?總結
本文總結了linux網絡IO中常見的五種IO模型,雖說不是Java IO,但是Java IO、NIO中也是遵循同樣的IO模型,關于這一點,后面會專門寫一篇文章來闡述。
五種IO模型分別為:阻塞I/O(blocking IO)、非阻塞I/O(nonblocking IO)、I/O多路復用(IO multiplexing)、信號驅動I/O(signal driven IO)、異步I/O(asynchronous IO)。在這五種模型的基礎上,有兩個概念不得不提,阻塞和非阻塞、同步和異步,這兩個概念容易搞混,:
3.1 阻塞和非阻塞
阻塞很好理解,就是進程或者線程停在那里等待某個狀態,什么都不干。但是什么情況稱為阻塞,什么情況稱為非阻塞呢?在IO模型中的定義是取決于前面提到的第一階段:數據準備階段,也就是說,在這個階段會導致進程或線程阻塞的IO就成為阻塞式IO,反之就是非阻塞式IO。所以Nonblocking IO在第一階段是可以做別的事情的,但是在第二階段任然是阻塞的,這點需要注意。
3.2 同步和異步
在說明同步IO模型和異步IO模型的區別之前,需要先給出兩者的定義。POSIX的定義是這樣子的:
- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
- An asynchronous I/O operation does not cause the requesting process to be blocked;
兩者的區別就在于執行"IO Operation"的時候是否會導致進程或線程阻塞,這個"IO Operation"是指前面提到的第二階段:將數據從kernel復制到用戶進程中。按照這個定義,前面說的Blocking IO、Non-blocking IO、IO multiplexing就都屬于synchronous IO,只有異步IO才屬于Asynchronous IO,因為只有它在整個IO過程中都不會導致阻塞。
最后再附上一張五種IO模型比較圖,以幫助理解:
?
參考文獻
Linux IO模式及 select、poll、epoll詳解
?
總結
以上是生活随笔為你收集整理的Java NIO学习系列五:I/O模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java NIO学习系列四:NIO和IO
- 下一篇: Java NIO学习系列六:Java中的