天天看點

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

推薦:​​Java網絡程式設計彙總​​

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

​​IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO​​

正文

IO模型

在《UNIX網絡變成卷1:套接字聯網API》這本書中,提到了五種I/O模型,分别為:阻塞式I/O、非阻塞式I/O、I/O複用(epoll、select都是一種I/O複用機制),資訊驅動式I/O、異步I/O,下面具體的一一介紹。

阻塞式I/O模型

阻塞,顧名思義,當程序在等待資料時,若該資料一直沒有産生,則該程序将一直等待,直到等待的資料産生為止,這個過程中程序的狀态是阻塞的。

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

如上圖所示,在linux中,使用者态程序調用​

​recvfrom系統調用​

​接收資料,目前核心中并沒有準備好資料,該使用者态程序将一直在此等待,不會進行其他的操作,待核心态準備好資料,将資料從核心态拷貝到使用者空間記憶體,然後recvfrom傳回成功的訓示,此時使用者态進行才解除阻塞的狀态,處理收到的資料。

從上述過程可以看出,使用者态接收核心态資料的時候,主要有兩個過程:核心态獲得資料–>将資料從核心态的記憶體空間中複制到使用者态程序的緩沖區中。

非阻塞式I/O模型

在非阻塞式I/O模型中,當程序等待核心的資料,而當該資料未到達的時候,程序會不斷詢問核心,直到核心準備好資料。

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

如上圖,使用者态程序調用recvfrom接收資料,目前并沒有資料封包産生,此時recvfrom傳回EWOULDBLOCK,使用者态程序會一直調用recvfrom詢問核心,待核心準備好資料的時候,之後使用者态程序不再詢問核心,待資料從核心複制到使用者空間,recvfrom成功傳回,使用者态程序開始處理資料。

需要注意的是,當資料從核心複制到使用者空間中的這一段時間中,使用者态程序是處于阻塞的狀态的。

非阻塞式I/O模型,個人覺得這個名字可能有點混淆,并不是和阻塞式模型是完全對立的,不是說程序等不到資料,就去做别的事情,恰恰程序這個時候一直在原地等待資料的到來,與阻塞式模型不同的是,非阻塞式模型相當于程序一直在敲門問“資料好了麼,快給我”,然後房門後的人說“沒有準備好,請稍後!”,這個過程是一種輪詢的狀态,而阻塞式是佛系的态度,敲了一次門,房門後的人沒有給任何回應,于是就去睡覺,啥都不做,直到房門後的人做出響應叫醒他,程序才去做下一步動作。

I/O複用模型

在ovs的使用者态源碼裡,就用到了I/O複用模型,在計算機網絡裡面,有很多關于“複用”的用法,比如多路複用,意思就是本來一條鍊路上一次隻能傳輸一個資料流,如果要實作兩個源之間多條資料流同時傳輸,那就得需要多條鍊路了,但是複用技術可以通過将一條鍊路劃分頻率,或者劃分傳輸的時間,使得一條鍊路上可以同時傳輸多條資料流。

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

套用到I/O複用模型上,可以對應到如下應用場景:如果一個程序需要等待多種不同的消息,那麼一般的做法就是開啟多條線程,每個線程接收一類消息,如果每個線程都是采用阻塞式I/O模型,那麼每個線程在消息未産生的時候就會阻塞,也就是說在多線程中使用阻塞式I/O。I/O複用就是基于上述的場景中,無需采用多線程監聽消息的方式,程序直接監聽所有的消息類型,這其中就涉及到select、poll、epoll等不同的方法。

如上圖所示,使用者态程序采用select的方法,通過select可以等待多個不同類型的消息,如果其中有一個類型的消息準備好,則select會傳回資訊,然後使用者态程序調用recvfrom接收資料。

可以将select複用機制看作是一個描述符集合的管理,程序通過向這個集合中放入不同的描述符,用來等待不同的消息産生,然後通過select統一的進行管理,讓其可以同時等待這個集合中任意一個事件的産生。

I/O複用和阻塞式I/O很相似,不同的是,I/O複用等待多類事件,阻塞式I/O隻等待一類事件,另外,在I/O複用中,會産生兩個系統調用(如上圖,select和recvfrom),而阻塞式I/O隻産生一個系統調用。那麼這就涉及到具體的性能問題,當隻存在一類事件的時候,使用阻塞式I/O模型的性能會更好,當存在多種不同類型的事件時,I/O複用的性能要好的多,因為阻塞式I/O模型隻能監聽一類事件,是以這個時候需要使用多線程進行處理。

信号驅動式I/O模型

在信号驅動式I/O模型中,與阻塞式和非阻塞式有了一個本質的差別,那就是使用者态程序不再等待核心态的資料準備好,直接可以去做别的事情。

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

