1. 程式人生 > >IO模型詳解及應用

IO模型詳解及應用

邊緣 會有 通知機制 子進程 sele lex ons 中一 消息通知

如何閱讀這篇文章順序

1.1:了解同步異步和阻塞非阻塞

1.11: 同步異步

1.12:阻塞非阻塞

1.2:了解一次read操作需要的步驟

1.3:五種模型

1.1:I/O模型中的同步異步,阻塞非阻塞:

1.11:同步和異步:synchronousasyncronous

關註的是消息通知機制

同步概念:調用發出之後不會立即返回,但一旦返回,則返回則是最終結果:

異步:調用發出之後,被調用方立即返回消息,但返回的並非最終結果,被調用者通過狀態、通知機制等來通知調用者,或通過回調函數來處理結果。

同步異步:關註的是調用者如何等待結果

1.22:阻塞和非阻塞:

blocknonblock

阻塞:調用結果返回之前,調用者會被掛起:調用者只有在得到返回結果之後才能繼續:

非阻塞:調用者在結果返回之前:不會被掛起,即調用不會阻塞調用者

例子:比如你去飯館吃面需要等著上面才能吃到。

阻塞:當你在點面的時候前面排隊的人很多,老板和你說一句客官稍等,這時候你沒辦法做其他事情這就是阻塞。

非阻塞:比如你和老板關系不錯,你跟老板說一句給你上面,你就出去買煙估摸時間差不多了就回去了,這就是非阻塞。

  • 異步同步:關註的是調用者等待被調用者返回調用結果時的狀態
  • 阻塞和非阻塞:關註的是如何通知被調用者結果已經完成

根據上面的概念劃分的話常見的
I/O模型分為這五種。

I/O模型:

blocking IO # 阻塞式IO

nonblocking IO # 非阻塞式IOIO multiplexing # 復用式IO

signal driven #事件驅動IO,事件驅動式IO有通知機制的

通知:

水平觸發:多次通知

邊緣觸發:只通知一次

asynchronous IO # 異步IO

1.2:為了解釋I/O模型,舉個read操作的例子。

例如:從磁盤一次read操作大體分為兩個步驟

用戶空間的進程是無法直接訪問硬件的,所以步驟大概如下。

當一個用戶進程需要進行io操作的時候向系統內核發送調用請求調用什麽數據。這時候就分為兩個步驟了。

  • 步驟一.當內核得到用戶進程通知要調用數據的時候,內核本身是沒有這個數據的,數據是在磁盤上的。所以內核會從磁盤加載到自己的內存中(內核內存) # 這個內存不是用戶進程的那個內存,雖然內核能這麽做,但這是不推薦的。
  • 步驟二.這個時候用戶進程還是沒有這個數據的,所以用戶進程還需要從內核內存中copy一份數據到自己的這個進程內存中。這個就是第二個步驟

真正屬於IO操作的步驟是內核內存中的數據到進程內存數據,這個屬於真正執行IO操作步驟的階段。內核內存從磁盤取數據的階段是等待事件完成階段。

五種IO模型

阻塞式I/O模型

當調用者發起調用請求之後,此處調用者一方會被掛起,這個時候進程會轉入不可中斷式睡眠狀態,這個時候調用者在得到結果之前什麽事情也不能夠去做,將一直處於等待過程當中。 阻塞型I/O舉例:當一個用戶訪問你web服務器的時候,這個時候你服務器會有一個進程去處理用戶的請求去取數據,這個時候這個進程是被掛起的狀態。這個時候這個進程是不能處理其他用戶的請求,因為處於睡眠狀態。當然事實情況是不會用阻塞式I/O的因為是需要處理兩路請求的,一部分網絡I/O請求,一部分磁盤I/O請求。一個進程通常情況下只能處理單路I/O的

非阻塞式I/O模型

當調用者發起調用請求時候,被調用者告訴你我收到你的請求了你等著吧~這個時候會有個問題是你必須每過一段時間去問被調用者:好了沒?這個叫做盲等待。那到底阻塞好還是非阻塞好。如果一件事情你沒辦法知道什麽時候好。你一遍遍問好了沒?還不如直接告訴進程你進入不可中斷式睡眠狀態吧。盲等待效率不一定高,非阻塞式不一定就是性能好。對於進程而言,由於他沒被阻塞,所以他不得不輪詢一遍遍的問。當老板告訴你面OK的時候,這個時候數據是從磁盤到了內核內存中了,還沒到進程內存中。這個時候調用者去取數據的時候這個時間段還是阻塞的,因為你在取數據不能做其他事情。

這個時候對web服務器來說非阻塞式並沒有帶來性能的提升。

復用式I/O模型

