Event Poll epoll 詳解

因爲poll()和select()的侷限,2.6內核引入了event poll(epoll)機制。雖然稍微複雜,可是epoll解決了它們共有的基本性能問題,並增長了一些新的特性。c++

poll()和select()每次調用都須要全部被監聽的文件描述符。內核必須遍歷全部被監視的文件描述符。當這個表變得很大時,成千上百的文件描述符,每次調用時的遍歷就成爲了明顯的瓶頸。編程

一、建立一個新的epoll實例

使用epoll_create()或者epoll_cerate1()建立一個epoll上下文。這裏epoll_cerate1()是epoll_cerate()的擴展版本。數組

#include <sys/epoll.h>
int epoll_create (int size)

調用成功後,epoll_create()建立一個epoll實例,返回與該實例關聯的文件描述符。這個文件描述符和真正的文件沒有關係,僅僅是爲了後續調用使用epoll而建立的。size參數告訴內核須要監聽的文件描述符數目,但不是最大值。傳遞一個適當的近似值會帶來性能的提高,但不須要給出確切的數字。出錯時,返回-1,設置errno爲下列值之一:socket

EINVAL   size不是正數函數

ENFILE    系統達到打開文件數的上限性能

ENOMEN    沒有足夠內存完成該次操做。pwa

標準調用以下:進程

int epfd;
epfd = epoll_create (100); 
if (epfd <0 )
	perror("epoll_create");

epoll_create返回的文件描述符須要用close()關閉。事件

二、控制 epoll

epoll_ctl 能夠向指定的epoll上下文加入或刪除文件描述符:內存

#include <sys/epoll.h>
int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
頭文件<sys/epoll.h>中定義了epoll event結構體
struct epoll_event {
	_u32 events;
	union {
	void * ptr;
	int fd;
	_u32 u32;
	_u64 u64;
	}data;
};
epoll_ctl()成功調用將關聯epoll實例和epfd。參數op指定對fd要進行的操做。event參數描述epoll更具體的行爲
如下是參數op的有效值:
EPOLL_CTL_ADD 把fd指定的文件添加到epfd指定的epoll實例監聽集中,監聽event中定義的事件。
EPOLL_CTL_DEL 把fd指定的文件從epfd指定的epoll監聽集中刪除。
EPOLL_CTL_MOD 使用event改變在已有fd上的監聽行爲。
epoll_event結構體中的event參數列出了在給定文件描述符上監聽的事件。多個事件可使用位或運算同時指定。如下是有效值:
EPOLLERR 文件出錯。即便不設置這個標誌,這個事件也是被監聽的。
EPOLLET 使用邊沿觸發。默認是水平觸發。
EPOLLHUP 文件被掛起。即便不設置這個標誌,這個事件也是被監聽的。
EPOLLIN 文件未阻塞,可讀。
EPOLLONESHOT 在一次事件產生被處理以後,文件不在被監聽。必須再也不被監聽。必須使用EPOLL_CTL_MOD指定新的事件,以便從新監聽文件。
EPOLLOUT 文件未阻塞,可寫。
EPOLLPRI 高優先級的帶外數據可讀。
event_poll中的data字段由用戶使用。確認監聽事件後,data會被返回給用戶。一般將event.data.fd設定爲fd,這樣就能夠知道那個文件描述符觸發事件。
成功後,epoll_ctl()返回0.失敗返回-1,並設置errno爲下列值:
EBADF epfd不是一個有效的epoll實例,或者fd不是有效文件描述符。
EEXIST op爲EPOLL_CTL_ADD,可是fd已經與epfd關聯。
EINVAL epfd不是一個epoll實例,epfd和fd相同,或者op無效。
ENOENT op是EPOLL_CTL_MOD或者是EPOLL_CTL_DEL,可是fd沒有與epfd關聯。
ENOMEN 沒有足夠內存完成進程的請求。
EPERM fd不支持epoll。
在epfd實例中加入一個fd指定的監聽文件,使用以下代碼:
struct epoll_event event;
int ret;
event.data.fd = fd;/*return the fd to us later*/
event.events = EPOLLIN|EPOLLOUT  ;
ret = epoll_ctl (epfd,EPOLL_CTL_MOD,fd,&event);
if (ret)
	perror ("epoll_ctl");
