1. 程式人生 > >IO模型淺析-阻塞、非阻塞、IO複用、訊號驅動、非同步、同步IO

IO模型淺析-阻塞、非阻塞、IO複用、訊號驅動、非同步、同步IO

segmentfault 對應博文頁面 https://segmentfault.com/a/1190000016359495 最近看到OVS使用者態的程式碼,在接收核心態資訊的時候,使用了Epoll多路複用機制,對其十分不解,於是從網上找了一些資料,學習了一下《UNIX網路變成卷1:套接字聯網API》這本書對應的章節,網上雖然關於該主題的博文很多,並且講解的很詳細,但是在這裡還是做一個學習筆記,記錄一下自己的想法。

IO模型

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

阻塞式I/O模型

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

如上圖所示,在linux中,使用者態程序呼叫recvfrom系統呼叫接收資料,當前核心中並沒有準備好資料,該使用者態程序將一直在此等待,不會進行其他的操作,待核心態準備好資料,將資料從核心態拷貝到使用者空間記憶體,然後recvfrom返回成功的指示,此時使用者態進行才解除阻塞的狀態,處理收到的資料。

從上述過程可以看出,使用者態接收核心態資料的時候,主要有兩個過程:核心態獲得資料-->將資料從核心態的記憶體空間中複製到使用者態程序的緩衝區中

非阻塞式I/O模型

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

如上圖,使用者態程序呼叫recvfrom接收資料,當前並沒有資料報文產生,此時recvfrom返回EWOULDBLOCK,使用者態程序會一直呼叫recvfrom詢問核心,待核心準備好資料的時候,recvfrom才返回結果,之後使用者態程序不再詢問核心,待資料從核心複製到使用者空間,使用者態程序開始處理資料。

需要注意的是,recvfrom不是等待資料從核心態複製到使用者態之後才返回資訊,而是當資料準備好就返回,所以之後當資料從核心複製到使用者空間中的這一段時間中,使用者態程序是處於阻塞的狀態的。

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

I/O複用模型

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

套用到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模型中,與阻塞式和非阻塞式有了一個本質的區別,那就是使用者態程序不再等待核心態的資料準備好,直接可以去做別的事情。

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

通過上面的圖,可以看出訊號驅動式I/O模型有種非同步操作的趕腳,但是在將資料從核心複製到使用者空間這段時間內使用者態程序是阻塞的

非同步I/O模型

非同步I/O模型相對於訊號驅動式I/O模型就更徹底了。

如上圖,首先使用者態程序告訴核心態需要什麼資料(上圖中通過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模型的區別,這裡不再贅述。

總結

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

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

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

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

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

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

非同步I/O模型:只點一個菜,然後給服務員留下手機,告訴他菜準備好了先上菜,菜上桌了打電話給你,然後你就出去玩耍了,等到菜上桌了,服務員手機通知你,你立馬回到了餐桌,開始吃飯。

參考資料