理解Linux裏面的IO模型

前言

在談到IO模型之前,我們先來了解下Liunx裏面的幾個概念:

User space(用戶空間)和 Kernel space(內核空間)。Linux裏面這麼設計的目的主要是爲了安全,即使用戶空間崩潰了,內核也不受影響。所以在Linux世界,進程不能直接訪問硬件設備,當進程需要訪問硬件設備(比如讀取磁盤文件,接收網絡數據等等)時,必須由用戶態模式切換至內核態模式,通過系統調用訪問硬件設備。

此外還要理解阻塞,非阻塞,同步,異步這幾個概念,這裏不再詳細介紹,我之前的文章中非常詳細的介紹過。

說下目前Liunx的5種IO模型:

blocking IO - 阻塞IO

nonblocking IO - 非阻塞IO

IO multiplexing - IO多路複用

signal-driven IO - 信號驅動式IO(異步阻塞)

asynchronous IO - 異步IO

其中前面三種都可以歸納爲同步IO,最後一種爲異步IO,在linux裏面一次io操作會涉及兩個系統對象:用戶進程,內核空間。其中用戶進程發起io的讀寫操作後,數據會先被拷貝到操作系統內核的緩衝區中,然後纔會從操作系統內核的緩衝區拷貝到應用程序的地址空間。所以說當一個read操作發生後,它會經歷兩個階段:

第一階段:等待數據準備 (Waiting for the data to be ready)。.

第二階段:將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)。

對於socket流而言:

第一步:通常涉及等待網絡上的數據分組到達,然後被複制到內核的某個緩衝區.

第二步:把數據從內核緩衝區複製到應用進程緩衝區。

同步阻塞IO

同步阻塞 IO 模型是最常用的一個模型,也是最簡單的模型。在linux中,默認情況下所有的socket都是blocking。它符合人們最常見的思考邏輯。

在這個IO模型中,用戶空間的應用程序執行一個系統調用(recvform),這會導致應用程序阻塞,什麼也不幹,直到數據準備好,並且將數據從內核複製到用戶進程,最後進程再處理數據,在等待數據到處理數據的兩個階段,整個進程都被阻塞。不能處理別的網絡IO。直到kernel返回結果,用戶進程才解除block的狀態,重新運行起來。

可以看出來,這兩個階段都block住了。

同步非阻塞IO

在這種模式下,用戶進程發出請求後,並不會阻塞,內核會裏面返回一個error狀態,然後用戶進程需要輪詢不斷的check狀態,在輪詢期間可以乾點別的事,最終直到內核把數據準備好了,然後通知用戶進程,把數據從內核空間拷貝到用戶所在的進程進行處理。

IO多路複用 multiplexing

IO多路複用,指的是由轉門的一個進程負責輪詢檢查IO操作的狀態,而不用每個用戶進程都得自己負責輪詢,這樣就大大節省了線程資源。那麼這就是所謂的 “IO 多路複用”。UNIX/Linux 下的 select、poll、epoll 就是幹這個的(epoll 比 poll、select 效率高,做的事情是一樣的)

。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。

當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。 多路複用的特點是通過一種機制一個進程能同時等待IO文件描述符,內核監視這些文件描述符(套接字描述符),其中的任意一個進入讀就緒狀態,select, poll,epoll函數就可以返回

信號驅動IO

信號驅動式I/O:首先我們允許Socket進行信號驅動IO,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據

異步非阻塞IO

相對於同步IO,異步IO不是順序執行。用戶進程進行aio_read系統調用之後,無論內核數據是否準備好,都會直接返回給用戶進程,然後用戶態進程可以去做別的事情。等到socket數據準備好了,內核直接複製數據給進程,然後從內核向進程發送通知。IO兩個階段,進程都是非阻塞的。

Linux提供了AIO庫函數實現異步,但是用的很少。目前有很多開源的異步IO庫,例如libevent、libev、libuv。異步過程如下圖所示:

總結

各個IO模型的比較圖如下:

通過上面的圖片,可以發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,但是它仍然要求進程去主動的check,並且當數據準備完成以後,也需要進程主動的再次調用recvfrom來將數據拷貝到用戶內存。而asynchronous IO則完全不同。它就像是用戶進程將整個IO操作交給了他人(kernel)完成,然後他人做完後發信號通知。在此期間,用戶進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章