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