IO模型
輸入操作包括兩個(gè)階段
- 內(nèi)核等待數(shù)據(jù)到達(dá)
- 應(yīng)用進(jìn)程將數(shù)據(jù)從內(nèi)核拷貝數(shù)據(jù)
Unix有5種IO模型
- 阻塞IO
- 非阻塞IO
- I/O復(fù)用(select/poll/epoll)
- 信號(hào)驅(qū)動(dòng) I/O(SIGIO)
- 異步IO
阻塞IO(BIO)
應(yīng)用進(jìn)程被阻塞數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)返回蚪战。
調(diào)用阻塞IO的應(yīng)用進(jìn)程,并不影響其他進(jìn)程執(zhí)行贼急,因此這種模型的CPU利用率比較高令杈。
結(jié)合下面這張圖來深入看下阻塞IO經(jīng)歷的過程。
當(dāng)用戶進(jìn)程recvfrom這個(gè)系統(tǒng)調(diào)用時(shí)候,kernel就開始IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)。對(duì)于network io來說,很多時(shí)候數(shù)據(jù)還沒有到達(dá)(比如沒有收到完整的UDP包,kernel要等到收到足夠的數(shù)據(jù)。而在用戶進(jìn)程這邊锭部,整個(gè)進(jìn)程都會(huì)被阻塞盆耽。當(dāng)kernel把數(shù)據(jù)準(zhǔn)備好循榆,它會(huì)將數(shù)據(jù)從kernel拷貝到用戶內(nèi)存中泽篮,然后kernal返回結(jié)果亏拉,用戶進(jìn)程解除阻塞狀態(tài)锐极。
BIO在執(zhí)行IO的兩個(gè)階段(等待數(shù)據(jù)和拷貝數(shù)據(jù))都被block了肋层。
這里可以看下進(jìn)程的幾個(gè)狀態(tài)轉(zhuǎn)換,于是就和操作系統(tǒng)里面的知識(shí)聯(lián)系起來了檬嘀。
其次鸳兽,系統(tǒng)調(diào)用作為中斷的一種(soft intrrrupt) 注意read(),recvfrom() 等區(qū)別。
read()函數(shù)原型如下戚嗅,只能向已經(jīng)建立好連接的socket讀取數(shù)據(jù)
/**
* @fd 文件描述符
* @buf 讀出數(shù)據(jù)緩沖區(qū)
* @count 請(qǐng)求讀出字節(jié)數(shù)
* 成功返回讀取的字節(jié)數(shù),0表示讀到文件尾胀糜,失敗返回-1
*/
ssize_t read(int fd, void *buf, size_t count);
recvfrom()函數(shù)原型如下.如果用在已經(jīng)建立連接的socket上颅拦,需要忽略其地址和地址長(zhǎng)度參數(shù),即地址指針設(shè)置為NULL,地址長(zhǎng)度設(shè)置為0.并且flag參數(shù)設(shè)置為0,就和read()的功能一樣锥债。manual 手冊(cè)解釋的很清楚陡蝇,下面是摘錄
The only difference between recv() and read(2) is the presence of flags. With a zero flags argument, recv() is generally equivalent to read(2) (but see NOTES). Also, the following call recv(sockfd, buf, len, flags);is equivalent to recvfrom(sockfd, buf, len, flags, NULL, NULL); All three calls return the length of the message on successful completion.
/**
* @sockfd socket文件描述符
* @buf 讀取數(shù)據(jù)的緩沖區(qū)
* @len 請(qǐng)求讀取數(shù)據(jù)長(zhǎng)度
* @flags 標(biāo)志位
* @src_addr 源地址
* @addrlen 地址長(zhǎng)度
* 成功返回讀取字節(jié)數(shù) 失敗返回-1
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
非阻塞IO
應(yīng)用進(jìn)程執(zhí)行系統(tǒng)調(diào)用之后,內(nèi)核返回一個(gè)錯(cuò)誤碼(errorno,全局變量)哮肚。應(yīng)用進(jìn)程可以繼續(xù)執(zhí)行登夫,但是需要不斷重復(fù)執(zhí)行系統(tǒng)調(diào)用來確定I/O 是否完成,這種方式稱為輪詢(polling)
在這種工作模式下允趟,由于應(yīng)用進(jìn)程需要不停的進(jìn)行系統(tǒng)調(diào)用恼策,加大了系統(tǒng)的開銷,導(dǎo)致CPU的利用效率不高潮剪。為什么系統(tǒng)調(diào)用開銷大涣楷?
下圖清楚的說明了非阻塞調(diào)用在第一階段不斷的詢問內(nèi)核,第二階段阻塞等待數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程空間
IO復(fù)用
復(fù)用顧名思義就是一個(gè)應(yīng)用進(jìn)程同時(shí)監(jiān)聽多個(gè)套接字連接抗碰。
具體來看通過使用 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。
如果一個(gè) Web 服務(wù)器沒有 I/O 復(fù)用两芳,那么每一個(gè)客戶端發(fā)起Socket連接都需要?jiǎng)?chuàng)建一個(gè)線程去處理摔寨。如果同時(shí)有幾萬個(gè)連接,那么就需要?jiǎng)?chuàng)建相同數(shù)量的線程怖辆。相比于多進(jìn)程和多線程技術(shù)是复,I/O 復(fù)用不需要進(jìn)程線程創(chuàng)建和切換的開銷,系統(tǒng)開銷更小竖螃。
由下圖可知第一階段select()阻塞等待數(shù)據(jù)到來,第二階段recvfrom()阻塞等待數(shù)據(jù)拷貝到進(jìn)程空間緩沖區(qū)淑廊。這與阻塞IO模型的區(qū)別主要在于:阻塞IO調(diào)用recvfrom() 阻塞階段從等數(shù)據(jù)到數(shù)據(jù)拷貝到用戶緩沖區(qū),需要注意其中的區(qū)別。
信號(hào)驅(qū)動(dòng)IO
本質(zhì)是進(jìn)程之間的通信斑鼻,在內(nèi)核接接受到數(shù)據(jù)之后通過信號(hào)來通知應(yīng)用進(jìn)程蒋纬。
可以借機(jī)復(fù)習(xí)下CSAPP里面的信號(hào)部分猎荠。
應(yīng)用進(jìn)程注冊(cè)sigalaction信號(hào),內(nèi)核立刻返回坚弱,應(yīng)用進(jìn)程在等待數(shù)據(jù)階段非阻塞蜀备。當(dāng)數(shù)據(jù)到達(dá)時(shí),內(nèi)核向應(yīng)用程序發(fā)送SIGIO信號(hào)荒叶,應(yīng)用進(jìn)程在收到該信號(hào)之后碾阁,在信號(hào)處理函數(shù)中調(diào)用recvfrom() 將數(shù)據(jù)從內(nèi)核復(fù)制到應(yīng)用進(jìn)程中。相比較非阻塞IO,信號(hào)驅(qū)動(dòng)下的CPU利用率高些楣。
異步IO
應(yīng)用進(jìn)程執(zhí)行aio_read()
系統(tǒng)調(diào)用立即返回脂凶,應(yīng)用程序在整個(gè)IO操作不會(huì)阻塞,內(nèi)核在完成所有操作之后向應(yīng)用進(jìn)程發(fā)送信號(hào)愁茁。
AIO和信號(hào)驅(qū)動(dòng)IO區(qū)別:
AIO的信號(hào)通知應(yīng)用IO完成蚕钦,信號(hào)驅(qū)動(dòng)IO通知進(jìn)程開始IO.
小結(jié)
- 同步IO:將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū),應(yīng)用進(jìn)程會(huì)阻塞
- 異步IO:上述階段不會(huì)阻塞
下面這張圖很好總結(jié)呢五大IO模型