linux 提供了select、poll和epoll三種接口來實現多路IO複用。下面總結下這三種接口。html
該函數容許進程指示內核等待多個事件中的任何一個發生,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒它。linux
函數接口:數組
1: #include <sys/select.h>
2: #include <sys/time.h>
3:
4: int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
5: const struct timeval* timeout);
參數說明:安全
1: struct timeval {
2: long tv_set; // 秒
3: long tv_usec; // 微秒 10^-6
4: }
返回值:服務器
有就緒的描述符,返回值爲就緒的描述符的個數(早期的select若是返回時多個描述符集合的同一位爲1,即某個描述符又可讀有可寫,那麼在函數返回值時只計一次,而如今的版本則修正了該問題,按照事件分開計算),超時返回0, 出錯則返回-1.網絡
select的幾點說明:數據結構
1: void FD_ZERO(fd_set *fdset) //清空集合
2: void FD_SET(int fd, fd_set *fdset) // 設置fdset中fd對應的位
3: void FD_CLR(int fd, fd_set *fdset) // 清除fdset中fd對應的位
4: void FD_ISSET(int fd, fd_set* fdset) // 檢測fdset中fd對應的位是否設置。
poll起源於SVR3, 最初侷限於流設備,SVR4取消了這種限制,容許poll工做在任何描述符上。它提供了和select相似的功能。併發
函數接口:app
1: #include <poll.h>
2:
3: int poll(struct pollfd* fdarray, unsigned long nfds, int timeout);
4:
5: struct pollfd {
6: int fd; // 須要檢測的文件描述符
7: short event; // fd上關心的事件
8: short revent; // fd上發生的時間,即返回值
9: };
參數說明:less
timeout的值 | 說明 |
INFTIM(常被定義爲一個負值) | 永遠等待 |
0 | 當即返回,不阻塞進程 |
>0 | 等待指定數目的毫秒數 |
返回值:
發生錯誤的時候,返回-1, 定時器到時以前沒有任何描述符就緒,返回0. 不然返回就緒描述符的個數,即revents成員非0的描述符個數。
poll函數的幾點說明:
poll的原理:
poll是一個系統調用,其內核入口函數爲sys_poll,sys_poll幾乎不作任何處理直接調用do_sys_poll,do_sys_poll的執行過程能夠分爲三個部分:
1,將用戶傳入的pollfd數組拷貝到內核空間,由於拷貝操做和數組長度相關,時間上這是一個O(n)操做,這一步的代碼在do_sys_poll中包括從函數開始到調用do_poll前的部分。
2,查詢每一個文件描述符對應設備的狀態,若是該設備還沒有就緒,則在該設備的等待隊列中加入一項並繼續查詢下一設備的狀態。查詢完全部設備後若是沒有一個設備就緒,這時則須要掛起當前進程等待,直到設備就緒或者超時,掛起操做是經過調用schedule_timeout執行的。設備就緒後進程被通知繼續運行,這時再次遍歷全部設備,以查找就緒設備。這一步由於兩次遍歷全部設備,時間複雜度也是O(n),這裏面不包括等待時間。相關代碼在do_poll函數中。
3,將得到的數據傳送到用戶空間並執行釋放內存和剝離等待隊列等善後工做,向用戶空間拷貝數據與剝離等待隊列等操做的的時間複雜度一樣是O(n),具體代碼包括do_sys_poll函數中調用do_poll後到結束的部分。
epoll是linux下多路IO複用select/poll的加強版本,它能顯著提供程序在大量併發鏈接中只有少許活躍狀況下的系統CPU利用率。
函數結構:
它有三個主要結構,分別爲epoll_create,epoll_ctl,epoll_wait。
1: #include <sys/epoll.h>
2:
3: int epoll_create(int size)
該函數用於建立一個epoll的文件描述符,
參數說明:
建立一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大(能夠不是監聽的總數,該參數用來提示內存分配空間的)。這個參數不一樣於select()中的第一個參數,給出最大監聽的fd+1的值。須要注意的是,當建立好epoll句柄後,它就是會佔用一個fd值,在linux下若是查看/proc/進程id/fd/,是可以看到這個fd的,因此在使用完epoll後,必須調用close()關閉,不然可能致使fd被耗盡。
返回值:
成功的返回文件描述符,出錯返回-1,並設置errno
錯誤類型:
EINVAL: size不是正數
ENFILE: 文件描述符達到系統的文件描述符限制
ENOMEM: 沒有足夠的內存建立內核對象。
1: #include <sys/epoll.h>
2:
3: int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
4:
5: typedef union epoll_data {
6: void * ptr;
7: int fd;
8: _uint32_t u32;
9: _uint64_t u64;
10: }epoll_data_t;
11:
12: struct epoll_event {
13: _uint32 events; // epoll事件
14: epoll_data_t data; // 用戶變量。
15: };
函數說明:
epoll的事件註冊函數,它不一樣與select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。第一個參數是epoll_create()的返回值,第二個參數表示動做,用三個宏來表示:
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
events能夠是如下幾個宏的集合:
EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符能夠寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的。
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
返回值:
成功返回0, 失敗返回-1, 並設置errno
錯誤類型:
EBADF: epfd不是一個有效的描述符。
EEXIST: op爲EPOLL_CTL_ADD,而且提供的描述符fd已經在epfd中。此時應該用EPOLL_CTL_MOD
EINVAL: epfd不是一個epoll文件描述符,或者fd和epfd同樣,揮着請求的操做op不是一個有效的操做。
ENOENT: op是EPOLL_CTL_MOD或者EPOLL_CTL_DEL,而且fd不在epfd中。
ENOMEM: 沒有足夠的內存區執行相應的op操做。
EPERM: epoll不支持目標的文件描述符fd
1: #include <sys/epoll.h>
2:
3: int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
函數說明:
epfd是經過epoll_create常見的描述符,events是接受返回事件的結構,maxevents是數組的最大值,timeout是設置超時時間的,單位是毫秒。
等待事件的產生,相似於select()調用。參數events用來從內核獲得事件的集合,maxevents告以內核這個events有多大,這個 maxevents的值必須大於0,maxevents 是epoll_wait能夠處理的鏈接事件的最大限度值,這個值通常要小於或等於epoll_create的那個size,固然若是設置成比size還大 的話也無所謂,size是epoll總體能夠監聽的最大fd數量。maxevents的意義是防止epoll的API在填寫你傳進去的指針events的 時候,超過指針指向的內存的大小從而致使內存溢出。參數timeout是超時時間(毫秒,0會當即返回,-1將不肯定,也有說法說是永久阻塞)。該函數返回須要處理的事件數目,如返回0表示已超時。
當成功返回時,每一個epoll_event結構中將包含epoll_ct中的用戶數據。
返回值:
成功返回準備好的描述符數,超時仍沒有描述符準備好返回0, 出錯返回-1,並設置errno
錯誤類型:
EBADF: epfd不是一個有效的文件描述符
EFAULT: 指向events的內存沒有寫權限
EINTR: 調用在IO 準備好和超時以前被信號中斷
EINVAL: epfd不是有個epoll文件描述符,或者maxevents小於等於0
epoll的幾點說明:
ET模式僅當狀態發生變化的時候纔得到通知(只告訴進程哪些文件描述符剛剛變爲就緒狀態,),這裏所謂的狀態的變化並不包括緩衝區中還有未處理的數據,也就是說,若是要採用ET模式,須要一直read/write直到出錯爲止,不少人反映爲何採用ET模式只接收了一部分數據就再也得不到通知了,大多由於這樣;而LT模式是隻要有數據沒有處理就會一直通知下去的.
接下來分析epoll,與poll/select不一樣,epoll再也不是一個單獨的系統調用,而是由epoll_create/epoll_ctl/epoll_wait三個系統調用組成,後面將會看到這樣作的好處。
先來看sys_epoll_create(epoll_create對應的內核函數),這個函數主要是作一些準備工做,好比建立數據結構,初始化數據並最終返回一個文件描述符(表示新建立的虛擬epoll文件),這個操做能夠認爲是一個固定時間的操做。
epoll是作爲一個虛擬文件系統來實現的,這樣作至少有如下兩個好處:
1,能夠在內核裏維護一些信息,這些信息在屢次epoll_wait間是保持的,好比全部受監控的文件描述符。
2, epoll自己也能夠被poll/epoll;
具體epoll的虛擬文件系統的實現和性能分析無關,再也不贅述。
在sys_epoll_create中還能看到一個細節,就是epoll_create的參數size在現階段是沒有意義的,只要大於零就行。
接着是sys_epoll_ctl(epoll_ctl對應的內核函數),須要明確的是每次調用sys_epoll_ctl只處理一個文件描述符,這裏主要描述當op爲EPOLL_CTL_ADD時的執行過程,sys_epoll_ctl作一些安全性檢查後進入ep_insert,ep_insert裏將 ep_poll_callback作爲回掉函數加入設備的等待隊列(假定這時設備還沒有就緒),因爲每次poll_ctl只操做一個文件描述符,所以也能夠認爲這是一個O(1)操做
ep_poll_callback函數很關鍵,它在所等待的設備就緒後被系統回掉,執行兩個操做:
1,將就緒設備加入就緒隊列,這一步避免了像poll那樣在設備就緒後再次輪詢全部設備找就緒者,下降了時間複雜度,由O(n)到O(1);
2,喚醒虛擬的epoll文件;
最後是sys_epoll_wait,這裏實際執行操做的是ep_poll函數。該函數等待將進程自身插入虛擬epoll文件的等待隊列,直到被喚醒(見上面ep_poll_callback函數描述),最後執行ep_events_transfer將結果拷貝到用戶空間。因爲只拷貝就緒設備信息,因此這裏的拷貝是一個O(1)操做。
還有一個讓人關心的問題就是epoll對EPOLLET的處理,即邊沿觸發的處理,粗略看代碼就是把一部分水平觸發模式下內核作的工做交給用戶來處理,直覺上不會對性能有太大影響,感興趣的朋友歡迎討論。
POLL/EPOLL對比:
表面上poll的過程能夠看做是由一次epoll_create/若干次epoll_ctl/一次epoll_wait/一次close等系統調用構成,實際上epoll將poll分紅若干部分實現的緣由正是由於服務器軟件中使用poll的特色(好比Web服務器):
1,須要同時poll大量文件描述符;
2,每次poll完成後就緒的文件描述符只佔全部被poll的描述符的不多一部分。
3,先後屢次poll調用對文件描述符數組(ufds)的修改只是很小;
傳統的poll函數至關於每次調用都重起爐竈,從用戶空間完整讀入ufds,完成後再次徹底拷貝到用戶空間,另外每次poll都須要對全部設備作至少作一次加入和刪除等待隊列操做,這些都是低效的緣由。
epoll將以上狀況都細化考慮,不須要每次都完整讀入輸出ufds,只需使用epoll_ctl調整其中一小部分,不須要每次epoll_wait都執行一次加入刪除等待隊列操做,另外改進後的機制使的沒必要在某個設備就緒後搜索整個設備數組進行查找,這些都能提升效率。另外最明顯的一點,從用戶的使用來講,使用epoll沒必要每次都輪詢全部返回結果已找出其中的就緒部分,O(n)變O(1),對性能也提升很多。
此外這裏還發現一點,是否是將epoll_ctl改爲一次能夠處理多個fd(像semctl那樣)會提升些許性能呢?特別是在假設系統調用比較耗時的基礎上。不過關於系統調用的耗時問題還會在之後分析。
refer:http://kaiyuan.blog.51cto.com/930309/341121
http://www.360doc.com/content/09/0727/15/1894_4486873.shtml
http://blog.csdn.net/ljx0305/article/details/4065058
http://blog.endlesscode.com/2010/03/27/select-poll-epoll-intro/