「基礎(chǔ)知識總結(jié)」 IO模型

IO模型

五種IO模型包括:阻塞IO、非阻塞IO、信號驅(qū)動IO、IO多路轉(zhuǎn)接、異步IO。其中,前四個被稱為同步IO。

阻塞IO(blocking I/O BIO)

A拿著一支魚竿在河邊釣魚,并且一直在魚竿前等,在等的時候不做其他的事情,十分專心。只有魚上鉤的時,才結(jié)束掉等的動作,把魚釣上來。
在內(nèi)核將數(shù)據(jù)準(zhǔn)備好之前,系統(tǒng)調(diào)用會一直等待所有的套接字,默認(rèn)的是阻塞方式。
其實,我們例子中所說的魚竿就是這一個文件描述符。這個模型是我們最常見的,程序調(diào)用和我們編寫的基本程序是一致的。


fd=connect();
write(fd);
read(fd);
close(fd);

程序的read必須在write之后執(zhí)行,當(dāng)write阻塞住了,read就不能執(zhí)行下去,一直處于等待狀態(tài)。

非阻塞IO(noblocking I/O)

B也在河邊釣魚,但是B不想將自己的所有時間都花費在釣魚上,在等魚上鉤這個時間段中,B也在做其他的事情(一會看看書,一會讀讀報紙,一會又去看其他人的釣魚等),但B在做這些事情的時候,每隔一個固定的時間檢查魚是否上鉤。一旦檢查到有魚上鉤,就停下手中的事情,把魚釣上來。


其實,B在檢查魚竿是否有魚,是一個輪詢的過程。
每次客戶詢問內(nèi)核是否有數(shù)據(jù)準(zhǔn)備好,即文件描述符緩沖區(qū)是否就緒。當(dāng)有數(shù)據(jù)報準(zhǔn)備好時,就進(jìn)行拷貝數(shù)據(jù)報的操作。當(dāng)沒有數(shù)據(jù)報準(zhǔn)備好時,也不阻塞程序,內(nèi)核直接返回未準(zhǔn)備就緒的信號,等待用戶程序的下一個輪尋。
但是,輪尋對于CPU來說是較大的浪費,一般只有在特定的場景下才使用。

信號驅(qū)動IO(signal blocking I/O)

C也在河邊釣魚,但與A、B不同的是,C比較聰明,他給魚竿上掛一個鈴鐺,當(dāng)有魚上鉤的時候,這個鈴鐺就會被碰響,C就會將魚釣上來。


信號驅(qū)動IO模型,應(yīng)用進(jìn)程告訴內(nèi)核:當(dāng)數(shù)據(jù)報準(zhǔn)備好的時候,給我發(fā)送一個信號,對SIGIO信號進(jìn)行捕捉,并且調(diào)用我的信號處理函數(shù)來獲取數(shù)據(jù)報。

IO多路復(fù)用(I/O multiplexing)

D同樣也在河邊釣魚,但是D生活水平比較好,D拿了很多的魚竿,一次性有很多魚竿在等,D不斷的查看每個魚竿是否有魚上鉤。增加了效率,減少了等待的時間。


IO多路轉(zhuǎn)接是多了一個select函數(shù),select函數(shù)有一個參數(shù)是文件描述符集合,對這些文件描述符進(jìn)行循環(huán)監(jiān)聽,當(dāng)某個文件描述符就緒時,就對這個文件描述符進(jìn)行處理。

其中,select只負(fù)責(zé)等,recvfrom只負(fù)責(zé)拷貝。
IO多路轉(zhuǎn)接是屬于阻塞IO,但可以對多個文件描述符進(jìn)行阻塞監(jiān)聽,所以效率較阻塞IO的高。

異步IO(asynchronous I/O)

E也想釣魚,但E有事情,于是他雇來了F,讓F幫他等待魚上鉤,一旦有魚上鉤,F(xiàn)就打電話給E,E就會將魚釣上去。
當(dāng)應(yīng)用程序調(diào)用aio_read時,內(nèi)核一方面去取數(shù)據(jù)報內(nèi)容返回,另一方面將程序控制權(quán)還給應(yīng)用進(jìn)程,應(yīng)用進(jìn)程繼續(xù)處理其他事情,是一種非阻塞的狀態(tài)。
當(dāng)內(nèi)核中有數(shù)據(jù)報就緒時,由內(nèi)核將數(shù)據(jù)報拷貝到應(yīng)用程序中,返回aio_read中定義好的函數(shù)處理程序。
很少有Linux系統(tǒng)支持,Windows的IOCP就是該模型。

可以看出,阻塞程度:阻塞IO>非阻塞IO>多路轉(zhuǎn)接IO>信號驅(qū)動IO>異步IO,效率是由低到高的。

