Linux network I/O


1 緩存 I/O (Buffered I/O)介紹

對於傳統的操作系統來說,普通的 I/O 操作一般會被內核緩存,這種 I/O 被稱作緩存 I/O。緩存 I/O 又被稱作標准 I/O,大多數文件系統的默認 I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操作系統會將 I/O 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間

(1) 對於讀操作來說,當應用程序嘗試讀取某塊數據的時候,如果這塊數據已經存放在了頁緩存中,那么這塊數據就可以立即返回給應用程序,而不需要經過實際的物理讀盤操作。當然,如果數據在應用程序讀取之前並未被存放在頁緩存中,那么就需要先將數據從磁盤讀到頁緩存中去。一個典型的讀取磁盤中數據的流程圖如下所示: 
這里寫圖片描述

(2) 對於寫操作來說,應用程序也會將數據先寫到頁緩存中去,數據是否被立即寫到磁盤上去取決於應用程序所采用的寫操作機制:如果用戶采用的是同步寫機制( synchronous writes ), 那么數據會立即被寫回到磁盤上,應用程序會一直等到數據被寫完為止;如果用戶采用的是延遲寫機制( deferred writes ),那么應用程序就完全不需要等到數據全部被寫回到磁盤,數據只要被寫到頁緩存中去就可以了。在延遲寫機制的情況下,操作系統會定期地將放在頁緩存中的數據刷到磁盤上。與異步寫機制( asynchronous writes )不同的是,延遲寫機制在數據完全寫到磁盤上的時候不會通知應用程序,而異步寫機制在數據完全寫到磁盤上的時候是會返回給應用程序的。所以延遲寫機制本身是存在數據丟失的風險的,而異步寫機制則不會有這方面的擔心。

緩存 I/O 優點:

  • 緩存 I/O 使用了操作系統內核緩沖區,在一定程度上分離了應用程序空間和實際的物理設備。緩存 I/O 可以減少讀盤的次數,從而提高性能。

緩存I/O缺點:

  • 在緩存 I/O 機制中,DMA 方式可以將數據直接從磁盤讀到頁緩存中,或者將數據從頁緩存直接寫回到磁盤上,而不能直接在應用程序地址空間和磁盤之間進行數據傳輸,這樣的話,數據在傳輸過程中需要在應用程序地址空間和頁緩存之間進行多次數據拷貝操作,這些數據拷貝操作所帶來的 CPU 以及內存開銷是非常大的。對於某些特殊的應用程序來說,避開操作系統內核緩沖區而直接在應用程序地址空間和磁盤之間傳輸數據會比使用操作系統內核緩沖區獲取更好的性能。

2 Linux環境下的network I/O

網絡IO的本質就是socket的讀取,socket在linux系統被抽象為流,IO可以理解為對流的操作。文章開始的時候也提到了,對於一次IO訪問(以read為例),數據會先被拷貝到操作系統內核的緩沖區,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間中。

所以說,當一個read操作發生時,它會經歷兩個階段: 
第一個階段:等待數據准備。 
第二個階段:將數據從內核拷貝到進程中 
對於socket流而言: 
第一步:通常涉及等待網絡上的數據分組到達,然后復制到內核的某個緩沖區。 
第二步:把數據從內核緩沖區復制到應用進程緩沖區。

Linux環境下的五種IO Modle: blocking IO, nonblocking IO, IO multiplexing, signal driven IO, asynchronous IO. 其中前四種比較常見。

(1) blocking IO 
在阻塞IO模型中,從調用系統函數獲取數據開始到得到數據,當前的進程或者線程始終是處於阻塞狀態的,也就是什么都不干,直到等完數據准備好和將數據搬遷到用戶空間為止:

blocking IO

 

1) 用戶首先發出系統調用函數希望獲取數據; 
2) 系統調用會進入內核檢查是否有數據准備完畢,如果沒有就一直等待; 
3) 當數據准備完畢的時候會將數據拷貝到用戶空間; 
4) 拷貝完畢返回一個獲取數據成功的返回值來告訴用戶可以進行數據的處理了;在此期間,用戶進程或者線程一直是處於阻塞狀態的,無論是等待數據還是進行數據的拷貝;

