歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> Unix基礎知識

UNIX環境高級編程:I/O多路轉接(select、pselect和poll)

I/O多路轉接:先構造一張有關描述符的列表,然後調用一個函數,直到這些描述符中的一個已准備好進行I/O時,該函數才返回。在返回時,它告訴進程哪些描述符已准備好可以進行I/O。

poll、pselect和select這三個函數使我們能夠執行I/O多路轉接。
一、select函數

在所有依從POSIX的平台上,select函數使我們可以執行I/O多路轉接。傳向select的參數告訴內核:

我們所關心的描述符。

對於每個描述符我們所關心的狀態。(是否讀一個給定的描述符?是否想寫一個給定的描述符?是否關心一個描述符異常狀態?)

願意等待多長時間(可以永遠等待,等待一個固定量時間或完全不等待)。

從select返回時,內核告訴我們:

已准備好的描述符的數量。

對於讀、寫或異常這三個狀態中的每一個,哪些描述符已准備好。

#include <sys/select.h>  
int select(int maxfdp1,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *tvptr);//返回值:准備就緒的描述符數,若超時則返回0,若出錯則返回-1

先說明最後一個參數,它指定願意等待的時間:

struct timeval{  
  long tv_sec; //seconds  
  long tv_usec;//and microseconds  
}

有三種情況:

tvptr == NULL

永遠等待。如果捕捉到一個信號則中斷此無限期等待。當所指定的描述符中的一個已准備好或捕捉到一個信號則返回。如果捕捉到一個信號,則select返回-1,errno設置為EINTR

tvptr->tv_sec == 0 && tvptr->tv_usec == 0

完全不等待。測試所有指定的描述符並立即返回。

tvptr->tv_sec != 0 || tvptr->tv_usec != 0

等待指定的秒數和微妙數。當指定的描述符之一已准備好或當指定的時間值已經超過時立即返回。如果在超時時還沒有一個描述符准備好,則返回值是0.與第一種情況一樣,這種等待可被捕捉到的信號中斷。

中間三個參數readfds、writefds和exceptfds是指向描述符集的指針。這三個描述符集說明了我們關心的可讀、可寫或處於異常條件的各個描述符。每個描述符集存放在一個fd_set數據類型中。這種數據類型為每一可能的描述符保持了一位。

對fd_set數據類型可以進行的處理是:分配一個這種類型的變量;將這種類型的一個變量值賦予同類型的另一個變量;或對於這種類型的變量使用下列四個函數中的一個。

#include <sys/select.>  
int FD_ISSET(int fd,fd_set *fdset);//返回值:若fd在描述符集中則返回非0值,否則返回0  
void FD_CLR(int fd,fd_set *fdset);  
void FD_SET(int fd,fd_set *fdset);  
void FD_ZERO(fd_set *fdset);

調用FD_ZERO將一個指定的fd_set變量的所有位設置為0.

調用FD_SET設置一個fd_set變量的指定位。

調用FD_CLR則將一指定位清除。

調用FD_ISSET測試一指定位是否設置。

select的中間三個參數(指向描述符集的指針)中的任意一個或全部都可以是空指針,這表示對相應狀態並不關心。如果所有三個指針都是空指針,則select提供了較sleep更精確的計時器。

select的第一個參數maxfdp1的意思是“最大描述符加1”。在三個描述符集中找出最大描述符值,然後加1,這就是第一個參數。也可以將第一個參數設置為FD_SETSIZE,這是<sys/select.h>中的一個常量,它說明了最大的描述符(經常是1024)。如果將第一個參數設置為我們關注的最大描述符編號值加1,內核就只需在此范圍內尋找打開的位,而不必在三個描述符集中的數百位內搜索。

例如,若編寫下列代碼:

fd_set readset,writeset;  
FD_SERO(&readset);  
FD_ZERO(&writeset);  
FD_SET(0,&readset);  
FD_SET(3,&readset);  
FD_SET(1,&writeset);  
FD_SET(2,&writeset);  
select(4,readset,&writeset,NULL,NULL);

因為描述符編號從0開始,所以要在最大描述符編號值上加1。第一個參數實際上是要檢查的描述符(從描述符0開始)。

select有三個可能的返回值:

返回-1表示出錯。出錯是有可能的,例如在所指定的描述符都沒有准備好時捕捉到一個信號。在此種情況下,將不修改其中任何描述符集。(即原先描述符值1的還是保持1,不變)

返回0表示沒有描述符准備好。若指定的描述符都沒有准備好,而且指定的時間已經超時,則發生這種情況。此時,所有描述符集皆被清0。

查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/

正返回值表示已經准備好的描述符數,該值是三個描述符集中已經准備好的描述符數之和,所以如果同意描述符已准備好讀和寫,那麼在返回值中將計2。在這種情況下,三個描述符集中仍舊打開的位對應於已准備好的描述符,其他沒有准備好的清0。

如果在一個描述符上碰到了文件結尾處,則select認為該描述符是可讀的。然後調用read,它返回0,這是UNIX系統指示到達文件結尾處的方法。(很多人錯誤的認為,當到達文件結尾處時,select會指示一個異常狀態)。

二、pselect函數

#include <sys/select.h>  
int pselect(int maxfdp1,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,const struct timespec *tsptr,const sigset_t *sigmask);//返回值:准備就緒的描述符數,若超時則返回0,若出錯則返回-1

除以下幾點,pselect與select相同:

select的超時值用timeval結構指定,但pselect使用timespec,timespec結構以秒和納秒表示超時值,而非秒和微妙。如果平台支持這樣精細的粒度,那麼timespec就提供了更精准的超時時間。

對於pselect可使用一可選擇的信號屏蔽字。若sigmask為空,那麼在與信號有關的方面,pselect的運行狀況和select相同。否則,sigmask指向一信號屏蔽字,在調用pselect時,以原子操作的方式安裝該信號屏蔽字。在返回時恢復以前的信號屏蔽字。

三、poll函數

#include <poll.h>  
int poll(struct pollfd fdarray[],nfds_t nfds,int timeout);

與select不同,poll不是為每個狀態(可讀性、可寫性和異常狀態)構造一個描述符集,而是構造一個pollfd結構數組,每個數組元素指定一個描述符編號以及對其關心的狀態。

struct pollfd{  
  int    fd; //file descriptor to check,or < 0 ignore  
  short  events; //events of interest on fd  
  short  revents; //events that occurred on fd  
}

fdarray數組中的元素數由nfds說明。

應將每個數組元素的events成員設置為下圖。通過這些值告訴內核我們對該描述符關心的是什麼。返回時,內核設置revents成員,以說明對於該描述符已經發生了什麼事件。(注意:poll沒有更改events成員,這與select不同,select修改其參數以指示哪一個描述符已准備好了)

最後三行是由內核在返回時設置的。即使在events字段中沒有指定這三個值,如果相應條件發生,則在revents中也返回它們。

poll的最後一個參數說明我們願意等待時間。有三種不同的情形:

timeout == -1 永遠等待。當所指定的描述符中的一個已准備好,或捕捉到一個信號時則返回。如果捕捉到一個信號,則poll返回-1,errno設置為EINTR。

timeout == 0  不等待。

timeout > 0   等待timeout毫秒。當指定的描述符之一已准備好,或指定的時間值已超過時立即返回。如果已超時,但是還沒有一個描述符准備好,則返回值是0。

Copyright © Linux教程網 All Rights Reserved