I/O複用中select函數簡介

                                            I/O複用之select函數:html

select函數:該函數容許進程指示內核等待多個事件中的任何一個發生,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒它。linux

在紅帽linux下用manpage看select,select給咱們的形式以下網絡

int select{int nfds, fd_set  *readfds, fd_set  *writefds,fd_set *exceptfds, struct timeval *timeout};socket

返回值:就緒描述符的數目,超時返回0,出錯返回-1。tcp

(1)第一個參數nfds指定測試的描述字個數,它的值是待測試的最大描述字加1。函數

(2)中間的三個參數readfds,writefds和exceptfds指定咱們要內核測試讀,寫和異常的條件的描述字。若是對於其中的一個不感興趣,就能夠把它設置爲空指針。struct fd_set能夠理解爲一個集合,這個集合中存放的是文件描述符,能夠經過四個宏來進行設置:測試

void  FD_ZERO(fd_set  *fdset);             //清空集合spa

void  FD_SET(int fd,fd_set  *fdset);      //將一個給定的文件描述符加入集合中.net

void  FD_CLR(int fd,fd_set  *fdset);     //將一個給定的文件描述符從集合中刪除unix

int     FD_ISSET(int fd,fd_set  *fdset); //檢查集合中指定的文件描述符是否能夠寫

目前支持的異常條件只有兩個:

1.某個套接字的待外數據的到達;

2.某個已置爲分組模式的僞終端存在可從其主端讀取的控制狀態信息。

(3) 其中timeval是一個結構體,用於指定這段時間的秒數和微妙數,manpage中顯示以下:

struct timeval{

long   tv_sec;                      /*seconds*/

long   tv_usec;                    /*microseconds*/

}

and

struct timespec{

long  tv_sec;                       /*seconds*/

long  tv_nsec;                     /*nanoseconds*/

}

timeval這個參素有三種可能:

1.永遠等待下去。僅在有一個描述符準備好I/O時才返回,爲此,咱們把該參數設置爲空指針,即timeout == NULL。也就是說,若是捕獲到一個信號則中斷此無限期等待,當所指定的描述符中的一個已準備好或捕捉到一個信號則返回。若是捕捉到一個信號,則select返回-1,errno設置爲EINTR。

2.等待固定的時間。在有一個描述字準備好I/O時返回,但不超過timeout參數所指的timeval結構中所指定的秒數和微妙數。即timeout→tv_sec != 0 || timeout→tv_usec != 0。也就是說,當指定的描述符之一已準備好,或當指定的時間值已經超過期當即返回。若是在超市到期時尚未一個描述符準備好,則返回值是0。與第一種狀況同樣,這種等待可能被捕捉到的信號中斷。

3.根本不等待。檢查描述字後當即返回,這稱爲輪詢。爲了實現這一點,參數timeout必須指向結構timeval,且定時器的值(由結構timeval指定的秒數和微秒數)必須爲0。即timeout→tv_sec == 0 && timeout→tv_usec == 0。測試全部指定的描述符並當即返回。這是輪詢系統找到多個描述符狀態而不阻塞select函數的方法。

在前二者狀況的等待中,若是進程捕獲了一個信號並從信號處理程序返回,那麼等待通常是被中斷的。

雖然結構體timeval爲咱們指定了一個微秒級的分辨率,但內核支持的分辨率卻要粗糙的多。參數timeout前的限定詞const表示它返回時不會被select修改。


select函數的調用過程

1.使用copy_from_user從用戶空間拷貝fd_set到內核空間

2.註冊回調函數_pollwait

3.遍歷全部fd,調用其對應的poll方法(對於socket,這個poll方法是sock_poll,sock_poll根據狀況會調用tcp_poll,udp_poll或者datagram_poll)

4.以tcp爲例,其核心實現就是_pollwait,也就是上面註冊的回調函數。

5._pollwait的主要工做就是把current(當前進程)掛到設備的等待隊列中,不一樣的設備由不一樣的等待隊列,對於tcp_poll來講,其等待隊列是sk→sk_sleep,(注意把進程掛到等待隊列中並不表明進程已經睡眠了)。在設別接受一條消息(網絡設備)或填寫文件數據(磁盤設備)後,會喚醒設備等待隊列上睡眠的進程,這是current就被喚醒了