(2) nonblocking IO 
和阻塞式的IO模型不同,當發出了系統調用的時候,如果這時候數據還沒有准備好,進程或線程並不會進入阻塞模式一直等待,而是會反復輪詢“數據好沒…數據好沒…數據好沒…”,這是比較耗CPU資源的,而當數據准備好之后會和阻塞IO模型一樣進行數據的拷貝:

nonblocking IO

1) 首先用戶發出系統調用,去向內核申請獲取想要的數據; 
2) 內核檢查發現數據還沒准備好,就會返回一個錯誤值; 
3) 用戶接收到錯誤值並不甘心,就會反復反復詢問內核是否有數據准備好; 
4) 內核一直檢查直到有數據准備完畢,進行數據的搬遷,這時用戶會進入阻塞等待狀態等待數據拷貝完畢; 
5) 數據提取到用戶空間之后會返回一個成功狀態通知用戶數據完畢,這時用戶就可以進行數據的處理了;

(3) IO multiplexing 
既然是復用,說明一次性可以管理或處理多個IO,主要是靠select函數或者poll(epoll)函數來完成;這些函數同樣會阻塞進程,但是和阻塞式IO不同的是IO復用模型可以一次性阻塞多個IO操作。同時,kernel會“監視”所有select負責的socket,當任何一個socket中的數據准備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。

IO multiplexing

 

1) 用戶首先調用select或者poll函數對多個IO接口操作進行檢測,同時會使進程或者線程阻塞; 
2) 當有至少一個IO接口響應的時候,系統就會通知內核調用相應的函數來獲取數據; 
3) 這時內核將數據拷貝遷移至內核空間,進程或者線程仍然處於阻塞狀態; 
4) 數據就緒,返回一個成功值告訴用戶可以處理數據了;

(4) signal driven IO 
用戶首先注冊一個處理IO信號的信號處理函數,當數據還沒准備好的時候進程或者線程並不阻塞,當數據准備好的時候用戶進程或者線程會收到一個信號SIGIO,這時候就會調用信號處理函數,在信號處理函數中調用IO函數操作數據,完成之后通知用戶:

signal driven IO

 

1) 用戶程序中事先注冊好一個對於SIGIO的信號處理函數; 
2) 當數據准備完畢的時候會向用戶進程或者線程發送一個SIGIO信號,這時信號就會被捕捉; 
3) 捕捉信號之后就會執行用戶自定義的一個信號處理函數,並且在函數中調用系統函數去獲取數據; 
4) 獲取數據同樣會將數據進行拷貝到用戶空間,這時進程或者線程仍然會被阻塞; 
5) 當數據准備完畢同樣會通知用戶,之后就可以進行數據的處理了;

(5) asynchronous IO 
對於異步的IO模型來說,數據的等待和搬遷都不由當前的進程或者線程來處理,調用相應系統函數之后就會直接返回繼續執行,因此當前用戶程並不會被阻塞,當數據已經在用戶空間准備就緒之后會以狀態、通知或者回調來告訴用戶可以進行數據的處理了:

asynchronous IO

1) 用戶程序調用aio_read函數,告訴內核描述字、緩沖區指針、緩沖區大小、文件偏移以及通知的方式,之后便立即返回; 
2) 這時內核相應的數據操作組件會進行數據的等待和搬遷,期間用戶程序並不受影響繼續執行; 
3) 當數據都已經在用戶空間准備就緒之后就會通過在函數中預留的通知方式來通知用戶程序處理數據;

總結: 
從上面的分析中不難發現,在對於數據的獲取過程中都是進行了兩個主要的部分:數據的等待和數據的搬遷;除此相同點之外,下面就總結一下各種IO模型的區別: 
這里寫圖片描述

從上面的比較可以發現:前四種IO模型也就是阻塞IO、非阻塞IO、IO復用和信號驅動IO模型都是同步的,只有最后一種是異步的異步IO模型;默認情況下所創建出來的socket都是以阻塞的形式,比如網絡通信中的recvfrom和sendto,或者read和write等函數都是以阻塞的方式來實現的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM