Linux-I/O模型詳解
I/O介紹
I/O通常有內(nèi)存IO、網(wǎng)絡I/O、磁盤I/O等,但我們通常說的是網(wǎng)絡I/O以及磁盤I/O。網(wǎng)絡I/O:本質(zhì)是socket讀取
每次I/O請求,都會有兩個階段組成: 第一步:等待數(shù)據(jù),即數(shù)據(jù)從磁盤到內(nèi)核內(nèi)存;將數(shù)據(jù)從磁盤文件先加載到內(nèi)核內(nèi)存空間(緩沖區(qū)),等待數(shù)據(jù)準備完成,時間較長。第二步:復制數(shù)據(jù),即數(shù)據(jù)內(nèi)核內(nèi)存到進程內(nèi)存;將數(shù)據(jù)從內(nèi)核緩沖復制到用戶空間的進程內(nèi)存中,時間較短。
Web請求處理過程
1.客戶端發(fā)起情況到服務器網(wǎng)卡
2.服務器網(wǎng)卡接受到請求后轉(zhuǎn)交給內(nèi)核處理
3.內(nèi)核根據(jù)請求對應的套接字,將請求交給工作在用戶空間的Web服務器進程
4.Web服務器進程根據(jù)用戶請求,向內(nèi)核進行系統(tǒng)調(diào)用,申請獲取相應資源(如:客戶端獲取圖片)
5.內(nèi)核發(fā)現(xiàn)Web服務器進程請求的是一個存放在本地硬盤上的資源,因此通過驅(qū)動程序連接磁盤
6.內(nèi)核調(diào)用磁盤,獲取需要的資源
7.內(nèi)核將資源存放在自己的緩存區(qū)中,并通知Web服務器進程
8.Web服務器進程通過系統(tǒng)調(diào)用取得資源,并將其復制到進程自己的緩沖區(qū)中
9.Web服務器進程形成響應,通過系統(tǒng)調(diào)用再次發(fā)給內(nèi)核以響應請求
10.內(nèi)核將響應發(fā)送至網(wǎng)卡
11.網(wǎng)卡發(fā)送響應給用戶
通過這樣的一個復雜過程,一次請求就完成了
簡單來說就是:
用戶請求——》送達用戶空間——〉系統(tǒng)調(diào)用——》內(nèi)核空間——〉內(nèi)核到磁盤上讀取圖片資源——》返回到用戶空間——〉響應給用戶
上述簡單的說明了一下,客戶端向Web服務器請求過程,在這個過程中,有兩個I/O過程:一是客戶端請求的網(wǎng)絡I/O,二個是Web服務器請求圖片磁盤I/O。
I/O模型名詞介紹
說到I/O模型,都會牽扯到同步、異步、阻塞、非阻塞這幾個詞,以下講解這幾個詞的概念。
阻塞和非阻塞
阻塞和非阻塞指的是執(zhí)行一個操作時等操作結束再返回結果,還是馬上返回結果。
阻塞(blocking):指IO操作需要徹底完成后才返回到用戶空間,調(diào)用結果返回之前,調(diào)用者被掛起(當前線程進入非可執(zhí)行狀態(tài),在這個狀態(tài),CPU不會分配時間片,線程暫停運行)只有到到結果才進入活動狀態(tài);
阻塞例子:海底撈的服務器為你點菜,當你點完菜后,服務員把消息傳到后廚,這時你就在餐桌上等待,直到廚師把湯鍋和配菜都準備好以后送到你桌上,你才能開吃。在上菜的過程中你還不能離開,因為你離開了之后服務員上菜了卻找不到你人,所以你就是能等待,這個時候你處于阻塞等待狀態(tài),就是前面說的,你是調(diào)用者,你被掛起了,進入了非可執(zhí)行狀態(tài)。
非阻塞(nonblocking):指I/O操作被調(diào)用后立即返回給用戶一個狀態(tài)值,無需等到I/O操作徹底完成,最終的調(diào)用結果返回之前,調(diào)用者不會被掛起;
非阻塞例子:海底撈的服務器為你點菜,當你點完菜后,服務員把消息傳到后廚,過了三分鐘,你跑到后廚問,我的鍋底或者肥牛卷好了沒有?后廚說沒好,然后你去處理其它事情,然后又過了五分鐘,你又跑到后廚問,我的某個菜好了沒有,如果沒有,你還是繼續(xù)做其他事情,然后等會再問一次,這個時候就是在I/O操作的同時,你沒有被掛起,可以操作其他事情,但是如果I/O操作完成,你需要立馬接受。
同步和異步
同步/異步關注的是消息通信機制
同步(synchronous):調(diào)用者等待被調(diào)用者返回消息,才能繼續(xù)執(zhí)行。同步阻塞例子:去餐館吃飯,點了一個蓋澆飯,然后在餐桌上一直等到蓋澆飯做好,自己端到餐桌就餐。這就是典型的同步阻塞。當廚師給你做飯的時候,你需要一直在那里等著。
同步非阻塞例子:去餐館吃飯,點了一個蓋澆飯,你點完飯之后,過了幾分鐘感覺時間差不多了,就去問老板飯做好了沒有,如果好了就去端,如果沒好等一會再去問,實時同步做飯進度,依次循環(huán)去問直到飯做好,這就是同步非阻塞。 異步(asynchronous):被調(diào)用者通過狀態(tài)、通知或回調(diào)機制主動通知調(diào)用者被調(diào)用者的運行狀態(tài)。
I/O模型類型
IO模型分為以下五類
1.阻塞型:所有過程全阻塞
2.非阻塞型:如果沒有數(shù)據(jù)buffer,則立即返回EWOULDBLOCK
3.I/O復用型(select和poll):在wait和copy階段分別阻塞
4.信號驅(qū)動型I/O(SIGIO):在wait階段不阻塞,但copy階段阻塞(信號驅(qū)動I/O),即通知
5.異步I/O(AIO):完全無阻塞方式,當I/O完成時提供信號
阻塞I/O
說明:應用程序調(diào)用一個IO的recvfrom函數(shù),會導致應用程序阻塞,進入阻塞狀態(tài)后直到I/O操作結束才會返回;如果系統(tǒng)內(nèi)核數(shù)據(jù)沒有準備好,那就一直等待數(shù)據(jù)準備,因為是調(diào)用了recvfrom函數(shù)導致了應用程序阻塞,所以一直在等,做不了任何事情,內(nèi)核數(shù)據(jù)準備好之后把數(shù)據(jù)從內(nèi)核拷貝到用戶空間,拷貝結束后,I/O函數(shù)返回成功指示。注:其阻塞時在I/O操作階段
非阻塞I/O
說明:用戶線程發(fā)起IO請求時立即返回。但并未讀取到任何數(shù)據(jù),則返回字段為“EWOULDBLOCK”,用戶線程需要不斷地發(fā)起IO請求,直到數(shù)據(jù)到達后,才真正讀取到數(shù)據(jù),繼續(xù)執(zhí)行。即“輪詢”機制。整個IO請求過程中,雖然用戶線程每次發(fā)起IO請求后可以立即返回,但是為了等到數(shù)據(jù)。仍需要不斷地輪詢、重復請求、消耗了大量的CPU資源;是比較浪費CPU的方式,一般很少用這種模型,而是在其他模型中使用非阻塞IO這一特性。
I/O復用(select和poll)
說明:I/O復用模型會用到select或poll函數(shù),在I/O復用模型中,并不是阻塞到I/O操作過程中,而是阻塞到select或者poll函數(shù)中;以select為例:進程在select處阻塞,等待幾個描述符中的一個變?yōu)榭刹僮鳎绻麤]等待到就繼續(xù)阻塞在第一階段,如果等到了一個描述符變?yōu)榱丝刹僮鳎瑒t調(diào)用recvfrom函數(shù)將數(shù)據(jù)拷貝到應用緩沖區(qū)。
信號驅(qū)動I/O(SIGIO)
說明:首先,我們允許套接口進行信號驅(qū)動I/O,并安裝一個信號處理函數(shù)SIGIO,如果數(shù)據(jù)沒有準備好,則立即返回結果,進程繼續(xù)工作并不阻塞。當數(shù)據(jù)準備好時,系統(tǒng)內(nèi)核會主動發(fā)送一個SIGIO信號給應用程序,應用程序收到信號后,可以在信號處理函數(shù)中調(diào)用I/O操作函數(shù)recvfrom進行數(shù)據(jù)處理。信號驅(qū)動I/O模型的優(yōu)點是當數(shù)據(jù)報到達時,可以不阻塞,主循環(huán)可以繼續(xù)執(zhí)行,只是等待處理程序的通知,或者數(shù)據(jù)已經(jīng)準備好被處理,或者數(shù)據(jù)報已經(jīng)準備好被讀了。
異步I/O(AIO)
說明:當一個異步過程調(diào)用發(fā)出后,調(diào)用者不能立刻得到結果。實際處理這個調(diào)用的部件在完成后,通過狀態(tài)通知和回調(diào)通知來告訴調(diào)用者的輸入輸出操作。用戶可以直接對I/O執(zhí)行讀寫操作,這些操作告訴內(nèi)核用戶讀寫緩沖區(qū)的位置,以及I/O操作完成之后內(nèi)核通知應用程序的方式,就是上面講的通過狀態(tài)通知或者回調(diào)通知來告訴調(diào)用者。異步I/O的讀寫操作總是立即返回,但沒有返回結果說是否阻塞,因為異步I/O操作真正的讀寫操作已由內(nèi)核接管,內(nèi)核自己對數(shù)據(jù)處理完成后生成一個信號,然后通知用戶剛才交給自己的事件已經(jīng)處理完成。
五種I/O模型的總結及比較
中文圖示如下:
英文圖示如下:
從兩張圖中我們可以看到,越往后,阻塞越少,理論上效率也是最優(yōu)。其中五種I/O模型中,前三種屬于同步I/O,后兩者屬于異步I/O。
同步I/O
阻塞I/O 非阻塞I/O I/O復用(select和poll)
異步I/O
信號驅(qū)動I/O(SIGIO) 半異步 異步I/O(AIO) 全異步
異步I/O和信號驅(qū)動I/O的區(qū)別
信號驅(qū)動I/O模式下,內(nèi)核可以復制的時候通知給我們應用程序發(fā)送SIGIO信號。異步I/O模式下,內(nèi)核在所有的操作由內(nèi)核操作完成后才會通知我們的應用程序。