本篇文章是作為學(xué)習(xí)NIO的前置文章的闯袒,在學(xué)習(xí)NIO之前必須得了解一下IO模型旬牲。
對(duì)于一次IO訪問(以read舉例),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中搁吓,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間原茅。所以說,當(dāng)一個(gè)read操作發(fā)生時(shí)堕仔,它會(huì)經(jīng)歷兩個(gè)階段:
- 第一階段:等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)擂橘。
- 第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)。
對(duì)于socket流而言:
第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達(dá)摩骨,然后被復(fù)制到內(nèi)核的某個(gè)緩沖區(qū)通贞。
第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)。
網(wǎng)絡(luò)應(yīng)用需要處理的無非就是兩大類問題恼五,網(wǎng)絡(luò)IO昌罩,數(shù)據(jù)計(jì)算。相對(duì)于后者灾馒,網(wǎng)絡(luò)IO的延遲茎用,給應(yīng)用帶來的性能瓶頸大于后者。網(wǎng)絡(luò)IO的模型大致有如下幾種:
- 阻塞IO(bloking IO)
- 非阻塞IO(non-blocking IO)
- 多路復(fù)用IO(multiplexing IO)
- 信號(hào)驅(qū)動(dòng)式IO(signal-driven IO)
- 異步IO(asynchronous IO)
阻塞I/O模型
默認(rèn)情況下睬罗,socket都是阻塞類型的轨功,一個(gè)典型的讀流程應(yīng)該是這樣的:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
當(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模型
當(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ù)好了沒有。
I/O多路復(fù)用
最常用的的I/O事件通知機(jī)制就是I/O復(fù)用厕怜。Linux 環(huán)境中使用select/poll/epoll_wait 實(shí)現(xiàn)I/O復(fù)用衩匣,I/O復(fù)用接口本身是阻塞的,在應(yīng)用程序中通過I/O復(fù)用接口向內(nèi)核注冊(cè)fd所關(guān)注的事件酣倾,當(dāng)關(guān)注事件觸發(fā)時(shí)舵揭,通過I/O復(fù)用接口的返回值通知到應(yīng)用程序谤专。I/O復(fù)用接口可以同時(shí)監(jiān)聽多個(gè)I/O事件以提高事件處理效率躁锡。
使用 select 或者 poll 等待數(shù)據(jù),并且可以等待多個(gè)套接字中的任何一個(gè)變?yōu)榭勺x置侍,這一過程會(huì)被阻塞映之,當(dāng)某一個(gè)套接字可讀時(shí)返回。之后再使用 recvfrom 把數(shù)據(jù)從內(nèi)核復(fù)制到進(jìn)程中蜡坊。
它可以讓單個(gè)進(jìn)程具有處理多個(gè) I/O 事件的能力杠输。又被稱為 Event Driven I/O,即事件驅(qū)動(dòng) I/O秕衙。
SIGIO 信號(hào)驅(qū)動(dòng) I/O
應(yīng)用進(jìn)程使用 sigaction 系統(tǒng)調(diào)用蠢甲,內(nèi)核立即返回,應(yīng)用進(jìn)程可以繼續(xù)執(zhí)行据忘,也就是說等待數(shù)據(jù)階段應(yīng)用進(jìn)程是非阻塞的鹦牛。內(nèi)核在數(shù)據(jù)到達(dá)時(shí)向應(yīng)用進(jìn)程發(fā)送 SIGIO 信號(hào)搞糕,應(yīng)用進(jìn)程收到之后在信號(hào)處理程序中調(diào)用 recvfrom 將數(shù)據(jù)從內(nèi)核復(fù)制到應(yīng)用進(jìn)程中。
相比于非阻塞式 I/O 的輪詢方式曼追,信號(hào)驅(qū)動(dòng) I/O 的 CPU 利用率更高窍仰。
AIO 異步IO(asynchronous IO AIO)
進(jìn)行 aio_read 系統(tǒng)調(diào)用會(huì)立即返回,應(yīng)用進(jìn)程繼續(xù)執(zhí)行礼殊,不會(huì)被阻塞驹吮,內(nèi)核會(huì)在所有操作完成之后向應(yīng)用進(jìn)程發(fā)送信號(hào)。異步 I/O 與信號(hào)驅(qū)動(dòng) I/O 的區(qū)別在于晶伦,異步 I/O 的信號(hào)是通知應(yīng)用進(jìn)程 I/O 完成碟狞,而信號(hào)驅(qū)動(dòng) I/O 的信號(hào)是通知應(yīng)用進(jìn)程可以開始 I/O。