1. 程式人生 > >IO多路復用

IO多路復用

實的 同時 不能 標準 multipl 空間 應用 就會 eight

同步IO和異步IO,阻塞IO和非阻塞IO分別是什麽,到底有什麽區別?不同的人在不同的上下文下給出的答案是不同的。

所以先限定一下本文的上下文:本文討論的背景是Linux環境下的network IO。

一、概念介紹

下面主要介紹5個概念:用戶空間和內核空間,進程切換,進程的阻塞,文件描述符,緩存I/O。

1.用戶空間和內核空間

  現在操作系統都是采用虛擬存儲器,那麽對32位操作系統而言,它的尋址空間虛擬存儲空間)為4G(2的32次方)。(2^32 Bit = 4 GB)

  操作系統的核心是內核,內核獨立於普通的應用程序:

  • 可以訪問受保護的內存空間;
  • 也有訪問底層硬件設備的所有權限。

  為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。

技術分享

2.進程切換

為了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復以前掛起的某個進程的執行。這種行為被稱為進程切換。因此可以說,任何進程都是在操作系統內核的支持下運行的,是與內核緊密相關的。

從一個進程的運行轉到另一個進程上運行,這個過程中經過下面這些變化:

  1. 保存處理機上下文,包括程序計數器和其他寄存器。
  2. 更新PCB信息。PCB(Processing Control Block)
  3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
  4. 選擇另一個進程執行,並更新其PCB.
  5. 更新內存管理的數據結構。
  6. 恢復處理機上下文。

總而言之,進程切換很耗資源:CPU上下文切換的開銷等。

註意:

  進程控制塊PCB,是操作系統核心中的一種數據結構,主要表示進程狀態。

  它的作用是使一個在多道程序環境下不能獨立運行的程序(含數據),成為一個能獨立運行的基本單位或與其它進程並發執行的進程。

  或者說,OS是根據PCB來對並發執行的進程進行控制和管理的。

  PCB通常是系統內存占用區中的一個連續存區,它存放著操作系統用於描述進程情況及控制進程運行所需的全部信息。

3.進程的阻塞

正在執行的進程,由於期待的某些事件未發生,如請求系統資源失敗、等待某種操作的完成、新數據尚未到達或無新工作做等,則由系統自動執行阻塞原語Block,使自己由運行狀態變為阻塞狀態。

可見,進程的阻塞是進程自身的一種主動行為,也因此只有處於運行狀態的進程(獲得CPU),才可能將其轉為阻塞狀態。

當進程進入阻塞狀態,是不占用CPU資源的。

4.文件描述符

文件描述符File Descriptor是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。

文件描述符在形式上是一個非負整數。

實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。

當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。

在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用於UNIX,Linux這樣的操作系統。

5. 緩存 I/O

緩存I/O又被稱作標準I/O,大多數文件系統的默認I/O操作都是緩存I/O.

在Linux的緩存I/O機制中,操作系統會將I/O的數據緩存在文件系統的頁緩存(page cache)中,也就是說,數據會被先拷貝到操作系統內核的緩沖區中,然後才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。

緩存I/O的缺點:

  數據在傳輸過程中需要在應用程序地址空間和內核進行多次數據拷貝操作,這些數據拷貝操作的CPU和內存開銷很大。

例如:在socketserver中有個數據緩沖區,如果不做粘包處理會出現粘包現象,粘包出現的原因在於,數據是先被拷貝到操作系統內核的緩沖區中,系統默認為了降低數據從內核態到用戶態的拷貝操作,會等內核的緩沖區滿了才會向拷貝到應用程序的地址空間。

二、IO模式

對於一次IO訪問,以read舉例,數據會被先拷貝到操作系統內核的緩沖區中,然後才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。

所以說,當一個read操作發生時,它會經歷兩個階段:

  1.等待數據準備 Waiting for the data to be ready

  2.將數據從內核拷貝到進程中 Copying the data from the kernel to the process

由於一次IO訪問都要經歷這兩個階段,Linux系統產生了下面五種網絡模式的方案:

  阻塞IO,非阻塞IO,I/O多路復用,信號驅動I/O,異步I/O

  blocking IO, nonblocking IO,IO Multiplexing, signal driven IO, asynchronous IO

其中信號驅動I/O在實際中不常用。

1.阻塞IO (blocking IO)

  在Linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:

技術分享

