由于poll()和select()的局限,2.6內(nèi)核引入了event poll(epoll)機(jī)制。雖然稍微復(fù)雜,但是epoll解決了它們共有的基本性能問題,并增加了一些新的特性。
poll()和select()每次調(diào)用都需要所有被監(jiān)聽的文件描述符。內(nèi)核必須遍歷所有被監(jiān)視的文件描述符。當(dāng)這個(gè)表變得很大時(shí),成千上百的文件描述符,每次調(diào)用時(shí)的遍歷就成為了明顯的瓶頸。
1、創(chuàng)建一個(gè)新的epoll實(shí)例使用epoll_create()或者epoll_cerate1()創(chuàng)建一個(gè)epoll上下文。這里epoll_cerate1()是epoll_cerate()的擴(kuò)展版本。
#include <sys/epoll.h>int epoll_create (int size)
調(diào)用成功后,epoll_create()創(chuàng)建一個(gè)epoll實(shí)例,返回與該實(shí)例關(guān)聯(lián)的文件描述符。這個(gè)文件描述符和真正的文件沒有關(guān)系,僅僅是為了后續(xù)調(diào)用使用epoll而創(chuàng)建的。size參數(shù)告訴內(nèi)核需要監(jiān)聽的文件描述符數(shù)目,但不是最大值。傳遞一個(gè)適當(dāng)?shù)慕浦禃?huì)帶來(lái)性能的提升,但不需要給出確切的數(shù)字。出錯(cuò)時(shí),返回-1,設(shè)置errno為下列值之一:
EINVAL size不是正數(shù)
ENFILE 系統(tǒng)達(dá)到打開文件數(shù)的上限
ENOMEN 沒有足夠內(nèi)存完成該次操作。
標(biāo)準(zhǔn)調(diào)用如下:
int epfd;epfd = epoll_create (100); if (epfd <0 )perror("epoll_create");
epoll_create返回的文件描述符需要用close()關(guān)閉。
2、控制 epollepoll_ctl 可以向指定的epoll上下文加入或刪除文件描述符:
#include <sys/epoll.h>int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
頭文件<sys/epoll.h>中定義了epoll event結(jié)構(gòu)體
struct epoll_event {_u32 events;union {void * ptr;int fd;_u32 u32;_u64 u64;}data;};
epoll_ctl()成功調(diào)用將關(guān)聯(lián)epoll實(shí)例和epfd。參數(shù)op指定對(duì)fd要進(jìn)行的操作。event參數(shù)描述epoll更具體的行為
以下是參數(shù)op的有效值:
EPOLL_CTL_ADD 把fd指定的文件添加到epfd指定的epoll實(shí)例監(jiān)聽集中,監(jiān)聽event中定義的事件。
EPOLL_CTL_DEL 把fd指定的文件從epfd指定的epoll監(jiān)聽集中刪除。
EPOLL_CTL_MOD 使用event改變?cè)谝延衒d上的監(jiān)聽行為。
epoll_event結(jié)構(gòu)體中的event參數(shù)列出了在給定文件描述符上監(jiān)聽的事件。多個(gè)事件可以使用位或運(yùn)算同時(shí)指定。以下是有效值:
EPOLLERR 文件出錯(cuò)。即使不設(shè)置這個(gè)標(biāo)志,這個(gè)事件也是被監(jiān)聽的。
EPOLLET 使用邊沿觸發(fā)。默認(rèn)是水平觸發(fā)。
EPOLLHUP 文件被掛起。即使不設(shè)置這個(gè)標(biāo)志,這個(gè)事件也是被監(jiān)聽的。
EPOLLIN 文件未阻塞,可讀。
EPOLLONESHOT 在一次事件產(chǎn)生被處理之后,文件不在被監(jiān)聽。必須不再被監(jiān)聽。必須使用EPOLL_CTL_MOD指定新的事件,以便重新監(jiān)聽文件。
EPOLLOUT 文件未阻塞,可寫。
EPOLLPRI 高優(yōu)先級(jí)的帶外數(shù)據(jù)可讀。
event_poll中的data字段由用戶使用。確認(rèn)監(jiān)聽事件后,data會(huì)被返回給用戶。通常將event.data.fd設(shè)定為fd,這樣就可以知道那個(gè)文件描述符觸發(fā)事件。
成功后,epoll_ctl()返回0.失敗返回-1,并設(shè)置errno為下列值:
EBADF epfd不是一個(gè)有效的epoll實(shí)例,或者fd不是有效文件描述符。
EEXIST op為EPOLL_CTL_ADD,但是fd已經(jīng)與epfd關(guān)聯(lián)。
EINVAL epfd不是一個(gè)epoll實(shí)例,epfd和fd相同,或者op無(wú)效。
ENOENT op是EPOLL_CTL_MOD或者是EPOLL_CTL_DEL,但是fd沒有與epfd關(guān)聯(lián)。
ENOMEN 沒有足夠內(nèi)存完成進(jìn)程的請(qǐng)求。
EPERM fd不支持epoll。
在epfd實(shí)例中加入一個(gè)fd指定的監(jiān)聽文件,使用如下代碼:
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實(shí)例中的fd上的一個(gè)監(jiān)聽事件,可以使用如下代碼:
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");
刪除一個(gè)fd監(jiān)聽事件,可以使用如下代碼:
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");3、等待Epoll事件
epoll_wait()等待給定epoll實(shí)例關(guān)聯(lián)的文件描述符上的事件:
#include <sys/epoll.h>int epoll_wait (int epfd, struct epoll_event * * events, int maxevents, int timeout);
對(duì)epoll_wait()的調(diào)用等待epoll實(shí)例epfd中的文件fd上的事件,時(shí)限為timeout毫秒。成功返回,struct epoll_event *events指向包含epoll_event結(jié)構(gòu)體(該結(jié)構(gòu)體描述了每個(gè)事件)的內(nèi)存,且最多可以有maxevents
個(gè)事件。這樣可以根據(jù)events結(jié)構(gòu)體來(lái)確定哪些fd觸發(fā)了事件,從而做出相應(yīng)的處理。返回值是事件數(shù),出錯(cuò)返回-1,并將errno設(shè)置為以下值
EBADF epfd是無(wú)效文件描述符
EFAULT 進(jìn)程對(duì)events指向的內(nèi)存無(wú)寫權(quán)限
EINTR 系統(tǒng)調(diào)用在完成前被信號(hào)中斷
EINVAL epfd不是有效的epoll實(shí)例,或者maxevents小于等于0
如果timeout 為0.即使沒有事件發(fā)生,調(diào)用也立即發(fā)生,此時(shí)調(diào)用返回0.如果timeout為-1,調(diào)用將一直等待到有事件發(fā)生。
當(dāng)調(diào)用epoll_wait()返回,epoll_event結(jié)構(gòu)體中的events數(shù)組描述了一次等待發(fā)生的事件,最多返回maxevents個(gè)事件。data字段包含了用戶在調(diào)用epoll_ctl前的設(shè)置,
如文件的句柄,用來(lái)區(qū)分那個(gè)文件所發(fā)生的事件。
一個(gè)完整的epoll_wait()例子如下:
#define MAX_EVENTS 64struct 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);4、邊沿觸發(fā)時(shí)間和水平觸發(fā)事件
EPOLL事件有兩種模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平觸發(fā)模式)是缺省的工作方式,并且同時(shí)支持 block 和 non-block socket。在這種做法中,內(nèi)核告訴你一個(gè)文件描述符是否就緒了,然后你可以對(duì)這個(gè)就緒的fd進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會(huì)繼續(xù)通知你的,所以,這種模式編程出錯(cuò)誤可能性要小一點(diǎn)。
ET(edge-triggered,邊緣觸發(fā)模式)是高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時(shí),內(nèi)核通過epoll告訴你。然后它會(huì)假設(shè)你知道文件描述符已經(jīng)就緒,并且不會(huì)再為那個(gè)文件描述符發(fā)送更多的就緒通知,等到下次有新的數(shù)據(jù)進(jìn)來(lái)的時(shí)候才會(huì)再次出發(fā)就緒事件。
5、man epoll 中的實(shí)例setnonblocking()函數(shù)將socket文件設(shè)置為非阻塞,因?yàn)槭褂玫氖荅T模式。do_use_fd()是對(duì)此文件做出一定的處理,如讀寫等。
#define MAX_EVENTS 10struct 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); } }}
新聞熱點(diǎn)
疑難解答
圖片精選