目錄linux
epoll的行爲與poll(2)類似,監視多個有IO事件的文件描述符。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減小epoll_wait/epoll_pwait的調用,提升應用程序效率。數組
epoll_create(2)
建立一個新的epoll實例,並返回一個引用該實例的文件描述符
epoll_ctl(2)
建立epoll實例後,註冊對感興趣的文件描述符。當前註冊在epoll實例上的文件描述符集被稱爲epoll集合。
epoll_wait(2)
等待I/O事件,若是當前沒有事件可用,則阻塞調用線程。緩存
水平觸發
和邊沿觸發
epoll事件分佈接口既能夠表現爲邊緣觸發(ET),也能夠表現爲水平觸發(LT)。這兩種機制的區別
能夠這樣描述。假設有這種狀況發生:數據結構
若是使用邊緣觸發標誌將rfd文件描述符註冊到epoll接口,那麼第五步的epoll_wait(2)的調用可能會掛起,儘管文件輸入緩衝區仍然有1kb數據可讀;同時,遠程對等端可能正在指望基於它已發送的數據的應答。這樣作的緣由是,只有在被監視文件描述符上發生更改時,邊緣觸發模式才交付事件。所以,在步驟5中,調用者可能會以等待那些仍在輸入緩衝區中的數據的狀態下結束。
在上面的例子中,將生成rfd上的一個事件,由於在2中完成了寫入,而在3中使用了該事件。因爲在4中完成的讀操做不會消耗整個緩衝區數據,因此在步驟5中完成的對epoll_wait(2)的調用可能會無限期阻塞。併發
使用EPOLLET標誌的應用程序應該使用非阻塞文件描述符,以免在處理多個文件描述符時出現有阻塞的讀寫飢餓任務。建議使用epoll做爲邊沿觸發(EPOLLET)接口的方式以下:
i、 具備非阻塞文件描述符
ii、只有在read(2)或write(2)返回EAGAIN後纔等待事件。
相反,當EPOLLET做爲水平觸發接口使用時(默認狀況下,沒有指定EPOLLET), epoll只是一個更快的poll(2),而且能夠在使用後者的任何地方使用,由於它具備相同的語義。socket
select能打開的文件描述符有必定的限制,FD_SETSIZE設置,默認值是2048,有兩種解決方法,一、修改它的值,而後從新編譯內核。二、使用多進程加入要併發20w個客戶,那麼就要開100進程;epoll則沒有這個限制,它所支持的FD上限是最大能夠打開文件的數目,這個數字通常遠大於2048,舉個例子,在1GB內存的機器上大約是2萬左右,具體數目能夠cat /proc/sys/fs/file-max察看,通常來講這個數目和系統內存關係很大。函數
select/poll採用輪詢的方式掃描文件描述符,文件描述符數量越多,性能越差;內核 / 用戶空間內存拷貝問題,select/poll須要複製大量的句柄數據結構,產生巨大的開銷;select/poll返回的是含有整個句柄的數組,應用程序須要遍歷整個數組才能發現哪些句柄發生了事件,致使效率呈現線性降低。可是epoll不存在這個問題,它只會對"活躍"的socket進行操做---這是由於在內核實現中epoll是根據每一個fd上面的callback函數實現的。性能
select/poll的觸發方式是水平觸發,應用程序若是沒有完成對一個已經就緒的文件描述符進行IO操做,那麼以後每次select/poll調用仍是會將這些文件描述符通知進程。測試
select/poll和epoll都須要內核把FD消息通知給用戶空間,如何避免沒必要要的內存拷貝很重要,在這點上,select/poll須要複製整個FD數組,產生巨大的開銷;而epoll是經過內核於用戶空間mmap同一塊內存實現的。ui
int epoll_create(int size);
int epoll_create1(int flags);
建立一個epoll的句柄。自從linux2.6.8以後,size參數是被忽略的,更推薦使用epoll_crete1(0)來替代,flags能夠設置EPOLL_CLOEXEC標誌
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
該系統調用對文件描述符epfd引用的epoll(7)實例執行控制操做。它請求對目標文件描述符fd執行操做op。
epfd
: epoll_create建立的文件描述符.
op
:參數的有效參數爲:
EPOLL_CTL_ADD
在文件描述符epfd引用的epoll實例上註冊目標文件描述符fd。
EPOLL_CTL_MOD
修改已註冊描述符fd關聯的事件。
EPOLL_CTL_DEL
從epfd引用的epoll實例中刪除(取消註冊)目標文件描述符fd。該事件將被忽略,而且能夠是NULL
fd
:待監聽的fd
epoll_event
: 描述連接到文件描述符fd的對象,它的定義以下
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
events成員是由如下可用事件類型的零個或多個組合在一塊兒組成的位掩碼:
EPOLLIN :關聯的文件描述符能夠讀(包括對端SOCKET正常關閉);
EPOLLOUT:關聯的文件描述符能夠寫;
EPOLLPRI:關聯的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:關聯的文件描述符發生錯誤;
EPOLLHUP:關聯的文件描述符被掛斷;
EPOLLRDHUP:流套接字對等關閉鏈接,或半關閉寫。(當使用邊緣觸發監視時,此標記對於編寫簡單代碼檢測對等端是否關閉特別有用。2.6.17引入)
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的。
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個fd的話,須要再次把這個fd加入到EPOLL隊列裏
它們在內核頭文件裏的定義以下:
33 34 enum EPOLL_EVENTS 35 { 36 EPOLLIN = 0x001, 37 #define EPOLLIN EPOLLIN 38 EPOLLPRI = 0x002, 39 #define EPOLLPRI EPOLLPRI 40 EPOLLOUT = 0x004, 41 #define EPOLLOUT EPOLLOUT 42 EPOLLRDNORM = 0x040, 43 #define EPOLLRDNORM EPOLLRDNORM 44 EPOLLRDBAND = 0x080, 45 #define EPOLLRDBAND EPOLLRDBAND 46 EPOLLWRNORM = 0x100, 47 #define EPOLLWRNORM EPOLLWRNORM 48 EPOLLWRBAND = 0x200, 49 #define EPOLLWRBAND EPOLLWRBAND 50 EPOLLMSG = 0x400, 51 #define EPOLLMSG EPOLLMSG 52 EPOLLERR = 0x008, 53 #define EPOLLERR EPOLLERR 54 EPOLLHUP = 0x010, 55 #define EPOLLHUP EPOLLHUP 56 EPOLLRDHUP = 0x2000, 57 #define EPOLLRDHUP EPOLLRDHUP 58 EPOLLEXCLUSIVE = 1u << 28, 59 #define EPOLLEXCLUSIVE EPOLLEXCLUSIVE 60 EPOLLWAKEUP = 1u << 29, 61 #define EPOLLWAKEUP EPOLLWAKEUP 62 EPOLLONESHOT = 1u << 30, 63 #define EPOLLONESHOT EPOLLONESHOT 64 EPOLLET = 1u << 31 65 #define EPOLLET EPOLLET 66 }; 67 68 69 /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */ 70 #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */ 71 #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */ 72 #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);
等待在epoll監控的事件中已經發生的事件。
epfd
: epoll_create() 的返回值.
events
: 分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中(events不能夠是空指針,內核只負責把數據複製到這個events數組中,不會去幫助咱們在用戶態中分配內存)
maxevents
: maxevents告知內核這個events有多大,這個 maxevents的值大於0(不然Error :Invalid argument)
timeout
: 超時時間(毫秒,0會當即返回,-1將不肯定,也有說法說是永久阻塞)。若是函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時,它會阻塞直到
此程序簡單測試一下三個API,註冊標準輸出的描述符到epoll,監視標準輸出的讀事件,觸發後回顯一遍,quit退出程序.
#include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <errno.h> #include <sys/epoll.h> #include <vector> typedef std::vector<struct epoll_event> PollFdList; int main(int argc ,char **argv) { int fd; char buf[1024]; int i,res,real_read, maxfd; if((fd=open("/dev/stdin",O_RDONLY|O_NONBLOCK)) < 0) { fprintf(stderr,"open data1 error:%s",strerror(errno)); return 1; } PollFdList m_pollfds; int epfd = epoll_create1(EPOLL_CLOEXEC); struct epoll_event ev; ev.events = EPOLLIN | EPOLLPRI; ev.data.fd = fd; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); m_pollfds.resize(1024); while(1) { int ret = epoll_wait(epfd, m_pollfds.data(), m_pollfds.size(), 5000); if (ret < 0) { printf("ePoll error : %s\n",strerror(errno)); return 1; } if(ret == 0){ printf("ePoll timeout\n"); continue; } for (i = 0; i< 1; i++) { if (m_pollfds[i].events & EPOLLIN) { memset(buf, 0, 1024); real_read = read(m_pollfds[i].data.fd, buf, 1024); if (real_read < 0) { if (errno != EAGAIN) { printf("read eror : %s\n",strerror(errno)); continue; } } else if (!real_read) { close(m_pollfds[i].data.fd); m_pollfds[i].events = 0; } else { if (i == 0) { buf[real_read] = '\0'; printf("%s", buf); if ((buf[0] == 'q') || (buf[0] == 'Q')) { printf("quit\n"); return 1; } } else { buf[real_read] = '\0'; printf("%s", buf); } } } } } exit(0); }
./test hello hello hello epoll hello epoll ePoll timeout quit quit quit