select、poll、epoll之間的區(qū)別

  • select==>時間復(fù)雜度O(n)
    它僅僅知道了,有I/O事件發(fā)生了,卻并不知道是哪那幾個流(可能有一個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出數(shù)據(jù),或者寫入數(shù)據(jù)的流,對他們進(jìn)行操作。所以select具有O(n)的無差別輪詢復(fù)雜度,同時處理的流越多,無差別輪詢時間就越長。

  • poll==>時間復(fù)雜度O(n)
    poll本質(zhì)上和select沒有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個fd對應(yīng)的設(shè)備狀態(tài), 但是它沒有最大連接數(shù)的限制,原因是它是基于鏈表來存儲的.

  • epoll==>時間復(fù)雜度O(1)
    epoll可以理解為event poll,不同于忙輪詢和無差別輪詢,epoll會把哪個流發(fā)生了怎樣的I/O事件通知我們。所以我們說epoll實際上是事件驅(qū)動(每個事件關(guān)聯(lián)上fd)的,此時我們對這些流的操作都是有意義的。(復(fù)雜度降低到了O(1))

select,poll,epoll都是IO多路復(fù)用的機(jī)制。I/O多路復(fù)用就通過一種機(jī)制,可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作。但select,poll,epoll本質(zhì)上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負(fù)責(zé)進(jìn)行讀寫,異步I/O的實現(xiàn)會負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

epoll跟select都能提供多路I/O復(fù)用的解決方案。在現(xiàn)在的Linux內(nèi)核里有都能夠支持,其中epoll是Linux所特有,而select則應(yīng)該是POSIX所規(guī)定,一般操作系統(tǒng)均有實現(xiàn)

select:

select本質(zhì)上是通過設(shè)置或者檢查存放fd標(biāo)志位的數(shù)據(jù)結(jié)構(gòu)來進(jìn)行下一步處理。這樣所帶來的缺點是:

  1. 單個進(jìn)程可監(jiān)視的fd數(shù)量被限制,即能監(jiān)聽端口的大小有限。
    一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機(jī)默認(rèn)是1024個。64位機(jī)默認(rèn)是2048.

  2. 對socket進(jìn)行掃描時是線性掃描,即采用輪詢的方法,效率較低:
    當(dāng)套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字注冊某個回調(diào)函數(shù),當(dāng)他們活躍時,自動完成相關(guān)操作,那就避免了輪詢,這正是epoll與kqueue做的。

3、需要維護(hù)一個用來存放大量fd的數(shù)據(jù)結(jié)構(gòu),這樣會使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時復(fù)制開銷大

poll:

poll本質(zhì)上和select沒有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個fd對應(yīng)的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊列中加入一項并繼續(xù)遍歷,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設(shè)備,則掛起當(dāng)前進(jìn)程,直到設(shè)備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經(jīng)歷了多次無謂的遍歷。

它沒有最大連接數(shù)的限制,原因是它是基于鏈表來存儲的,但是同樣有一個缺點:

1、大量的fd的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核地址空間之間,而不管這樣的復(fù)制是不是有意義。

2、poll還有一個特點是“水平觸發(fā)”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。

epoll:

epoll有EPOLLLT和EPOLLET兩種觸發(fā)模式,LT是默認(rèn)的模式,ET是“高速”模式。LT模式下,只要這個fd還有數(shù)據(jù)可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發(fā))模式中,它只會提示一次,直到下次再有數(shù)據(jù)流入之前都不會再提示了,無 論fd中是否還有數(shù)據(jù)可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小于請求值,或者 遇到EAGAIN錯誤。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內(nèi)核就會采用類似callback的回調(diào)機(jī)制來激活該fd,epoll_wait便可以收到通知。

epoll為什么要有EPOLLET觸發(fā)模式?

如果采用EPOLLLT模式的話,系統(tǒng)中一旦有大量你不需要讀寫的就緒文件描述符,它們每次調(diào)用epoll_wait都會返回,這樣會大大降低處理程序檢索自己關(guān)心的就緒文件描述符的效率.。而采用EPOLLET這種邊沿觸發(fā)模式的話,當(dāng)被監(jiān)控的文件描述符上有可讀寫事件發(fā)生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數(shù)據(jù)全部讀寫完(如讀寫緩沖區(qū)太小),那么下次調(diào)用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現(xiàn)第二次可讀寫事件才會通知你?。?!這種模式比水平觸發(fā)效率高,系統(tǒng)不會充斥大量你不關(guān)心的就緒文件描述符

epoll的優(yōu)點:

