1. 程式人生 > >I/O復用 - 各種不同的IO模型

I/O復用 - 各種不同的IO模型

gac 結合 lec 原因 大量 三次 而不是 內核空間 http

一、概述

我們看到上面的TCP客戶同時處理兩個輸入:標準輸入和TCP套接字。我們遇到的問題就是在客戶阻塞於(標準輸入上的)fgets調用期間,服務器進程會被殺死。服務器TCP雖然正確地給客戶TCP發送一個FIN,但是既然客戶進程阻塞於從標準輸入讀入的過程,它將看不到這個ROF,知道從套接字讀時為止(可能已經過了很長時間)。這樣的進程需要一種預先告知內核的能力,使得內核一旦發現進程指定的一個或多個I/O條件就緒(也就是說輸入已準備好被讀取,或者描述符已能承接更多的輸出),它就通知進程。這個能力成為I/O復用,是由select和poll這兩個函數支持的。

I/O復用典型使用在下列網絡應用場合:

1)當客戶處理多個描述符(通常是交互式輸入和網絡套接字)時,必須使用I/O復用

2)一個客戶同時處理多個套接字是可能的,不過比較少見。在16.5節結合一個web客戶的上下文給出這種場合使用select的例子

3)如果一個TCP服務器既要處理監聽套接字,又要處理已連接套接字,一般就要使用I/O復用

4)如果一個服務器既要處理TCP,又要處理UDP,一般就要使用I/O復用。8.15節有這麽一個例子

5)如果一個服務器要處理多個服務或者鍍鉻協議(在13.5節講述的inetd守護進程),就要用I/O復用

I/O復用並非只限於網絡編程,許多重要的應用程序也需要使用這項技術

二、I/O模型

在UNIX下可用的5種I/O模型:

阻塞式I/O;

非阻塞式I/O;

I/O復用(select和poll);

信號驅動式I/O;

異步I/O

在上述所說的那樣,一個輸入操作通常包括兩個不同的階段:

1)等待數據準備好;

2)從內核向進程復制數據

對於一個套接字上的輸入操作,第一步通常涉及等待數據從網絡中到達。當所等待分組到達時,它被復制到內核總的某個緩沖區。第二步就是把數據從內核緩沖區復制到應用進程緩沖區。

(1)阻塞時I/O模型:

最流行的I/O模型,本書到目前為止的所有例子都使用該模型。默認情形下,所有套接字都是阻塞的。

技術分享圖片

使用UDP而不是TCP為例子的原因在於就UDP而言,數據準備好讀取的概念比較簡單:要麽整個數據報已經收到,要麽還沒有。對於TCP而言,諸如套接字低水位標記等額外變量開始起作用,道指這個概念復雜。

我們把recvfrom函數視為系統調用,因為我們正在區分應用進程和內核。不管如何實現,一般都會從在應用進程空間中國運行切換到在內核空間中運行,一端時間之後再切換回來。 在上圖中,進程調用recvfrom,其系統調用直到數據報到達且被復制到應用進程的緩沖區中或者發送錯誤才返回。最常見的錯誤是系統調用被信號中斷,我們說進程在從調用recvfrom開始到它返回的整段時間內是被阻塞的。recvfrom成功返回後,應用進程開始處理數據報。

(2)非阻塞式I/O模型:

進程把一個套接字設置成非阻塞是在通知內核:當所有請求的I/O操作非得把本進程投入睡眠才能完成時,不要把本進程投入睡眠,而是返回一個錯誤。將在16章中詳細介紹非阻塞是I/O

技術分享圖片

前三次調用recvfrom時沒有數據可返回,因此內核轉而立即返回一個EWOULDBLOCK錯誤。第四次調用recvfrom時已有一個數據報準備好,它被復制到應用進程緩沖區,於是recvfrom成功返回。接著處理數據。

當一個應用進程像這樣對一個非阻塞描述符循環調用recvfrom時,我們成為輪詢,應用進程持續輪詢內核,以查看某個操作是否就緒。這麽做往往耗費大量CPU時間,不過這種模型偶爾也會遇到。

(3)I/O復用模型:

有了I/O復用,我們就可以調用select或者poll,阻塞在這兩個系統調用中的某一個,而不是阻塞在真正的I/O系統調用上。下圖展示了I/O復用模型

技術分享圖片

我們阻塞與select調用,等待數據報套接字變為可讀。當select返回套接字可讀這一條件時,我們調用recvfrom把所可讀數據報復制到應用進程緩沖區。比較上面兩圖,I/O復用並不顯得有什麽優勢,事實上由於使用select需要兩個而不是單個系統調用,其優勢在於可以等待多個描述符就緒

(4)信號驅動式U/O模型:

可以用信號,讓內核在描述符就緒時發送SIGIO信號通知我們。稱為信號驅動式I/O

技術分享圖片

我們首先開啟套接字的信號驅動式I/O功能,並通過sigaction系統調用安裝一個信號處理函數。該系統調用將立即返回,我們的進程繼續工作,也就是說它沒有被阻塞。當數據報準備好讀取時,內核就為該進程產生一個SIGIO信號。我們隨後既可以在信號處理函數中調用recvfrom讀取數據報,並通知主循環數據已準備好待處理。也可以立即通知循環,讓它讀取數據報。

無論如何處理SIGIO信號,這種模型的優勢在於等待數據報到達期間進程不被阻塞。主循環可以繼續執行,只要等待來自信號處理函數的通知:既可以是數據已準備好被處理,也可以是數據報已準備好被讀取。

(5) 異步I/O模型:

告知內核啟動某個操作,並讓內核在整個操作(包括將數據從內核復制到我們自己的緩沖區)完成後通知我們。這種模型與前一節介紹的信號驅動模型的主要區別在於:信號驅動I/O是由內核通知我們如何啟動一個I/O操作,而異步I/O模型是由內核通知我們I/O操作何時完成。技術分享圖片

我們調用aio_read函數,給內核傳遞描述符、緩沖區指針。緩沖區大小和文件偏移,並告訴內核當整個操作完成時如何通知我們。該系統調用立即返回,而且在等到I/O完成期間,我們的進程不被阻塞。

前四種模型的主要區別在於第一階段,因為它們的第二階段是一樣的:在數據從內核復制到調用者的緩沖區期間,進程阻塞與recvfrom調用。相反,異步I/O模型在這兩個階段都要處理。技術分享圖片

4 同步IO和異步IO

  • 同步IO操作導致請求進程阻塞,直到IO操作完成
  • 異步IO操作不導致請求進行阻塞

從理論上講,非阻塞IO、阻塞IO、IO復用和信號驅動IO都是同步IO模型。因為這四種IO模型中,IO的讀寫操作,都是在IO事件發生之後,由應用進程來完成的。而POSIX規範所定義的異步IO模型則不同。對異步IO而言,用戶可以直接對IO執行讀寫操作,這些操作告訴內核用戶讀寫緩沖區的位置,以及IO操作完成之後內核通知應用程序的方式。異步IO的讀寫操作總是立即返回,而不論IO是否是阻塞的,因為真正的讀寫操作已經由內核接管。也就是說,同步IO模型要求用戶代碼自行執行IO操作(將數據從內核緩沖區讀入用戶緩沖區,或將數據從用戶緩沖區寫入內核緩沖區),而異步IO機制則由內核來執行IO操作(數據在內核緩沖區和用戶緩沖區之間的移動是由內核在“後臺”完成的)。你可以這樣認為,同步IO向應用程序通知的是IO就緒事件,而異步IO向應用程序通知的是IO完成事件。

I/O復用 - 各種不同的IO模型