6.poll方法返回時會返回一個描述讀寫操做是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。

7.若是遍歷完全部的fd,尚未返回一個能夠讀寫的mask掩碼,則會調用schedule_timeout,schedule_timeout是調用select進程(也就是current)進入睡眠,當設備驅動發生自身資源可讀寫後,會喚醒其等待隊列上睡眠的進程,若是超過必定的超時時間(schedule_timeout指定),仍是沒有人喚醒,則調用select的進程會從新被喚醒得到CPU,進而從新遍歷fd,判斷有沒有就緒的fd。

8.把fd_set從內核空間拷貝到用戶空間。


select的幾大缺點

1.每次調用select,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大。

2.同時每次使用select都須要在內核遍歷傳遞進來的全部的fd,這個開銷在fd不少時也很大。

3.select支持的文件描述符數量態小,默認爲1024,這裏所說的小僅僅指的時select與poll和epoll相比,

在限定描述符的數量上,1024個描述符其實已經比較大了,可是epoll不限制描述符的數量。


select的三個可能的返回值

1.返回-1表示出錯,這個是可能發生的,例如,在所指定的描述符一個都沒有準備好是捕捉到一個信號。在此種狀況下,一個描述符集都不修改。

2.返回值爲0表示沒有描述符準備好,若指定的描述符一個都沒有準備好,指定的時間就過了,那麼就會發生這種狀況,此時,全部的描述符集都置爲0.

3.一個正返回值說明了已經準備好的的描述符數。該值是3個描述符集中已準備好的描述符數之和,因此若是同一描述符已經準備好讀和寫,那麼在返回值中會對其計數兩次。

準備好的描述符的意思爲:

1.若對讀集中的一個描述符進行的read操做不會阻塞,則認爲此描述符是準備好的。

2.若對寫集中的一個描述符進行的write操做不會阻塞,則認爲此描述符是準備好的。

3.若對異常條件集中的一個描述符有一個未決異常條件,則認爲此描述符是準備好的。如今,異常條件包括:在網絡鏈接上到達帶外的數據,或者在處於數據包模式的僞終端上發生了某些條件。

4.對於讀,寫和異常條件,普通文件的文件描述符老是返回準備好。

一個描述符阻塞是否並不影響select是否阻塞,理解這一點很重要。


如下內容爲借鑑網上的文章:

http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml

select須要驅動程序的支持,驅動程序實現fops內的poll函數。select經過每一個設備文件對應的poll函數提供的信息判斷當前是否有資源 可用(如可讀或寫),若是有的話則返回可用資源的文件描述符個數,沒有的話則睡眠,等待有資源變爲可用時再被喚醒繼續執行。下面咱們分兩個過程來分析select:1. select的睡眠過程支持阻塞操做的設備驅動一般會實現一組自身的等待隊列如讀/寫等待隊列用於支持上層(用戶層)所需的BLOCK或NONBLOCK操做。當應用程序經過設 備驅動訪問該設備時(默認爲BLOCK操做),若該設備當前沒有數據可讀或寫,則將該用戶進程插入到該設備驅動對應的讀/寫等待隊列讓其睡眠一段時間,等 到有數據可讀/寫時再將該進程喚醒。select就是巧妙的利用等待隊列機制讓用戶進程適當在沒有資源可讀/寫時睡眠,有資源可讀/寫時喚醒。下面咱們看看select睡眠的詳細過程。select會循環遍歷它所監測的fd_set(一組文件描述符(fd)的集合)內的全部文件描述符對應的驅動程序的poll函數。驅動程序提供的 poll函數首先會將調用select的用戶進程插入到該設備驅動對應資源的等待隊列(如讀/寫等待隊列),而後返回一個bitmask告訴select 當前資源哪些可用。當select循環遍歷完全部fd_set內指定的文件描述符對應的poll函數後,若是沒有一個資源可用(即沒有一個文件可供操 做),則select讓該進程睡眠,一直等到有資源可用爲止,進程被喚醒(或者timeout)繼續往下執行。

相關文章
相關標籤/搜索