當用戶進程調用了recvfrom這個系統調用,kernal就開始了IO的第一個階段:準備數據(對於網絡IO來說,很多時候數據在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來。)這個過程需要等待,也就是說,數據被拷貝到操作系統內核的緩沖區中是需要一個過程的。

在用戶進程這邊,整個進程會被阻塞(然後,是進程自己選擇的阻塞)。

當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,然後kernel返回結果,用戶進行才解除block的狀態,重新運作起來。

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

在單線程的阻塞IO模式下,是無法同時處理多個socket的,因為單線程下IO阻塞了。

所以,單線程的阻塞IO模式下,是無法處理多路IO的。

2.非阻塞IO(nonblocking IO)

Linux下,可以通過設置socket使其變為non-blocking.

當對一個non-blocking socket執行read操作時,流程如下:

技術分享

當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那麽它並不會block用戶進程,而是立刻返回一個error.

從用戶進程角度講,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,於是它可以再次發送read操作。

一旦kernel中的數據準備好了,並且又再次收到了用戶進程的system call,那麽它馬上就將數據拷貝到了用戶內存,然後返回。

非阻塞IO的特點是:用戶進程需要不斷地主動詢問kernel數據是否準備好。

在單線程的非阻塞IO模式下,輪詢的收N個recvfrom,所以可以操作多個socket,可以實現用戶態的多並發。

非阻塞IO只是數據準備的過程不用阻塞,但是IO操作的第二個階段copy data from kernel to user還是會阻塞。如果拷貝的數據量大, 那麽在IO操作的第二階段耗時還是會長。

3.I/O 多路復用(IO multiplexing)

IO multiplexing就是我們說的select,poll,epoll,有些地方也稱這種IO方式為event driven IO(事件驅動)。

select/epoll的好處就在於單個進程process就可以同時處理多個網絡連接的IO。

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

技術分享

當用戶進程調用了select(select可以接受N個socket的連接句柄),那麽整個進程會被block,而同時,kernel會“監視”所有selcet負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。

IO 多路復用的特點是:通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。

IO多路復用的圖和阻塞IO的圖的差別大不,事實上,還更差一些。因為這裏需要使用兩個system call(select 和 recvfrom),而阻塞IO只調用了一個system call(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。

所以,如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading+blocking IO的web server性能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。

在IO多路復用模型中,實際中,對於每一個socket,一般都設置稱為non-blocking,但是,如上圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

4.異步I/O (asynchronous IO

Linux下的異步I/O用的很少。

流程:

技術分享

用戶進程發起read操作之後,立刻就可以開始去做其它的事。

而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對用戶進程產生任何block。

然後,kernel會等待數據準備完成,然後將數據拷貝到用戶內存,當這一切都完成之後,kernel會給用戶進程發送一個signal,告訴它read操作完成了。

各種網絡模式解決方案的區別:

阻塞IO和非阻塞IO的區別:

調用blocking IO會一直block對應的進程直到IO操作完成;

調用nonblocking IO在kernel還準備數據的情況下會立刻返回(如果kernel中數據未準備好,會立刻返回一個error,用戶進程會再次發送read操作;一旦kernel中數據準備好了,並再次收到了用戶進程的system call,則執行copy data from kernel to user)。用戶進程需要不斷地主動詢問kernel數據是否準備好。

異步IO和同步IO的區別:

定義:

同步IO會導致請求的進程阻塞,直到IO操作完成;

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

異步IO不會請求的進程被阻塞。

An asynchronous I/O operation does not cause the requesting process to be blocked.

分類:

阻塞IO,非阻塞IO,IO 多路復用 都屬於同步IO。

異步IO 屬於異步IO。

註意:

上面同步IO和異步IO的定義中所指的‘IO operation‘是指真實的IO操作,就是例子中的recvfrom這個system call。非阻塞IO在執行recvfrom這個system call的時候,如果kernel的數據沒有準備好,這時候不會block進程。但是當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。所以非阻塞IO還是屬於同步IO。

而異步IO則不同,當進程發起IO操作之後,就直接返回再也不理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程中,進程完全沒有被block。

各個IO Model的比較如圖所示:

技術分享

通過上面的圖片,可以發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,但是它仍然要求進程去主動的check,並且當數據準備完成以後,也需要進程主動的再次調用recvfrom來將數據拷貝到用戶內存。

而asynchronous IO則完全不同。它就像是用戶進程將整個IO操作交給了kernel完成,然後由kernel完成了數據準備和拷貝之後,kernel發送信號告知進程。在此期間,用戶進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝數據。

IO多路復用