linux中IO複用(select,poll,epoll)

轉自http://blog.csdn.net/dengjin20104042056/article/details/52269583

select(),poll(),epoll()都是I/O多路複用的機制。I/O多路複用通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒,就是這個文件描述符進行讀寫操作之前),能夠通知程序進行相應的讀寫操作。但select(),poll(),epoll()本質上都是同步I/O

1.select

select系統調用是用來讓我們的程序監視多個文件句柄(file descriptor)的狀態變化的。程序會停在select這裏等待,直到被監視的文件句柄有某一個或多個發生了狀態改變。

select函數原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函數的最後一個參數timeout顯然是一個超時時間值,其類型是struct timeval *,即一個struct timeval結構的變量的指針,所以我們在程序裏要申明一個struct timeval tv;然後把變量tv的地址&tv傳遞給select函數。struct timeval結構如下:


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

第2、3、4三個參數是一樣的類型: fd_set *,即我們在程序裏要申明幾個fd_set類型的變量,比如rdfds, wtfds, exfds,然後把這個變量的地址&rdfds, &wtfds, &exfds 傳遞給select函數。這三個參數都是一個句柄的集合,第一個rdfds是用來保存這樣的句柄的:當句柄的狀態變成可讀的時系統就會告訴select函數返回,同理第二個wtfds是指有句柄狀態變成可寫的時系統就會告訴select函數返回,同理第三個參數exfds是特殊情況,即句柄上有特殊情況發生時系統會告訴select函數返回。特殊情況比如對方通過一個socket句柄發來了緊急數據

select()目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點。

select()的缺點在於:

1)每次調用 select(),都需要把 fd 集合從用戶態拷貝到內核態,這個開銷在 fd 很多時會很大,同時每次調用 select() 都需要在內核遍歷傳遞進來的所有 fd,這個開銷在 fd 很多時也很大。

2)單個進程能夠監視的文件描述符的數量存在最大限制,在 Linux 上一般爲 1024,可以通過修改宏定義甚至重新編譯內核的方式提升這一限制,但是這樣也會造成效率的降低。


2.poll

select() 和 poll() 系統調用的本質一樣,前者在 BSD UNIX 中引入的,後者在 System V 中引入的。poll() 的機制與 select() 類似,與 select() 在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是 poll() 沒有最大文件描述符數量的限制(但是數量過大後性能也是會下降)poll() 和 select() 同樣存在一個缺點就是,包含大量文件描述符的數組被整體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。

poll() 的實現和 select() 非常相似,只是描述 fd 集合的方式不同,poll() 使用 pollfd 結構而不是 select() 的 fd_set 結構,其他的都差不多。

3.epoll

epoll 是在 2.6 內核中提出的,是之前的 select() 和 poll() 的增強版本。相對於 select() 和 poll() 來說,epoll 更加靈活,沒有描述符限制。

epoll包含以下三個接口

(1)

int epoll_create(int size);

功能:

該函數生成一個 epoll 專用的文件描述符(創建一個 epoll 的句柄)。

(2)

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:

epoll 的事件註冊函數,它不同於 select() 是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。

(3)

int epoll_wait( int epfd, struct epoll_event * events, int maxevents, int timeout );

功能:

等待事件的產生,收集在 epoll 監控的事件中已經發送的事件,類似於 select() 調用。

epoll 對文件描述符的操作有兩種模式:LT(level trigger)和 ET(edge trigger)。LT 模式是默認模式LT 模式與 ET 模式的區別如下

LT 模式:當 epoll_wait 檢測到描述符事件發生並將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用 epoll_wait 時,會再次響應應用程序並通知此事件。

ET 模式:當 epoll_wait 檢測到描述符事件發生並將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用 epoll_wait 時,不會再次響應應用程序並通知此事件。

ET 模式在很大程度上減少了 epoll 事件被重複觸發的次數,因此效率要比 LT 模式高。epoll 工作在 ET 模式的時候,必須使用非阻塞套接口,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

在 select/poll中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描,而 epoll() 事先通過 epoll_ctl() 來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似 callback 的回調機制(軟件中斷 ),迅速激活這個文件描述符,當進程調用 epoll_wait() 時便得到通知。


epoll 的優點主要是一下幾個方面:

1)監視的描述符數量不受限制,它所支持的 FD 上限是最大可以打開文件的數目,這個數字一般遠大於 2048,舉個例子,在 1GB 內存的機器上大約是 10 萬左右,具體數目可以 cat /proc/sys/fs/file-max 察看,一般來說這個數目和系統內存關係很大。select() 的最大缺點就是進程打開的 fd 是有數量限制的。這對於連接數量比較大的服務器來說根本不能滿足。雖然也可以選擇多進程的解決方案( Apache 就是這樣實現的),不過雖然 Linux 上面創建進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,所以也不是一種完美的方案。


2)I/O 的效率不會隨着監視 fd 的數量的增長而下降。select(),poll() 實現需要自己不斷輪詢所有 fd 集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而 epoll 其實也需要調用 epoll_wait() 不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒 fd 放入就緒鏈表中,並喚醒在 epoll_wait() 中進入睡眠的進程。雖然都要睡眠和交替,但是 select() 和 poll() 在“醒着”的時候要遍歷整個 fd 集合,而 epoll 在“醒着”的時候只要判斷一下就緒鏈表是否爲空就行了,這節省了大量的 CPU 時間。這就是回調機制帶來的性能提升


3)select(),poll() 每次調用都要把 fd 集合從用戶態往內核態拷貝一次,而 epoll 只要一次拷貝,這也能節省不少的開銷。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章