摘自:
我們都知道unix(like)世界裡,一切皆檔案,而檔案是什麼呢?檔案就是一串二進位製流而已,不管socket,還是fifo、管道、終端,對我們來說,一切都是檔案,一切都是流。在資訊 交換的過程中,我們都是對這些流進行資料的收發操作,簡稱為i/o操作(input and output),往流中讀出資料,系統呼叫read,寫入資料,系統呼叫write。不過話說回來了 ,計算機裡有這麼多的流,我怎麼知道要操作哪個流呢?對,就是檔案描述符,即通常所說的fd,乙個fd就是乙個整數,所以,對這個整數的操作,就是對這個檔案(流)的操作。我們建立乙個socket,通過系統呼叫會返回乙個檔案描述符,那麼剩下對socket的操作就會轉化為對這個描述符的操作。不能不說這又是一種分層和抽象的思想。
什麼是程式的阻塞呢?想象這種情形,比如你等快遞,但快遞一直沒來,你會怎麼做?有兩種方式:
很顯然,你無法忍受第二種方式,不僅耽擱自己的時間,也會讓快遞很想打你。
而在計算機世界,這兩種情形就對應阻塞和非阻塞忙輪詢。
先說說阻塞,因為乙個執行緒只能處理乙個套接字的i/o事件,如果想同時處理多個,可以利用非阻塞忙輪詢的方式,偽**如下:
[cpp] view plain
copy
while true
} 我們只要把所有流從頭到尾查詢一遍,就可以處理多個流了,但這樣做很不好,因為如果所有的流都沒有i/o事件,白白浪費cpu時間片。正如有一位科學家所說,計算機所有的問題都可以增加乙個中間層來解決,同樣,為了避免這裡cpu的空轉,我們不讓這個執行緒親自去檢查流中是否有事件,而是引進了乙個**(一開始是select,後來是poll),這個**很牛,它可以同時觀察許多流的i/o事件,如果沒有事件,**就阻塞,執行緒就不會挨個挨個去輪詢了,偽**如下:
[cpp] view plain
copy
while true
} 但是依然有個問題,我們從select那裡僅僅知道了,有i/o事件發生了,卻並不知道是哪那幾個流(可能有乙個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出資料,或者寫入資料的流,對他們進行操作。所以select具有o(n)的無差別輪詢複雜度,同時處理的流越多,無差別輪詢時間就越長。
epoll可以理解為event poll,不同於忙輪詢和無差別輪詢,epoll會把哪個流發生了怎樣的i/o事件通知我們。所以我們說epoll實際上是事件驅動(每個事件關聯上fd)的,此時我們對這些流的操作都是有意義的。(複雜度降低到了o(1))偽**如下:
[cpp] view plain
copy
while true
} 可以看到,select和epoll最大的區別就是:select只是告訴你一定數目的流有事件了,至於哪個流有事件,還得你乙個乙個地去輪詢,而epoll會把發生的事件告訴你,通過發生的事件,就自然而然定位到哪個流了。不能不說epoll跟select相比,是質的飛躍,我覺得這也是一種犧牲空間,換取時間的思想,畢竟現在硬體越來越便宜了。
好了,我們講了這麼多,再來總結一下,到底什麼是i/o多路復用。
先講一下i/o模型:
首先,輸入操作一般包含兩個步驟:
等待資料準備好(waiting for data to be ready)。對於乙個套介面上的操作,這一步驟關係到資料從網路到達,並將其複製到核心的某個緩衝區。
將資料從核心緩衝區複製到程序緩衝區(copying the data from the kernel to the process)。
其次了解一下常用的3種i/o模型:
最廣泛的模型是阻塞i/o模型,預設情況下,所有套介面都是阻塞的。 程序呼叫recvfrom系統呼叫,整個過程是阻塞的,直到資料複製到程序緩衝區時才返回(當然,系統呼叫被中斷也會返回)。
當我們把乙個套介面設定為非阻塞時,就是在告訴核心,當請求的i/o操作無法完成時,不要將程序睡眠,而是返回乙個錯誤。當資料沒有準備好時,核心立即返回ewouldblock錯誤,第四次呼叫系統呼叫時,資料已經存在,這時將資料複製到程序緩衝區中。這其中有乙個操作時輪詢(polling)。
此模型用到select和poll函式,這兩個函式也會使程序阻塞,select先阻塞,有活動套接字才返回,但是和阻塞i/o不同的是,這兩個函式可以同時阻塞多個i/o操作,而且可以同時對多個讀操作,多個寫操作的i/o函式進行檢測,直到有資料可讀或可寫(就是監聽多個socket)。select被呼叫後,程序會被阻塞,核心監視所有select負責的socket,當有任何乙個socket的資料準備好了,select就會返回套接字可讀,我們就可以呼叫recvfrom處理資料。
正因為阻塞i/o只能阻塞乙個i/o操作,而i/o復用模型能夠阻塞多個i/o操作,所以才叫做多路復用。
4、訊號驅動i/o模型(signal driven i/o, sigio)
首先我們允許套介面進行訊號驅動i/o,並安裝乙個訊號處理函式,程序繼續執行並不阻塞。當資料準備好時,程序會收到乙個sigio訊號,可以在訊號處理函式中呼叫i/o操作函式處理資料。當資料報準備好讀取時,核心就為該程序產生乙個sigio訊號。我們隨後既可以在訊號處理函式中呼叫recvfrom讀取資料報,並通知主迴圈資料已準備好待處理,也可以立即通知主迴圈,讓它來讀取資料報。無論如何處理sigio訊號,這種模型的優勢在於等待資料報到達(第一階段)期間,程序可以繼續執行,不被阻塞。免去了select的阻塞與輪詢,當有活躍套接字時,由註冊的handler處理。
5、非同步i/o模型(aio, asynchronous i/o)
程序發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到乙個asynchronous read之後,首先它會立刻返回,所以不會對使用者程序產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者程序傳送乙個signal,告訴它read操作完成了。
這個模型工作機制是:告訴核心啟動某個操作,並讓核心在整個操作(包括第二階段,即將資料從核心拷貝到程序緩衝區中)完成後通知我們。
這種模型和前一種模型區別在於:訊號驅動i/o是由核心通知我們何時可以啟動乙個i/o操作,而非同步i/o模型是由核心通知我們i/o操作何時完成。
I O多路復用
一 五種i o模型 1 阻塞i o模型 最流行的i o模型是阻塞i o模型,預設情形下,所有套介面都是阻塞的。我們以資料報套介面為例來講解此模型 我們使用udp而不是tcp作為例子的原因在於就udp而言,資料準備好讀取的概念比較簡單 要麼整個資料報已經收到,要麼還沒有。然而對於tcp來說,諸如套介面...
i o多路復用
最常見的i o多路復用就是 select poll epoll了,下面說說他們的一些特點和區別吧。select 可讀 可寫 異常三種檔案描述符集的申明和初始化。fd set readfds,writefds,exceptionfds fd zero readfds fd zero writefds ...
I O多路復用
我們都知道unix like 世界裡,一切皆檔案,而檔案是什麼呢?檔案就是一串二進位製流而已,不管socket,還是fifo 管道 終端,對我們來說,一切都是檔案,一切都是流。在資訊 交換的過程中,我們都是對這些流進行資料的收發操作,簡稱為i o操作 input and output 往流中讀出資料...