修改epfd實例中的fd上的一個監聽事件,可使用以下代碼:
struct epoll_event event;
int ret;
event.data.fd = fd;/*return the fd to us later*/
event.events = EPOLLIN ;
ret = epoll_ctl (epfd,EPOLL_CTL_MOD,fd,&event);
if (ret)
	perror ("epoll_ctl");
刪除一個fd監聽事件,可使用以下代碼:
struct epoll_event event;
int ret;
event.data.fd = fd;/*return the fd to us later*/
event.events = EPOLLIN ;
ret = epoll_ctl (epfd,EPOLL_CTL_DEL,fd,&event);
if (ret)
	perror ("epoll_ctl");

三、等待Epoll事件

epoll_wait()等待給定epoll實例關聯的文件描述符上的事件:
#include <sys/epoll.h>
int epoll_wait (int epfd, struct epoll_event * 
	* events, int maxevents, int timeout);
對epoll_wait()的調用等待epoll實例epfd中的文件fd上的事件,時限爲timeout毫秒。成功返回,struct epoll_event *events指向包含epoll_event結構體(該結構體描述了每一個事件)的內存,且最多能夠有maxevents
個事件。這樣能夠根據events結構體來肯定哪些fd觸發了事件,從而作出相應的處理。返回值是事件數,出錯返回-1,並將errno設置爲如下值
EBADF epfd是無效文件描述符
EFAULT 進程對events指向的內存無寫權限
EINTR 系統調用在完成前被信號中斷
EINVAL epfd不是有效的epoll實例,或者maxevents小於等於0
若是timeout 爲0.即便沒有事件發生,調用也當即發生,此時調用返回0.若是timeout爲-1,調用將一直等待到有事件發生。
當調用epoll_wait()返回,epoll_event結構體中的events數組描述了一次等待發生的事件,最多返回maxevents個事件。data字段包含了用戶在調用epoll_ctl前的設置,
如文件的句柄,用來區分那個文件所發生的事件。
一個完整的epoll_wait()例子以下:
#define MAX_EVENTS   64
struct epoll_event * events = NULL;
int nr_events, i, epfd;
events = malloc (sizeof(struct epoll_event) * MAX_EVENTS);
if (! events ){
	perror("malloc");
	exit(-1);
}
nr_events = epoll_wait (epfd,events,MAX_EVENTS,-1);
if (nr_events < 0){
	perror("epoll_wait");
	free(events);
	exit (-1);
}
for (int i=0; i<nr_eventsl i++)
	printf("event = %d on fd = %d \n",
		events[i].events,events[i].data.fd); 

四、邊沿觸發時間和水平觸發事件

EPOLL事件有兩種模型 Level Triggered (LT)Edge Triggered (ET):

LT(level triggered,水平觸發模式)是缺省的工做方式,而且同時支持 block 和 non-block socket。在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任何操做,內核仍是會繼續通知你的,因此,這種模式編程出錯誤可能性要小一點。

ET(edge-triggered,邊緣觸發模式)是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,等到下次有新的數據進來的時候纔會再次出發就緒事件。

五、man epoll 中的實例

setnonblocking()函數將socket文件設置爲非阻塞,由於使用的是ET模式。do_use_fd()是對此文件作出必定的處理,如讀寫等。

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Set up listening socket, 'listen_sock' (socket(),
    bind(), listen()) */

epollfd = epoll_create(10);
if (epollfd == -1) {
    perror("epoll_create");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_pwait");
        exit(EXIT_FAILURE);
    }

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == listen_sock) {
            conn_sock = accept(listen_sock,
                            (struct sockaddr *) &local, &addrlen);
            if (conn_sock == -1) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            setnonblocking(conn_sock);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                        &ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd(events[n].data.fd);
        }
    }
}
相關文章
相關標籤/搜索