如上圖所示,當需要等待資料的時候,首先使用者态會向核心發送一個信号,告訴核心我要什麼資料,然後使用者态就不管了,做别的事情去了,而當核心态中的資料準備好之後,核心立馬發給使用者态一個信号,說”資料準備好了,快來查收“,使用者态程序收到之後,立馬調用recvfrom,等待資料從核心空間複制到使用者空間,待完成之後recvfrom傳回成功訓示,使用者态程序才處理别的事情。

通過上面的圖,可以看出信号驅動式I/O模型有種異步操作的趕腳,但是在将資料從核心複制到使用者空間這段時間内使用者态程序是阻塞的。

異步I/O模型

異步I/O模型相對于信号驅動式I/O模型就更徹底了。

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

如上圖,首先使用者态程序告訴核心态需要什麼資料(上圖中通過aio_read),然後使用者态程序就不管了,做别的事情,核心等待使用者态需要的資料準備好,然後将資料複制到使用者空間,此時才告訴使用者态程序,”資料都已經準備好,請查收“,然後使用者态程序直接處理使用者空間的資料。

在複制資料到使用者空間這個時間段内,使用者态程序也是不阻塞的。

同步I/O

《UNIX網絡變成卷1:套接字聯網API》這本書中,并沒有把同步I/O作為一種單獨的I/O模型來說明,在沒有閱讀這些資料之前,我一直認為阻塞式I/O等同于同步I/O,非阻塞式I/O等同于異步I/O,可見不能單純的通過字面意思就進行判斷。

通過對上述幾種I/O模型的描述中,可以得到一個結論:阻塞式I/O、非阻塞式I/O、I/O複用模型是同步I/O模型,因為在等待資料的過程中,這三種模型中的程序都沒有去做别的事情,即便是非阻塞式的輪詢,也可以看作是一種同步。

同時書中也認為信号驅動式I/O模型是同步I/O,書中說到:POSIX将同步IO操作定義為“導緻請求程序阻塞,直到I/O操作完成”,而書中認為在信号驅動式I/O模型中等待資料的那段時間不算是真正的I/O操作(因為沒有調用I/O相關的系統調用),而資料從核心複制到使用者空間才是真正的I/O操作(這個時候調用了recvfrom系統調用)。

I/O模型比較

書中的這張圖表述的非常清楚,從等待資料和資料複制這兩個時間段,指出了不同I/O模型的差別,這裡不再贅述。

IO模型淺析-阻塞、非阻塞、IO複用、信号驅動、異步IO、同步IO

總結

從網上看了很多資料,不同的部落客對這五個模型總結的情況不同,無一例外,基本都采用一個生活場景來描述他們的不同,但是我個人覺得有些場景描述太過簡單,沒有将不同模型的差別描述完全,在這裡我也舉一個生活中的場景作為總結,當然這隻是我自己的想法,不妥之處評論區可以指出。

我們去餐廳吃飯,會經過以下幾個步驟:首先根據菜單點菜,然後等待廚房準備好,接着服務員上菜。在這個場景中,等待廚房準備菜肴等同于等待資料,服務員上菜等同于将資料從核心複制到使用者空間,你就是使用者态程序了,服務員和飯店看作是核心态的程序。

阻塞式I/O模型:隻點一個菜,然後在餐桌上開始等待,在這個過程中什麼事都不幹,等服務員把菜上到桌子上之後才開始大快朵頤。

非阻塞式I/O模型:隻點一個菜,然後開始等待,啥事都不做,等了一會兒然後就去問服務員,“我的菜好了嗎?”,沒好接着等待,過了一會兒然後又跑去問…重複這個過程,直到服務員說“親,你的菜好了,我現在給您送桌上去”,然後你坐在桌子上,等待服務員把飯菜送到你的餐桌上,才開始吃飯。

I/O複用模型:你點了很多菜,然後開始等待,某個時刻其中一個菜或者多個菜廚房裡同時好了,服務員跑過來說,“親,您的有些菜好了,要現在上桌麼?”, 你回答,現在就上,于是服務員上一個菜(服務員一次隻能上一個菜),你就吃完一個,上一個你就吃完一個。

信号驅動式I/O模型:隻點一個菜,然後給服務員留下手機,告訴他菜準備好了打個電話給你,先不要上菜,然後你就出去玩耍了,等到菜好了,服務員手機通知你,你立馬回到了餐廳,對服務員說“你現在可以上菜了”,于是你在餐桌上等待服務員把菜送上來,然後吃飯。

  1. 《UNIX網絡變成卷1:套接字聯網API》
  2. ​​網絡IO之阻塞、非阻塞、同步、異步總結​​
  3. ​​IO - 同步,異步,阻塞,非阻塞 (亡羊補牢篇)​​