基本 Linux I/O 模型的簡(jiǎn)單矩陣:
每個(gè) I/O 模型都有自己的使用模式,它們對(duì)于特定的應(yīng)用程序都有自己的優(yōu)點(diǎn)侄旬。
Unix的五種I/O模型:
1.阻塞I/O:應(yīng)用程序調(diào)用一個(gè)IO函數(shù)煌妈,導(dǎo)致應(yīng)用程序阻塞,如果數(shù)據(jù)已經(jīng)準(zhǔn)備好仇冯,從內(nèi)核拷貝到用戶空間族操,否則一直等待下去
2.非阻塞I/O:
3.I/O復(fù)用(select和poll)
4.信號(hào)驅(qū)動(dòng)I/O(SIGIO)
5.異步I/O(Posix.1的aio_系列函數(shù))
Unix的一個(gè)輸入操作一般有兩個(gè)不同的階段:
1色难、等待數(shù)據(jù)準(zhǔn)備好枷莉。
2、從內(nèi)核到進(jìn)程拷貝數(shù)據(jù)冒掌。
對(duì)于一個(gè)套接口上的輸入操作危喉,第一步一般是等待數(shù)據(jù)到達(dá)網(wǎng)絡(luò)辜限,當(dāng)分組到達(dá)時(shí)薄嫡,它被拷貝到內(nèi)核中的某個(gè)緩沖區(qū),第二步是將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到應(yīng)用緩沖區(qū)吩坝。
阻塞I/O:(blocking IO)
應(yīng)用程序調(diào)用一個(gè)IO函數(shù)钉寝,導(dǎo)致應(yīng)用程序阻塞闸迷,如果數(shù)據(jù)已經(jīng)準(zhǔn)備好腥沽,從內(nèi)核拷貝到用戶空間,否則一直等待下去师溅。
在linux中墓臭,默認(rèn)情況下所有的socket都是blocking,一個(gè)典型的讀操作流程大概是這樣:
當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用,kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)(對(duì)于網(wǎng)絡(luò)IO來說榆综,很多時(shí)候數(shù)據(jù)在一開始還沒有到達(dá)鼻疮。比如判沟,還沒有收到一個(gè)完整的UDP包。這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來)吧秕。這個(gè)過程需要等待砸彬,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個(gè)過程的斯入。而在用戶進(jìn)程這邊刻两,整個(gè)進(jìn)程會(huì)被阻塞(當(dāng)然,是進(jìn)程自己選擇的阻塞)滋迈。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了杀怠,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存厅克,然后kernel返回結(jié)果证舟,用戶進(jìn)程才解除block的狀態(tài)女责,重新運(yùn)行起來。
所以墙基,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了残制。
非阻塞I/O模型 (nonblocking IO)
我們把一個(gè)套接口設(shè)置為非阻塞就是告訴內(nèi)核掖疮,當(dāng)所請(qǐng)求的I/O操作無法完成時(shí)浊闪,不要將進(jìn)程睡眠搁宾,而是返回一個(gè)錯(cuò)誤盖腿。這樣我們的I/O操作函數(shù)將不斷的測(cè)試數(shù)據(jù)是否已經(jīng)準(zhǔn)備好,如果沒有準(zhǔn)備好堕伪,繼續(xù)測(cè)試欠雌,直到數(shù)據(jù)準(zhǔn)備好為止疙筹。在這個(gè)不斷測(cè)試的過程中而咆,會(huì)大量的占用CPU的時(shí)間暴备。
當(dāng)一個(gè)應(yīng)用程序像這樣對(duì)一個(gè)非阻塞描述符循環(huán)調(diào)用recvfrom時(shí),我們稱之為輪循(polling)浅妆。應(yīng)用進(jìn)程連續(xù)不斷地查詢內(nèi)核凌外,看看某操作是否準(zhǔn)備好,這對(duì)CPU時(shí)間是極大的浪費(fèi)摄欲,但這種模型只是偶爾才遇到胸墙,一般是在只專門提供某種功能的系統(tǒng)中才有劳秋。
當(dāng)用戶進(jìn)程發(fā)出read操作時(shí)玻淑,如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好补履,那么它并不會(huì)block用戶進(jìn)程剿另,而是立刻返回一個(gè)error雨女。從用戶進(jìn)程角度講 氛堕,它發(fā)起一個(gè)read操作后,并不需要等待括儒,而是馬上就得到了一個(gè)結(jié)果帮寻。用戶進(jìn)程判斷結(jié)果是一個(gè)error時(shí)固逗,它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送read操作掘鄙。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call饿这,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存长捧,然后返回吻贿。
所以舅列,nonblocking IO的特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問kernel數(shù)據(jù)好了沒有帐要。
總結(jié):阻塞I/O模式下,雖然不會(huì)占用大量的CPU時(shí)間奋早,一個(gè)線程只能處理一個(gè)流的I/O事件耽装。如果想要同時(shí)處理多個(gè)流掉奄,要么多進(jìn)程(fork)挥萌,要么多線程(pthread_create)引瀑,很不幸這兩種方法效率都不高榨馁。于是再來考慮非阻塞忙輪詢的I/O方式,我們發(fā)現(xiàn)我們可以同時(shí)處理多個(gè)流了(把一個(gè)流從阻塞模式切換到非阻塞模式再此不予討論):
for i in stream[]; {
if i has data
read until unavailable
}
}
我們只要不停的把所有流從頭到尾問一遍屡萤,又從頭開始死陆。這樣就可以處理多個(gè)流了措译,但這樣的做法顯然不好领虹,因?yàn)槿绻械牧鞫紱]有數(shù)據(jù)求豫,那么只會(huì)白白浪費(fèi)CPU蝠嘉。
為了避免CPU空轉(zhuǎn)是晨,可以引進(jìn)了一個(gè)代理(一開始有一位叫做select的代理,后來又有一位叫做poll的代理蚊逢,不過兩者的本質(zhì)是一樣的)烙荷。這個(gè)代理比較厲害终抽,可以同時(shí)觀察許多流的I/O事件昼伴,在空閑的時(shí)候圃郊,會(huì)把當(dāng)前線程阻塞掉持舆,當(dāng)有一個(gè)或多個(gè)流有I/O事件時(shí),就從阻塞態(tài)中醒來逸寓,于是我們的程序就會(huì)輪詢一遍所有的流(于是我們可以把“忙”字去掉了)
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
于是居兆,如果沒有I/O事件產(chǎn)生,我們的程序就會(huì)阻塞在select處竹伸。但是依然有個(gè)問題泥栖,我們從select那里僅僅知道了,有I/O事件發(fā)生了佩伤,但卻并不知道是那幾個(gè)流(可能有一個(gè)聊倔,多個(gè),甚至全部)生巡,我們只能無差別輪詢所有流见妒,找出能讀出數(shù)據(jù)孤荣,或者寫入數(shù)據(jù)的流,對(duì)他們進(jìn)行操作须揣。即使用select盐股,我們有O(n)的無差別輪詢復(fù)雜度,同時(shí)處理的流越多耻卡,沒一次無差別輪詢時(shí)間就越長(zhǎng)疯汁。
epoll可以理解為event poll,不同于忙輪詢和無差別輪詢卵酪,epoll之會(huì)把哪個(gè)流發(fā)生了怎樣的I/O事件通知我們幌蚊。此時(shí)我們對(duì)這些流的操作都是有意義的。(復(fù)雜度降低到了O(1))
I/O多路復(fù)用模型( IO multiplexing)
IO multiplexing就是我們說的select溃卡,poll溢豆,epoll,有些地方也稱這種IO方式為event driven IO瘸羡。select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO漩仙。它的基本原理就是select,poll犹赖,epoll這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket队他,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程峻村。select麸折,poll,epoll這個(gè)function也會(huì)使進(jìn)程阻塞雀哨,但是和阻塞I/O所不同的的磕谅,這兩個(gè)函數(shù)可以同時(shí)阻塞多個(gè)I/O操作私爷。而且可以同時(shí)對(duì)多個(gè)讀操作,多個(gè)寫操作的I/O函數(shù)進(jìn)行檢測(cè)膊夹,直到有數(shù)據(jù)可讀或可寫時(shí)衬浑,才真正調(diào)用I/O操作函數(shù)。
當(dāng)用戶進(jìn)程調(diào)用了select放刨,那么整個(gè)進(jìn)程會(huì)被block工秩,而同時(shí),kernel會(huì)“監(jiān)視”所有select負(fù)責(zé)的socket进统,當(dāng)任何一個(gè)socket中的數(shù)據(jù)準(zhǔn)備好了助币,select就會(huì)返回。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用read操作螟碎,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程眉菱。
所以,I/O 多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符(見《I/O模型之二:Linux IO模式及 select掉分、poll俭缓、epoll詳解》),而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài)酥郭,select()函數(shù)就可以返回华坦。
這個(gè)圖和blocking IO的圖其實(shí)并沒有太大的不同,事實(shí)上不从,還更差一些惜姐。因?yàn)檫@里需要使用兩個(gè)system call (select 和 recvfrom),而blocking IO只調(diào)用了一個(gè)system call (recvfrom)椿息。但是歹袁,用select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè)connection。
所以撵颊,如果處理的連接數(shù)不是很高的話宇攻,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大倡勇。select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快逞刷,而是在于能處理更多的連接。)
在IO multiplexing Model中妻熊,實(shí)際中夸浅,對(duì)于每一個(gè)socket,一般都設(shè)置成為non-blocking扔役,但是帆喇,如上圖所示,整個(gè)用戶的process其實(shí)是一直被block的亿胸。只不過process是被select這個(gè)函數(shù)block坯钦,而不是被socket IO給block预皇。
信號(hào)驅(qū)動(dòng)I/O模型
我們也可以用信號(hào),讓內(nèi)核在描述符就緒時(shí)發(fā)送SIGIO信號(hào)通知我們婉刀。通過sigaction系統(tǒng)調(diào)用安裝一個(gè)信號(hào)處理函數(shù)吟温。該系統(tǒng)調(diào)用將立即返回,我們的進(jìn)程繼續(xù)工作突颊,也就是說它沒有被阻塞鲁豪。當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好讀取時(shí),內(nèi)核就為該進(jìn)程產(chǎn)生一個(gè)SIGIO信號(hào)律秃。我們隨后既可以在信號(hào)處理函數(shù)中調(diào)用recvfrom讀取數(shù)據(jù)報(bào)爬橡,并通知主循環(huán)數(shù)據(jù)已經(jīng)準(zhǔn)備好待處理。
優(yōu)勢(shì):等待數(shù)據(jù)報(bào)到達(dá)期間進(jìn)程不被阻塞棒动。主循環(huán)可以繼續(xù)執(zhí)行糙申,只要等待來自信號(hào)處理函數(shù)的通知:既可以是數(shù)據(jù)已準(zhǔn)備好被處理,也可以是數(shù)據(jù)報(bào)已準(zhǔn)備好被讀取船惨。
異步I/O模型(asynchronous IO)
linux下的asynchronous IO其實(shí)用得很少郭宝。
告知內(nèi)核啟動(dòng)某個(gè)操作,并讓內(nèi)核在整個(gè)操作(包括將內(nèi)核復(fù)制到我們自己的緩沖區(qū))完成后通知我們掷漱。
與信號(hào)驅(qū)動(dòng)模型的主要區(qū)別在于:信號(hào)驅(qū)動(dòng)式I/O是由內(nèi)核通知我們何時(shí)可以啟動(dòng)一個(gè)I/O操作,而異步模型是由內(nèi)核通知我們I/O操作何時(shí)完成榄檬。
調(diào)用aio_read(Posix異步I/O函數(shù)以aio_或lio_開頭)函數(shù)卜范,給內(nèi)核傳遞描述字、緩沖區(qū)指針鹿榜、緩沖區(qū)大泻Q(與read相同的3個(gè)參數(shù))、文件偏移以及通知的方式舱殿,然后系統(tǒng)立即返回奥裸。我們的進(jìn)程不阻塞于等待I/0操作的完成。當(dāng)內(nèi)核將數(shù)據(jù)拷貝到緩沖區(qū)后沪袭,再通知應(yīng)用程序湾宙。
用戶進(jìn)程發(fā)起read操作之后,立刻就可以開始去做其它的事冈绊。而另一方面侠鳄,從kernel的角度,當(dāng)它受到一個(gè)asynchronous read之后死宣,首先它會(huì)立刻返回伟恶,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生任何block。然后毅该,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成博秫,然后將數(shù)據(jù)拷貝到用戶內(nèi)存潦牛,當(dāng)這一切都完成之后,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal挡育,告訴它read操作完成了巴碗。
總結(jié)
各種I/O模型的對(duì)比:
blocking和non-blocking的區(qū)別
調(diào)用blocking IO會(huì)一直block住對(duì)應(yīng)的進(jìn)程直到操作完成,而non-blocking IO在kernel還準(zhǔn)備數(shù)據(jù)的情況下會(huì)立刻返回静盅。
synchronous IO和asynchronous IO的區(qū)別
在說明synchronous IO和asynchronous IO的區(qū)別之前良价,需要先給出兩者的定義。POSIX的定義是這樣子的:
- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
- An asynchronous I/O operation does not cause the requesting process to be blocked;
兩者的區(qū)別就在于synchronous IO做”IO operation”的時(shí)候會(huì)將process阻塞蒿叠。按照這個(gè)定義明垢,之前所述的blocking IO,non-blocking IO市咽,IO multiplexing都屬于synchronous IO痊银。
有人會(huì)說,non-blocking IO并沒有被block啊施绎。這里有個(gè)非乘莞铮“狡猾”的地方,定義中所指的”IO operation”是指真實(shí)的IO操作谷醉,就是例子中的recvfrom這個(gè)system call致稀。non-blocking IO在執(zhí)行recvfrom這個(gè)system call的時(shí)候,如果kernel的數(shù)據(jù)沒有準(zhǔn)備好俱尼,這時(shí)候不會(huì)block進(jìn)程抖单。但是,當(dāng)kernel中數(shù)據(jù)準(zhǔn)備好的時(shí)候遇八,recvfrom會(huì)將數(shù)據(jù)從kernel拷貝到用戶內(nèi)存中矛绘,這個(gè)時(shí)候進(jìn)程是被block了,在這段時(shí)間內(nèi)刃永,進(jìn)程是被block的货矮。
而asynchronous IO則不一樣,當(dāng)進(jìn)程發(fā)起IO 操作之后斯够,就直接返回再也不理睬了囚玫,直到kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說IO完成雳刺。在這整個(gè)過程中劫灶,進(jìn)程完全沒有被block。
各個(gè)IO Model的比較如圖所示:
通過上面的圖片掖桦,可以發(fā)現(xiàn)non-blocking IO和asynchronous IO的區(qū)別還是很明顯的本昏。在non-blocking IO中,雖然進(jìn)程大部分時(shí)間都不會(huì)被block枪汪,但是它仍然要求進(jìn)程去主動(dòng)的check涌穆,并且當(dāng)數(shù)據(jù)準(zhǔn)備完成以后怔昨,也需要進(jìn)程主動(dòng)的再次調(diào)用recvfrom來將數(shù)據(jù)拷貝到用戶內(nèi)存。而asynchronous IO則完全不同宿稀。它就像是用戶進(jìn)程將整個(gè)IO操作交給了他人(kernel)完成趁舀,然后他人做完后發(fā)信號(hào)通知。在此期間祝沸,用戶進(jìn)程不需要去檢查IO操作的狀態(tài)矮烹,也不需要主動(dòng)的去拷貝數(shù)據(jù)。
參考《unix網(wǎng)絡(luò)編程》
參考http://blog.csdn.net/blueboy2000/article/details/4485874
參考http://blog.csdn.net/suxinpingtao51/article/details/46314097