IO多路复用模型之select()函数详解
IO復(fù)用
我們首先來看看服務(wù)器編程的模型,客戶端發(fā)來的請求服務(wù)端會產(chǎn)生一個進(jìn)程來對其進(jìn)行服務(wù),每當(dāng)來一個客戶請求就產(chǎn)生一個進(jìn)程來服務(wù),然而進(jìn)程不可能無限制的產(chǎn)生,因此為了解決大量客戶端訪問的問題,引入了IO復(fù)用技術(shù)。
即:一個進(jìn)程可以同時對多個客戶請求進(jìn)行服務(wù)。
也就是說IO復(fù)用的“介質(zhì)”是進(jìn)程(準(zhǔn)確的說復(fù)用的是select和poll,因為進(jìn)程也是靠調(diào)用select和poll來實現(xiàn)的),復(fù)用一個進(jìn)程(select和poll)來對多個IO進(jìn)行服務(wù),雖然客戶端發(fā)來的IO是并發(fā)的但是IO所需的讀寫數(shù)據(jù)多數(shù)情況下是沒有準(zhǔn)備好的,因此就可以利用一個函數(shù)(select和poll)來監(jiān)聽IO所需的這些數(shù)據(jù)的狀態(tài),一旦IO有數(shù)據(jù)可以進(jìn)行讀寫了,進(jìn)程就來對這樣的IO進(jìn)行服務(wù)。
IO多路復(fù)用指內(nèi)核一旦發(fā)現(xiàn)進(jìn)程指定的一個或者多個IO條件準(zhǔn)備讀取,它就通知該進(jìn)程。
IO多路復(fù)用適用如下場合:
1.當(dāng)客戶處理多個描述字時(一般是交互式輸入和網(wǎng)絡(luò)套接口),必須使用I/O復(fù)用。
2.當(dāng)一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現(xiàn)。
3.如果一個TCP服務(wù)器既要處理監(jiān)聽套接口,又要處理已連接套接口,一般也要用到I/O復(fù)用。
4.如果一個服務(wù)器即要處理TCP,又要處理UDP,一般要使用I/O復(fù)用。
5.如果一個服務(wù)器要處理多個服務(wù)或多個協(xié)議,一般要使用I/O復(fù)用。
與多進(jìn)程和多線程技術(shù)相比,I/O多路復(fù)用技術(shù)的最大優(yōu)勢是系統(tǒng)開銷小,系統(tǒng)不必創(chuàng)建進(jìn)程/線程,也不必維護(hù)這些進(jìn)程/線程,從而大大減小了系統(tǒng)的開銷。
select函數(shù)
該函數(shù)允許進(jìn)程指示內(nèi)核等待多個事件中的任何一個發(fā)生,并只在有一個或多個時間發(fā)生或經(jīng)歷一段指定的時間后才喚醒他。
#include <sys/select.h> #include >sys/time.h> int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
返回值:
若有就緒描述符返回其數(shù)目,若超時則為0,若出錯則為-1
參數(shù)
第一個參數(shù)——int maxfdp1
第一個參數(shù)maxfdp1指定待測試的描述字個數(shù)。
它的值是待測試的最大描述字加1(因此把該參數(shù)命名為maxfdp1),描述字0、1、2…maxfdp1-1均將被測試。
因為文件描述符是從0開始的。
fd_set *readset
fd_set *writeset
fd_set *exceptset
中間的三個參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測試讀、寫和異常條件的描述字。
如果對某一個的條件不感興趣,就可以把它設(shè)為空指針。struct fd_set可以理解為一個集合,這個集合中存放的是文件描述符,可通過以下四個宏進(jìn)行設(shè)置:
void FD_ZERO(fd_set *fdset); //清空集合 void FD_SET(int fd, fd_set *fdset); //將一個給定的文件描述符加入集合之中 void FD_CLR(int fd, fd_set *fdset); //將一個給定的文件描述符從集合中刪除 int FD_ISSET(int fd, fd_set *fdset); // 檢查集合中指定的文件描述符是否可以讀寫
const struct timeval *timeout
timeout告知內(nèi)核等待所指定描述字中的任何一個就緒可花多少時間。其timeval結(jié)構(gòu)用于指定這段時間的秒數(shù)和微秒數(shù)。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
這個參數(shù)有三種可能:
1.永遠(yuǎn)等待下去:僅在有一個描述字準(zhǔn)備好I/O時才返回。為此,把該參數(shù)設(shè)置為空指針NULL。
2.等待一段固定時間:在有一個描述字準(zhǔn)備好I/O時返回,但是不超過由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)。
3.根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數(shù)必須指向一個timeval結(jié)構(gòu),而且其中的定時器值必須為0。
select函數(shù)的調(diào)用過程
(1)使用copy_from_user從用戶空間拷貝fd_set到內(nèi)核空間
(2)注冊回調(diào)函數(shù)__pollwait
(3)遍歷所有fd
調(diào)用其對應(yīng)的poll方法(對于socket,這個poll方法是sock_poll,sock_poll根據(jù)情況會調(diào)用到tcp_poll,udp_poll或者datagram_poll)
(4)以tcp_poll為例,其核心實現(xiàn)就是__pollwait,也就是上面注冊的回調(diào)函數(shù)。
(5)__pollwait的主要工作就是把current(當(dāng)前進(jìn)程)掛到設(shè)備的等待隊列中,不同的設(shè)備有不同的等待隊列,對于tcp_poll來說,其等待隊列是sk->sk_sleep(注意把進(jìn)程掛到等待隊列中并不代表進(jìn)程已經(jīng)睡眠了)。在設(shè)備收到一條消息(網(wǎng)絡(luò)設(shè)備)或填寫完文件數(shù)據(jù)(磁盤設(shè)備)后,會喚醒設(shè)備等待隊列上睡眠的進(jìn)程,這時current便被喚醒了。
(6)poll方法返回時會返回一個描述讀寫操作是否就緒的mask掩碼,根據(jù)這個mask掩碼給fd_set賦值。
(7)如果遍歷完所有的fd,還沒有返回一個可讀寫的mask掩碼,則會調(diào)用schedule_timeout是調(diào)用select的進(jìn)程(也就是current)進(jìn)入睡眠。當(dāng)設(shè)備驅(qū)動發(fā)生自身資源可讀寫后,會喚醒其等待隊列上睡眠的進(jìn)程。如果超過一定的超時時間(schedule_timeout指定),還是沒人喚醒,則調(diào)用select的進(jìn)程會重新被喚醒獲得CPU,進(jìn)而重新遍歷fd,判斷有沒有就緒的fd。
(8)把fd_set從內(nèi)核空間拷貝到用戶空間。
select睡眠和喚醒過程
select巧妙的利用等待隊列機(jī)制讓用戶進(jìn)程適當(dāng)在沒有資源可讀/寫時睡眠,有資源可讀/寫時喚醒。
select睡眠過程
select會循環(huán)遍歷它所監(jiān)測的fd_ set內(nèi)的所有文件描述符對應(yīng)的驅(qū)動程序的poll函數(shù)。
驅(qū)動程序提供的poll函數(shù)首先會將調(diào)用select的用戶進(jìn)程插入到該設(shè)備驅(qū)動對應(yīng)資源的等待隊列(如讀/寫等待隊列),然后返回一個bitmask告訴select當(dāng)前資源哪些可用。
當(dāng)select循環(huán)遍歷完所有fd_set內(nèi)指定的文件描述符對應(yīng)的poll函數(shù)后,如果沒有一個資源可用(即沒有一個文件可供操作),則select讓該進(jìn)程睡眠,一直等到有資源可用為止,進(jìn)程被喚醒(或者timeout)繼續(xù)往下執(zhí)行。
select喚醒過程
喚醒該進(jìn)程的過程通常是在所監(jiān)測文件的設(shè)備驅(qū)動內(nèi)實現(xiàn)的。
驅(qū)動程序維護(hù)了針對自身資源讀寫的等待隊列。當(dāng)設(shè)備驅(qū)動發(fā)現(xiàn)自身資源變?yōu)榭勺x寫并且有進(jìn)程睡眠在該資源的等待隊列上時,就會喚醒這個資源等待隊列上的進(jìn)程。
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
使用select函數(shù)寫的服務(wù)器代碼如下:
chata
1 #include "func.h"
2
3 int main(int argc,char* argv[])
4 {
5 if(argc!=3)
6 {
7 printf("error args
");
8 return -1;
9 }
10 int fdr,fdw;
11 fdr=open(argv[1],O_RDONLY);
12 fdw=open(argv[2],O_WRONLY);
13 printf("fdr=%d,fdw=%d
",fdr,fdw);
14 char buf[128]={0};
15 //當(dāng)管道里沒有數(shù)據(jù)時,read會阻塞
16 int ret;
17 fd_set rdset;
18 while(1)
19 {
20 FD_ZERO(&rdset);//清空集合
21 FD_SET(0,&rdset);
22 FD_SET(fdr,&rdset);
23 ret=select(fdr+1,&rdset,NULL,NULL,NULL);
24 if(ret>0)
25 {
26 if(FD_ISSET(fdr,&rdset))
27 {
28 memset(buf,0,sizeof(buf));
29 ret=read(fdr,buf,sizeof(buf));
30 if(0==ret)
31 {
32 printf("byebye
");
33 break;
34 }
35 printf("%s
",buf);
36 }
37 if(FD_ISSET(0,&rdset))
38 {
39 memset(buf,0,sizeof(buf));
40 ret=read(0,buf,sizeof(buf));
41 if(ret==0)
42 {
43 printf("byebye
");
44 break;
45 }
46 write(fdw,buf,strlen(buf)-1);
47 }
48 }
49 }
50 close(fdr);
51 close(fdw);
52 return 0;
53 }
chatb
1 #include "func.h"
2
3 int main(int argc,char* argv[])
4 {
5 if(argc!=3)
6 {
7 printf("error args
");
8 return -1;
9 }
10 int fdw,fdr,ret;
11 fdw=open(argv[1],O_WRONLY);
12 fdr=open(argv[2],O_RDONLY);
13 printf("fdw=%d,fdr=%d
",fdw,fdr);
14 char buf[128]={0};
15 fd_set rdset;
16 while(1)
17 {
18 FD_ZERO(&rdset);//清空集合
19 FD_SET(0,&rdset);
20 FD_SET(fdr,&rdset);
21 ret=select(fdr+1,&rdset,NULL,NULL,NULL);
22 if(ret>0)
23 {
24 if(FD_ISSET(fdr,&rdset))
25 {
26 memset(buf,0,sizeof(buf));
27 ret=read(fdr,buf,sizeof(buf));
28 if(0==ret)
29 {
30 printf("byebye
");
31 break;
32 }
33 printf("%s
",buf);
34 }
35 if(FD_ISSET(0,&rdset))
36 {
37 memset(buf,0,sizeof(buf));
38 ret=read(0,buf,sizeof(buf));
39 if(ret==0)
40 {
41 printf("byebye
");
42 break;
43 }
44 write(fdw,buf,strlen(buf)-1);
45 }
46 }
47 }
48 return 0;
49 }
當(dāng)數(shù)據(jù)量足夠多時,slect模型的資源消耗會大幅提升,poll模型和slect差不多,因此多數(shù)時候選擇epoll模型,可參見下一篇……
原博客來源:https://blog.csdn.net/lixungogogo/article/details/52219951
總結(jié)
以上是生活随笔為你收集整理的IO多路复用模型之select()函数详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习之卷积神经网络(12)深度残差网
- 下一篇: 宽带接入服务器的方式有哪些