I/O多路转换 select
Linux驅(qū)動部分我們曾經(jīng)使用了poll機制完成了在應用層代碼讀取按鍵值。這節(jié)課介紹的select也很相似。當我們要監(jiān)控好幾個文件描述符的讀寫呢?如果我們阻塞的去處理其中一個,那第二個怎么辦呢?下面我們一起想想辦法。
方法一:使用fork將一個進程變成兩個進程,每個進程處理一套數(shù)據(jù)通路,如果使用多個進程,每一個進程就可以阻塞處理read,write函數(shù)。但是這也產(chǎn)生了問題:操作什么時候終止?如果子進程接收到了文件結束夫標志,那么該子進程就終止,然后父進程接收到了SIGCHLD信號。但是如果父進程終止,那么應該通知紫禁城停止,為此也需要一個信號。我們可以不使用多進程,而是使用一個進程中的兩個線程。這避免了終止進程的復雜性,但是卻要求處理線程之間的同步,在減少復雜性方面也是得不償失。
方法二:配置為不阻塞輪詢法,這種思路大部分情況下直接否定了,不做討論。
方法三:異步IO。基本思想就是告訴內(nèi)核當一個描述符已經(jīng)準備好了之后,再用一個信號量通知他。這種技術存在的問題,1、并不是所有的系統(tǒng)都支持(這個我個人沒遇到過,接觸比較少)2、之中信號對每個進程只有一個SIGOLL或者SIGIO。如果該信號要對兩個描述符都起作用,那么接收到此信號時,我們?nèi)耘f無法判斷是哪一個描述符已經(jīng)準備好了。
方法四:
I/O多路轉換,先構造一張有官描述符的列表,然后調(diào)用一個函數(shù),知道這些描述符中的一個準備好的進行I/O時,函數(shù)才回去返回,在返回的時候,他會告訴你在那些描述符已經(jīng)準備好了可以進行I/O。
從 select函數(shù)返回后,內(nèi)核告訴我們一下信息:
?對我們的要求已經(jīng)做好準備的描述符的個數(shù)
?對于三種條件哪些描述符已經(jīng)做好準備.(讀,寫,異常)
有了這些返回信息,我們可以調(diào)用合適的I/O函數(shù)(通常是 read 或 write),并且這些函數(shù)不會再阻塞.
返回:做好準備的文件描述符的個數(shù),超時為0,錯誤為 -1.
首先我們先看一下最后一個參數(shù)。它指明我們要等待的時間:
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
有三種情況:
timeout == NULL 等待無限長的時間。等待可以被一個信號中斷。當有一個描述符做好準備或者是捕獲到一個信號時函數(shù)會返回。如果捕獲到一個信號, select函數(shù)將返回 -1,并將變量 erro設為 EINTR。
timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回。加入描述符集的描述符都會被測試,并且返回滿足要求的描述符的個數(shù)。這種方法通過輪詢,無阻塞地獲得了多個文件描述符狀態(tài)。
timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的時間。當有描述符符合條件或者超過超時時間的話,函數(shù)返回。在超時時間即將用完但又沒有描述符合條件的話,返回 0。對于第一種情況,等待也會被信號所中斷。
中間的三個參數(shù) readset, writset, exceptset,指向描述符集。這些參數(shù)指明了我們關心哪些描述符,和需要滿足什么條件(可寫,可讀,異常)。一個文件描述集保存在 fd_set 類型中。fd_set類型變量每一位代表了一個描述符。我們也可以認為它只是一個由很多二進制位構成的數(shù)組。如下圖所示:
對于 fd_set類型的變量我們所能做的就是聲明一個變量,為變量賦一個同種類型變量的值,或者使用以下幾個宏來 #include <sys/select.h>
int FD_ZERO(int fd, fd_set *fdset);
int FD_CLR(int fd, fd_set *fdset);
int FD_SET(int fd, fd_set *fd_set);
int FD_ISSET(int fd, fd_set *fdset);ZERO宏將一個 fd_set類型變量的所有位都設為 0,使用FD_SET將變量的某個位置位。清除某個位時可以使用 FD_CLR,我們可以使用 FD_SET來測試某個位是否被置位。
當聲明了一個文件描述符集后,必須用FD_ZERO將所有位置零。之后將我們所感興趣的描述符所對應的位置位,操作如下:
具體解釋select的參數(shù):
(1)intmaxfdp是一個整數(shù)值,是指集合中所有文件描述符的范圍,即所有文件描述符的最大值加1,不能錯。
說明:對于這個原理的解釋可以看上邊f(xié)d_set的詳細解釋,fd_set是以位圖的形式來存儲這些文件描述符。maxfdp也就是定義了位圖中有效的位的個數(shù)。
(2)fd_setreadfds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監(jiān)視這些文件描述符的讀變化的,即我們關心是否可以從這些文件中讀取數(shù)據(jù)了,如果這個集合中有一個文件可讀,select就會返回一個大于0的值,表示有文件可讀;如果沒有可讀的文件,則根據(jù)timeout參數(shù)再判斷是否超時,若超出timeout的時間,select返回0,若發(fā)生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的讀變化。
(3)fd_setwritefds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監(jiān)視這些文件描述符的寫變化的,即我們關心是否可以向這些文件中寫入數(shù)據(jù)了,如果這個集合中有一個文件可寫,select就會返回一個大于0的值,表示有文件可寫,如果沒有可寫的文件,則根據(jù)timeout參數(shù)再判斷是否超時,若超出timeout的時間,select返回0,若發(fā)生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的寫變化。
(4)fd_seterrorfds同上面兩個參數(shù)的意圖,用來監(jiān)視文件錯誤異常文件。
(5)structtimeval timeout是select的超時時間,這個參數(shù)至關重要,它可以使select處于三種狀態(tài),第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置于阻塞狀態(tài),一定等到監(jiān)視文件描述符集合中某個文件描述符發(fā)生變化為止;第二,若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函數(shù),不管文件描述符是否有變化,都立刻返回繼續(xù)執(zhí)行,文件無變化返回0,有變化返回一個正值;第三,timeout的值大于0,這就是等待的超時時間,即 select在timeout時間內(nèi)阻塞,超時時間之內(nèi)有事件到來就返回了,否則在超時后不管怎樣一定返回,返回值同上述。
說明:
函數(shù)返回:
(1)當監(jiān)視的相應的文件描述符集中滿足條件時,比如說讀文件描述符集中有數(shù)據(jù)到來時,內(nèi)核(I/O)根據(jù)狀態(tài)修改文件描述符集,并返回一個大于0的數(shù)。
(2)當沒有滿足條件的文件描述符,且設置的timeval監(jiān)控時間超時時,select函數(shù)會返回一個為0的值。
(3)當select返回負值時,發(fā)生錯誤。
理解select模型:
理解select模型的關鍵在于理解fd_set,為說明方便,取fd_set長度為1字節(jié),fd_set中的每一bit可以對應一個文件描述符fd。則1字節(jié)長的fd_set最大可以對應8個fd。
(1)執(zhí)行fd_set set;FD_ZERO(&set);則set用位表示是0000,0000。
(2)若fd=5,執(zhí)行FD_SET(fd,&set);后set變?yōu)?001,0000(第5位置為1)
(3)若再加入fd=2,fd=1,則set變?yōu)?001,0011
(4)執(zhí)行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發(fā)生可讀事件,則select返回,此時set變?yōu)?000,0011。注意:沒有事件發(fā)生的fd=5被清空。
基于上面的討論,可以輕松得出select模型的特點:
(1)可監(jiān)控的文件描述符個數(shù)取決與sizeof(fd_set)的值。我這邊服務器上sizeof(fd_set)=512,每bit表示一個文件描述符,則我服務器上支持的最大文件描述符是512*8=4096。據(jù)說可調(diào),另有說雖然可調(diào),但調(diào)整上限受于編譯內(nèi)核時的變量值。
(2)將fd加入select監(jiān)控集的同時,還要再使用一個數(shù)據(jù)結構array保存放到select監(jiān)控集中的fd,一是用于再select返回后,array作為源數(shù)據(jù)和fd_set進行FD_ISSET判斷。二是select返回后會把以前加入的但并無事件發(fā)生的fd清空,則每次開始 select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個參數(shù)。
(3)可見select模型必須在select前循環(huán)array(加fd,取maxfd),select返回后循環(huán)array(FD_ISSET判斷是否有時間發(fā)生)。
- 在 知乎上看到大牛們在討論一個問題
- 作者:羅然
鏈接:https://www.zhihu.com/question/20114168/answer/31042919
來源:知乎
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
先用select接口(poll/epoll,kq,iocp)接受請求,這樣可以保證并發(fā),在這個環(huán)節(jié)他只管收,不處理業(yè)務,把FD放到一個buffer(一個q里面),然后業(yè)務處理模型對接線程池。可以使復雜業(yè)務處理上的負擔被分擔。select+線程池,這樣兼顧了并發(fā)(犧牲了一點性能),又保證了因為邏輯代碼的簡潔性。如果選擇完全異步的方式,你就要在業(yè)務處理里面使用完全的異步API,至少很多數(shù)據(jù)庫驅(qū)動,緩存驅(qū)動等等你需要用的到技術都沒有提供異步API,很多業(yè)務要保障流程的正確是需要同步操作的,而且業(yè)務如果全部使用異步API,各種不明確回調(diào)和閉包導致內(nèi)存暴棧的危險上升(我想各位應該被nodejs折磨過吧),對開發(fā)人員思考方式和技術實力都有較高的要求。一個部門里面有兩個了解epoll就算技術非常NB的核心部門了吧,假若有能正確駕馭epoll,了解各種觸發(fā)方式,狀態(tài)機,特別是要能正確讀寫完整的信息,而沒有造成大量的CLOSE_WAIT,是特別特別不易的。我曾在tornado上面搭建過一個線程池。原型參見:nikoloss/iceworld · GitHub雖然不算最完美的解決方案,但是也在工作中省去了很多煩惱。他的效率雖沒有原生tornado高,但是非常適合多人合作(盡管如此效率還是要暴webpy幾條街)。
對于這個回答我覺得還是非常不錯的。
總結
以上是生活随笔為你收集整理的I/O多路转换 select的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打开英伟达控制面板超时打不开解决办法
- 下一篇: 李开复现身说法成功的十个启发