簡介:這是select函數(shù)的詳細頁面,介紹了和linux,有關(guān)的知識、技巧、經(jīng)驗,和一些linux源碼等。
select
(1)select函數(shù)說明
前面的fcntl函數(shù)解決了文件的共享問題,接下來該處理I/O 復用的情況了。
總的來說,I/O 處理的模型有5 種。
· 阻塞 I/O模型:在這種模型下,若所調(diào)用的I/O 函數(shù)沒有完成相關(guān)的功能就會使進程
掛起,直到相關(guān)數(shù)據(jù)到才會出錯返回。如常見對管道設(shè)備、終端設(shè)備和網(wǎng)絡設(shè)備進行讀寫時
經(jīng)常會出現(xiàn)這種情況。
· 非阻塞模型:在這種模型下,當請求的I/O 操作不能完成時,則不讓進程睡眠,
而且返回一個錯誤。非阻塞I/O 使用戶可以調(diào)用不會永遠阻塞的I/O 操作,如open、write
和read。如果該操作不能完成,則會立即出錯返回,且表示該I/O 如果該操作繼續(xù)執(zhí)行
就會阻塞。
· I/O多路轉(zhuǎn)接模型:在這種模型下,如果請求的I/O操作阻塞,且它不是真正阻塞I/O,
而是讓其中的一個函數(shù)等待,在這期間,I/O 還能進行其他操作。如本節(jié)要介紹的select函數(shù)
和poll函數(shù),就是屬于這種模型。
· 信號驅(qū)動 I/O 模型:在這種模型下,通過安裝一個信號處理程序,系統(tǒng)可以自動
捕獲特定信號的到來,從而啟動I/O。這是由內(nèi)核通知用戶何時可以啟動一個I/O 操作
決定的。
· 異步 I/O模型:在這種模型下,當一個描述符已準備好,可以啟動I/O 時,進程會通
知內(nèi)核?,F(xiàn)在,并不是所有的系統(tǒng)都支持這種模型。
可以看到,select的I/O 多路轉(zhuǎn)接模型是處理I/O 復用的一個高效的方法。它可以具體設(shè)
置每一個所關(guān)心的文件描述符的條件、希望等待的時間等,從select 函數(shù)返回時,內(nèi)核會通
知用戶已準備好的文件描述符的數(shù)量、已準備好的條件等。通過使用select 返回值,就可以
調(diào)用相應的I/O 處理函數(shù)了。
(2)select函數(shù)格式
Select函數(shù)的語法格式如表6.8 所示。
表6.8 fcntl函數(shù)語法要點
所需頭文件
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
函數(shù)原型int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval
*timeout)
numfds:需要檢查的號碼最高的文件描述符加1
readfds:由select()監(jiān)視的讀文件描述符集合
writefds:由select()監(jiān)視的寫文件描述符集合
exeptfds:由select()監(jiān)視的異常處理文件描述符集合
NULL:永遠等待,直到捕捉到信號或文件描述符已準備好為止
具體值:struct timeval類型的指針,若等待為timeout時間還沒有文件描
符準備好,就立即返回
函數(shù)傳入值
timeout
0:從不等待,測試所有指定的描述符并立即返回
函數(shù)返回值
成功:準備好的文件描述符
-1:出錯
思考
請讀者考慮一下如何確定最高的文件描述符?
可以看到,select 函數(shù)根據(jù)希望進行的文件操作對文件描述符進行了分類處理,這里,
對文件描述符的處理主要涉及到4 個宏函數(shù),如表6.9 所示。
表6.9 select文件描述符處理函數(shù)
FD_ZERO(fd_set *set) 清除一個文件描述符集
FD_SET(int fd,fd_set *set) 將一個文件描述符加入文件描述符集中
FD_CLR(int fd,fd_set *set) 將一個文件描述符從文件描述符集中清除
FD_ISSET(int fd,fd_set *set) 測試該集中的一個給定位是否有變化
一般來說,在使用select 函數(shù)之前,首先使用FD_ZERO 和FD_SET 來初始化文件描述
符集,在使用了select 函數(shù)時,可循環(huán)使用FD_ISSET 測試描述符集,在執(zhí)行完對相關(guān)后文
件描述符后,使用FD_CLR來清楚描述符集。
另外,select函數(shù)中的timeout是一個struct timeval類型的指針,該結(jié)構(gòu)體如下所示:
struct timeval {
long tv_sec; /* second */
long tv_unsec; /* and microseconds*/
}
可以看到,這個時間結(jié)構(gòu)體的精確度可以設(shè)置到微秒級,這對于大多數(shù)的應用而言已經(jīng)
足夠了。
(3)使用實例
由于 Select函數(shù)多用于I/O 操作可能會阻塞的情況下,而對于可能會有阻塞I/O 的管道、
網(wǎng)絡編程,本書到現(xiàn)在為止還沒有涉及。因此,本例主要表現(xiàn)了如何使用select 函數(shù),而其
中的I/O 操作是不會阻塞的。
本實例中主要實現(xiàn)將文件hello1 里的內(nèi)容讀出,并將此內(nèi)容每隔10s 寫入hello2 中去。
在這里建立了兩個描述符集,其中一個描述符集inset1 是用于讀取文件內(nèi)容,另一個描述符
集inset2是用于寫入文件的。兩個文件描述符fds[0]和fds[1]分別指向這一文件描述符。在首
先初始化完各文件描述符集之后,就開始了循環(huán)測試這兩個文件描述符是否可讀寫,由于在
這里沒有阻塞,所以文件描述符處于準備就緒的狀態(tài)。這時,就分別對文件描述符fds[0]和
fsd[1]進行讀寫操作。該程序的流程圖如圖6.2 所示。
/*select.c*/
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
int fds[2];
char buf[7];
int i,rc,maxfd;
fd_set inset1,inset2;
struct timeval tv;
/*首先按一定的權(quán)限打開hello1文件*/
if((fds[0] = open ("hello1", O_RDWR|O_CREAT,0666))<0)
perror("open hello1");
/*再按一定的權(quán)限打開hello2文件*/
if((fds[1] = open ("hello2", O_RDWR|O_CREAT,0666))<0)
perror("open hello2");
if((rc = write(fds[0],"Hello!\n",7)))
printf("rc=%d\n",rc);
lseek(fds[0],0,SEEK_SET);
/*取出兩個文件描述符中的較大者*/
maxfd = fds[0]>fds[1] ? fds[0] : fds[1];
/*初始化讀集合inset1,并在讀集合中加入相應的描述集*/
FD_ZERO(&inset1);
FD_SET(fds[0],&inset1);
/*初始化寫集合inset2,并在寫集合中加入相應的描述集*/
FD_ZERO(&inset2);
FD_SET(fds[1],&inset2);
tv.tv_sec=2;
tv.tv_usec=0;
/*循環(huán)測試該文件描述符是否準備就緒,并調(diào)用select函數(shù)對相關(guān)文件描述符做對應操作*/
while(FD_ISSET(fds[0],&inset1)||FD_ISSET(fds[1],&inset2)){
if(select(maxfd+1,&inset1,&inset2,NULL,&tv)<0)
perror("select");
else{
if(FD_ISSET(fds[0],&inset1)){
rc = read(fds[0],buf,7);
if(rc>0){
buf[rc]='\0';
printf("read: %s\n",buf);
}else
perror("read");
}
if(FD_ISSET(fds[1],&inset2)){
rc = write(fds[1],buf,7);
if(rc>0){
buf[rc]='\0';
printf("rc=%d,write: %s\n",rc,buf);
}else
perror("write");
sleep(10);
}
}
}
exit(0);
}