一般情況下一個進程只能處理一路I/O,一旦這個進程在做I/O操作的時候,其他請求在做什麽的時候這個進程都不知道,但我們都知道一個web服務器是需要處理兩路I/O的,一路是用戶通過網絡請求過來,一路是處理磁盤加載到內存中的操作。一旦進程被阻塞在磁盤上,這個時候網絡I/O發生異動了(比如用戶不請求了)。這個時候阻塞型I/O進程是不得知的。我們沒有辦法去結束這個進程的,因為他處於不可中斷的睡眠狀態,你ctrl+C也沒用除非他加載完數據。 復用式IO就是在這種情況下被發明在內核中。這個模式是這樣的當你發起一個I/O請求的時候,你的操作會交給內核中的一個代理人。這個代理人會將其翻譯為內核能理解的請求。這個時候用戶請求被阻塞在代理人上而不是內存中。 舉例:當你去銀行辦業務的時候,你們所有人就在櫃臺人員那裏排隊,一個個完成。這個時候當你說你要辦銀行卡的時候就算出卡需要時間,銀行人員是不是不能跟你說在旁邊等著? 當然咱們還能有這種模式,當你去辦業務的時候有個機器讓你選擇你要辦什麽業務並給你發一個號碼,當你要辦卡然後存錢兩個業務的時候,這個時候你想辦理什麽業務的時候都是跟這個代理請求,這個代理給你結果。這樣就叫做復用性I/O。這種內核早期的調用就叫做select(),還有種是poll()這兩種區別不大。這兩個是兩個公司發明的,其中一個公司看select組件挺受別人歡迎的就山寨了一個。 select是有限制的最多只能接受1024個請求並發。你要想更多只能改內核源碼,但你如果改內核源碼的話可能性能還會下降。當用戶請求進來的時候主進程接待分給其他子進程響應最多只能1024個,再多就超出上限拒絕服務了。這種模式還是阻塞的,但它不是阻塞自己本身上,而是阻塞在代理人上(select)。 當你發起一個IO請求的時候狀態是阻塞的,這個阻塞不是阻塞在自己的調用上,而是阻塞在select上。因為select是內核中復用型I/O代理。 當你發起I/O調用的時候,數據要從磁盤到內核內存。這個時候我們調用是被阻塞的,這個時候阻塞不是阻塞在內核的I/O調用上,而是阻塞select上。這個時候select還能接受其他請求,這是最大的好處,因為他能接收其他信號上面。

他不是在性能上有提升,因為他還是阻塞的。只是他能處理其他請求。

事件驅動型I/O

當請求者發I/O請求的時候,被調用者會告訴你我知道了,你該幹嘛就幹嘛去。 例子:當你去叫面的時候,老板告訴你大概需要10分鐘你該幹嘛幹嘛。你幹完自己的事情的時候,去端面的時候你就是取數據了。這個時候就是你能並行運行的原因。

當這個用在web服務器上,就比如一個用戶向你提交請求了。你向內核要數據,內核告訴你我知道了你該幹嘛,幹嘛去。這個時候你就能去處理其他用戶的請求了。這就是一個進程能處理多個請求的原因。當然這個並不是說性能一定好,當然他比阻塞或者盲等待肯定有優勢的。因為當你去從內核取數據當進程內存中的時候還是阻塞的。有了事件驅動型I/O就使一個進程能處理多個請求了,當然事件驅動型I/O第二個階段還是阻塞的。

在看事件驅動型I/O當你一個請求過來的時候,系統告訴你該幹嘛幹嘛去。這個時候第二個請求過來了,系統告訴第二個請求我知道了你幹嘛幹嘛去。這個時候進程1的請求數據好了,告訴你過來拿數據吧,這個時候進程二的數據也好了也要告訴你過來拿數據吧。你需要知道一點當內核通知你拿數據的時候,你一段時間不過來拿,這個信號是會消失的。所以這時候需要講到事件驅動通知的兩種機制。 通知: 水平觸發:多次通知,就是通知你一次你沒響應,我就再通知你。這就很浪費系統資源了 邊緣觸發:所謂邊緣觸發就是我通知你一次你沒響應,我就將這個通知事件交給回調函數,讓調用者自行來獲取,或者你可以這麽理解我將資源放在某處。你可以自己過來找我要的。簡單理解就是面好了我電話一遍遍通知你是水平觸發,如果我電話通知你了,然後你沒響應,我將面放到後廚,你自己也知道我電話通知你了。你自己明白你會去後廚拿的。

異步I/O模型

當調用者發送請求的時候,內核告訴你請求收到了,你該幹嘛就去幹嘛。這個時候內核默默的完成兩個步驟後就告訴你OK了。 所以web服務器正確步驟是這樣的,當用戶過來請求資源的時候,內核自己完成這兩個步驟從磁盤到內核空間,在到進程空間,這個時候進程就立即打包響應報文給客戶端。我們httpd的event模型就是事件驅動型IO,後來我們httpd2.4以後也支持異步I/O了。這個異步模型大大提升了系統性能,他的好處讓一個進程能處理多個請求,我們的進程或者內存通常會把常用的資源放在緩存中,當你有兩個請求同樣的數據的時候,這個數據靜態的並不敏感,我們的系統能夠直接響應,這對性能的提升來說可想而知

nginx從剛開始設計的時候就是用的事件驅動型IO,它同時也支持異步I/O。它還有種機制是內存映射

IO模型詳解及應用