1、沒有最大并發(fā)連接的限制,能打開的FD的上限遠(yuǎn)大于1024(1G的內(nèi)存上能監(jiān)聽約10萬個端口);
2、效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調(diào)用callback函數(shù);
即Epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān),因此在實際的網(wǎng)絡(luò)環(huán)境中,Epoll的效率就會遠(yuǎn)遠(yuǎn)高于select和poll。
epoll并沒有使用mmap

select、poll、epoll 區(qū)別總結(jié):

1、支持一個進(jìn)程所能打開的最大連接數(shù)

  • select
    單個進(jìn)程所能打開的最大連接數(shù)有FD_SETSIZE宏定義,其大小是32個整數(shù)的大?。ㄔ?2位的機(jī)器上,大小就是3232,同理64位機(jī)器上FD_SETSIZE為3264),當(dāng)然我們可以對進(jìn)行修改,然后重新編譯內(nèi)核,但是性能可能會受到影響,這需要進(jìn)一步的測試。

  • poll
    poll本質(zhì)上和select沒有區(qū)別,但是它沒有最大連接數(shù)的限制,原因是它是基于鏈表來存儲的

  • epoll
    雖然連接數(shù)有上限,但是很大,1G內(nèi)存的機(jī)器上可以打開10萬左右的連接,2G內(nèi)存的機(jī)器可以打開20萬左右的連接

2、FD劇增后帶來的IO效率問題

  • select
    因為每次調(diào)用時都會對連接進(jìn)行線性遍歷,所以隨著FD的增加會造成遍歷速度慢的“線性下降性能問題”。

  • poll
    同上

  • epoll
    因為epoll內(nèi)核中實現(xiàn)是根據(jù)每個fd上的callback函數(shù)來實現(xiàn)的,只有活躍的socket才會主動調(diào)用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。

3、 消息傳遞方式

  • select
    內(nèi)核需要將消息傳遞到用戶空間,都需要內(nèi)核拷貝動作

  • poll
    同上

  • epoll
    epoll通過內(nèi)核和用戶空間共享一塊內(nèi)存來實現(xiàn)的。

總結(jié):
綜上,在選擇select,poll,epoll時要根據(jù)具體的使用場合以及這三種方式的自身特點。

  1. 表面上看epoll的性能最好,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機(jī)制需要很多函數(shù)回調(diào)。

  2. select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設(shè)計改善

select的幾大缺點:

(1)每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大
(2)同時每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數(shù)量太小了,默認(rèn)是1024

2 poll實現(xiàn)
  poll的實現(xiàn)和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結(jié)構(gòu)而不是select的fd_set結(jié)構(gòu),其他的都差不多,管理多個描述符也是進(jìn)行輪詢,根據(jù)描述符的狀態(tài)進(jìn)行處理,但是poll沒有最大文件描述符數(shù)量的限制。poll和select同樣存在一個缺點就是,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。

3、epoll
  epoll既然是對select和poll的改進(jìn),就應(yīng)該能避免上述的三個缺點。那epoll都是怎么解決的呢?在此之前,我們先看一下epoll和select和poll的調(diào)用接口上的不同,select和poll都只提供了一個函數(shù)——select或者poll函數(shù)。而epoll提供了三個函數(shù),epoll_create,epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個epoll句柄;epoll_ctl是注冊要監(jiān)聽的事件類型;epoll_wait則是等待事件的產(chǎn)生。

對于第一個缺點,epoll的解決方案在epoll_ctl函數(shù)中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進(jìn)內(nèi)核,而不是在epoll_wait的時候重復(fù)拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

對于第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應(yīng)的設(shè)備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)并為每個fd指定一個回調(diào)函數(shù),當(dāng)設(shè)備就緒,喚醒等待隊列上的等待者時,就會調(diào)用這個回調(diào)函數(shù),而這個回調(diào)函數(shù)會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現(xiàn)睡一會,判斷一會的效果,和select實現(xiàn)中的第7步是類似的)。

對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠(yuǎn)大于2048,舉個例子,在1GB內(nèi)存的機(jī)器上大約是10萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。

總結(jié):
(1)select,poll實現(xiàn)需要自己不斷輪詢所有fd集合,直到設(shè)備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調(diào)用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設(shè)備就緒時,調(diào)用回調(diào)函數(shù),把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進(jìn)入睡眠的進(jìn)程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節(jié)省了大量的CPU時間。這就是回調(diào)機(jī)制帶來的性能提升。

(2)select,poll每次調(diào)用都要把fd集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次,并且要把current往設(shè)備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊列并不是設(shè)備等待隊列,只是一個epoll內(nèi)部定義的等待隊列)。這也能節(jié)省不少的開銷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容