本文转自:http://blog.csdn.net/tianmohust/article/details/8502352
epoll简介
epoll
是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大量应用程序请求时能够获得较好的性能( 此时被监视的文件描述符数目非常大,与旧的 select 和 poll 系统调用完成操作所需 O(n) 不同, epoll能在O(1)时间内完成操作,所以性能相当高),epoll 与 FreeBSD的kqueue类似,都向用户空间提供了自己的文件描述符来进行操作。
- int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核需要监听的数目一共有多大。当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close() 关闭,否则可能导致fd被耗尽。
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,第一个参数是 epoll_create() 的返回值,第二个参数表示动作,使用如下三个宏来表示:
- EPOLL_CTL_ADD //注册新的fd到epfd中;
- EPOLL_CTL_MOD //修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL //从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event 结构如下:
- 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 //表示对应的文件描述符被挂断;
- EPOLLET //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
- EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
当对方关闭连接(FIN), EPOLLERR,都可以认为是一种EPOLLIN事件,在read的时候分别有0,-1两个返回值。
- int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数events用来从内核得到事件的集合,maxevents 告之内核这个events有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的size,参数 timeout 是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
我们目前的网络模型大都是epoll的,因为epoll模型会比select模型性能高很多, 尤其在大连接数的情况下,作为后台开发人员需要理解其中的原因。
select/epoll的特点: select的特点:select 选择句柄的时候,是遍历所有句柄,也就是说句柄有事件响应时,select需要遍历所有句柄才能获取到哪些句柄有事件通知,因此效率是非常低。但是如果连接很少的情况下, select和epoll的LT触发模式相比, 性能上差别不大。
这里要多说一句,select支持的句柄数是有限制的, 同时只支持1024个,这个是句柄集合限制的,如果超过这个限制,很可能导致溢出,而且非常不容易发现问题, TAF就出现过这个问题, 调试了n天,才发现:)当然可以通过修改linux的socket内核调整这个参数。
epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的。
对于epoll而言还有ET和LT的区别,LT表示水平触发,ET表示边缘触发,两者在性能以及代码实现上差别也是非常大的。
EPOLL事件有两种模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。
epoll 例子
我们将实现一个简单的TCP 服务器,该迷你服务器将会在标准输出上打印处客户端发送的数据,首先我们创建并绑定一个 TCP 套接字:
- static int
- create_and_bind (char *port)
- {
- struct addrinfo hints;
- struct addrinfo *result, *rp;
- int s, sfd;
- memset (&hints, 0, sizeof (struct addrinfo));
- hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
- hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
- hints.ai_flags = AI_PASSIVE; /* All interfaces */
- s = getaddrinfo (NULL, port, &hints, &result);
- if (s != 0)
- {
- fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
- return -1;
- }
- for (rp = result; rp != NULL; rp = rp->ai_next)
- {
- sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
- if (sfd == -1)
- continue;
- s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
- if (s == 0)
- {
- /* We managed to bind successfully! */
- break;
- }
- close (sfd);
- }
- if (rp == NULL)
- {
- fprintf (stderr, "Could not bind\n");
- return -1;
- }
- freeaddrinfo (result);
- return sfd;
- }
- struct addrinfo
- {
- int ai_flags;
- int ai_family;
- int ai_socktype;
- int ai_protocol;
- size_t ai_addrlen;
- struct sockaddr *ai_addr;
- char *ai_canonname;
- struct addrinfo *ai_next;
- };
如果函数成功则返回套接字,如果失败,则返回 -1,
下面,我们将一个套接字设置为非阻塞形式,函数如下:
- static int
- make_socket_non_blocking (int sfd)
- {
- int flags, s;
- flags = fcntl (sfd, F_GETFL, 0);
- if (flags == -1)
- {
- perror ("fcntl");
- return -1;
- }
- flags |= O_NONBLOCK;
- s = fcntl (sfd, F_SETFL, flags);
- if (s == -1)
- {
- perror ("fcntl");
- return -1;
- }
- return 0;
- }
- #define MAXEVENTS 64
- int
- main (int argc, char *argv[])
- {
- int sfd, s;
- int efd;
- struct epoll_event event;
- struct epoll_event *events;
- if (argc != 2)
- {
- fprintf (stderr, "Usage: %s [port]\n", argv[0]);
- exit (EXIT_FAILURE);
- }
- sfd = create_and_bind (argv[1]);
- if (sfd == -1)
- abort ();
- s = make_socket_non_blocking (sfd);
- if (s == -1)
- abort ();
- s = listen (sfd, SOMAXCONN);
- if (s == -1)
- {
- perror ("listen");
- abort ();
- }
- efd = epoll_create1 (0);
- if (efd == -1)
- {
- perror ("epoll_create");
- abort ();
- }
- event.data.fd = sfd;
- event.events = EPOLLIN | EPOLLET;
- s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
- if (s == -1)
- {
- perror ("epoll_ctl");
- abort ();
- }
- /* Buffer where events are returned */
- events = calloc (MAXEVENTS, sizeof event);
- /* The event loop */
- while (1)
- {
- int n, i;
- n = epoll_wait (efd, events, MAXEVENTS, -1);
- for (i = 0; i < n; i++)
- {
- if ((events[i].events & EPOLLERR) ||
- (events[i].events & EPOLLHUP) ||
- (!(events[i].events & EPOLLIN)))
- {
- /* An error has occured on this fd, or the socket is not
- ready for reading (why were we notified then?) */
- fprintf (stderr, "epoll error\n");
- close (events[i].data.fd);
- continue;
- }
- else if (sfd == events[i].data.fd)
- {
- /* We have a notification on the listening socket, which
- means one or more incoming connections. */
- while (1)
- {
- struct sockaddr in_addr;
- socklen_t in_len;
- int infd;
- char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
- in_len = sizeof in_addr;
- infd = accept (sfd, &in_addr, &in_len);
- if (infd == -1)
- {
- if ((errno == EAGAIN) ||
- (errno == EWOULDBLOCK))
- {
- /* We have processed all incoming
- connections. */
- break;
- }
- else
- {
- perror ("accept");
- break;
- }
- }
- s = getnameinfo (&in_addr, in_len,
- hbuf, sizeof hbuf,
- sbuf, sizeof sbuf,
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (s == 0)
- {
- printf("Accepted connection on descriptor %d "
- "(host=%s, port=%s)\n", infd, hbuf, sbuf);
- }
- /* Make the incoming socket non-blocking and add it to the
- list of fds to monitor. */
- s = make_socket_non_blocking (infd);
- if (s == -1)
- abort ();
- event.data.fd = infd;
- event.events = EPOLLIN | EPOLLET;
- s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
- if (s == -1)
- {
- perror ("epoll_ctl");
- abort ();
- }
- }
- continue;
- }
- else
- {
- /* We have data on the fd waiting to be read. Read and
- display it. We must read whatever data is available
- completely, as we are running in edge-triggered mode
- and won't get a notification again for the same
- data. */
- int done = 0;
- while (1)
- {
- ssize_t count;
- char buf[512];
- count = read (events[i].data.fd, buf, sizeof buf);
- if (count == -1)
- {
- /* If errno == EAGAIN, that means we have read all
- data. So go back to the main loop. */
- if (errno != EAGAIN)
- {
- perror ("read");
- done = 1;
- }
- break;
- }
- else if (count == 0)
- {
- /* End of file. The remote has closed the
- connection. */
- done = 1;
- break;
- }
- /* Write the buffer to standard output */
- s = write (1, buf, count);
- if (s == -1)
- {
- perror ("write");
- abort ();
- }
- }
- if (done)
- {
- printf ("Closed connection on descriptor %d\n",
- events[i].data.fd);
- /* Closing the descriptor will make epoll remove it
- from the set of descriptors which are monitored. */
- close (events[i].data.fd);
- }
- }
- }
- }
- free (events);
- close (sfd);
- return EXIT_SUCCESS;
- }
main() 首先调用 create_and_bind() 建立套接字,然后将其设置为非阻塞的,再调用 listen(2)。之后创建一个epoll 实例 efd(文件描述符),并将其加入到sfd的监听套接字中以边沿触发方式等待事件输入。
外层的 while 循环是主事件循环,它调用了 epoll_wait(2),此时线程仍然被阻塞等待事件,当事件可用时,epoll_wait(2) 将会在events参数中返回可用事件。
epoll 实例 efd 在每次事件到来并需要添加新的监听时就会得到更新,并删除死亡的链接。
当事件可用时,可能有一下三种类型:
- Errors: 当错误情况出现时,或者不是与读取数据相关的事件通告,我们只是关闭相关的描述符,关闭该描述符会自动的将其从被epoll 实例 efd 监听的的集合中删除。
- New connections: 当监听的文件描述符 sfd 可读时,此时会有一个或多个新的连接到来,当新连接到来时,accept(2) 该连接,并打印一条信息,将其设置为非阻塞的并把它加入到被 epoll 实例监听的集合中。
- Client data: 当数据在客户端描述符可用时,我们使用 read(2) 在一个内部循环中每次读取512 字节数据。由于我们必须读取所有的可用数据,此时我们并不能获取更多的事件,因为描述符是以边沿触发监听的,读取的数据被写到 stdout (fd=1) (write(2))。如果 read(2) 返回 0,意味着到了文件末尾EOF,我们可以关闭客户端连接,如果返回 -1,
errno
会被设置成EAGAIN
, 这意味着所有的数据已经被读取,可以返回主循环了。
- #include <iostream>
- #include <sys/socket.h>
- #include <sys/epoll.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <errno.h>
- using namespace std;
- #define MAXLINE 5
- #define OPEN_MAX 100
- #define LISTENQ 20
- #define SERV_PORT 5000
- #define INFTIM 1000
- void setnonblocking(int sock)
- {
- int opts;
- opts=fcntl(sock,F_GETFL);
- if(opts<0)
- {
- perror("fcntl(sock,GETFL)");
- exit(1);
- }
- opts = opts|O_NONBLOCK;
- if(fcntl(sock,F_SETFL,opts)<0)
- {
- perror("fcntl(sock,SETFL,opts)");
- exit(1);
- }
- }
- int main(int argc, char* argv[])
- {
- int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
- ssize_t n;
- char line[MAXLINE];
- socklen_t clilen;
- if ( 2 == argc )
- {
- if( (portnumber = atoi(argv[1])) < 0 )
- {
- fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
- return 1;
- }
- }
- else
- {
- fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
- return 1;
- }
- //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
- struct epoll_event ev,events[20];
- //生成用于处理accept的epoll专用的文件描述符
- epfd=epoll_create(256);
- struct sockaddr_in clientaddr;
- struct sockaddr_in serveraddr;
- listenfd = socket(AF_INET, SOCK_STREAM, 0);
- //把socket设置为非阻塞方式
- //setnonblocking(listenfd);
- //设置与要处理的事件相关的文件描述符
- ev.data.fd=listenfd;
- //设置要处理的事件类型
- ev.events=EPOLLIN|EPOLLET;
- //ev.events=EPOLLIN;
- //注册epoll事件
- epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
- bzero(&serveraddr, sizeof(serveraddr));
- serveraddr.sin_family = AF_INET;
- char *local_addr="127.0.0.1";
- inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);
- serveraddr.sin_port=htons(portnumber);
- bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
- listen(listenfd, LISTENQ);
- maxi = 0;
- for ( ; ; ) {
- //等待epoll事件的发生
- nfds=epoll_wait(epfd,events,20,500);
- //处理所发生的所有事件
- for(i=0;i<nfds;++i)
- {
- if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
- {
- connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
- if(connfd<0){
- perror("connfd<0");
- exit(1);
- }
- //setnonblocking(connfd);
- char *str = inet_ntoa(clientaddr.sin_addr);
- cout << "accapt a connection from " << str << endl;
- //设置用于读操作的文件描述符
- ev.data.fd=connfd;
- //设置用于注测的读操作事件
- ev.events=EPOLLIN|EPOLLET;
- //ev.events=EPOLLIN;
- //注册ev
- epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
- }
- else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
- {
- cout << "EPOLLIN" << endl;
- if ( (sockfd = events[i].data.fd) < 0)
- continue;
- if ( (n = read(sockfd, line, MAXLINE)) < 0) {
- if (errno == ECONNRESET) {
- close(sockfd);
- events[i].data.fd = -1;
- } else
- std::cout<<"readline error"<<std::endl;
- } else if (n == 0) {
- close(sockfd);
- events[i].data.fd = -1;
- }
- line[n] = '/0';
- cout << "read " << line << endl;
- //设置用于写操作的文件描述符
- ev.data.fd=sockfd;
- //设置用于注测的写操作事件
- ev.events=EPOLLOUT|EPOLLET;
- //修改sockfd上要处理的事件为EPOLLOUT
- //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
- }
- else if(events[i].events&EPOLLOUT) // 如果有数据发送
- {
- sockfd = events[i].data.fd;
- write(sockfd, line, n);
- //设置用于读操作的文件描述符
- ev.data.fd=sockfd;
- //设置用于注测的读操作事件
- ev.events=EPOLLIN|EPOLLET;
- //修改sockfd上要处理的事件为EPOLIN
- epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
- }
- }
- }
- return 0;
- }
函数声明:int epoll_create(int size)
该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。随你定好了。只要你有空间。可参见上面与select之不同2.
22、epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功返回0,不成功返回-1
用到的数据结构
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 */
};
如:
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。
参考资料:
http://en.wikipedia.org/wiki/Epoll
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
http://blog.csdn.net/ljx0305/article/details/4065058
http://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/
http://www.cnblogs.com/haippy/archive/2012/01/09/2317269.html
补充知识:
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define__FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
epoll的接口非常简单,一共就三个函数:
1. intepoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. intepoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值,
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,
第四个参数是告诉内核需要监听什么事,structepoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epollevents */
epoll_data_t data; /* User datavariable */
};
events可以是以下几个宏的集合:
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT: 表示对应的文件描述符可以写;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(EdgeTriggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3. intepoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
从man手册中,得到ET和LT的具体描述如下
EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来,才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发。
假如有这样一个例子:
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)......
Edge Triggered 工作模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
i 基于非阻塞文件句柄
ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
Level Triggered 工作模式
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。
然后详细解释ET, LT:
LT(leveltriggered)是缺省的工作方式,并且同时支持block和no-blocksocket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。
在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。(未测试)
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:
- while(rs)
- {
- buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
- if(buflen < 0)
- {
- // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
- // 在这里就当作是该次事件已处理处.
- if(errno == EAGAIN)
- break;
- else
- return;
- }
- else if(buflen == 0)
- {
- // 这里表示对端的socket已正常关闭.
- }
- if(buflen == sizeof(buf)
- rs = 1; // 需要再次读取
- else
- rs = 0;
- }
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
- ssize_t socket_send(int sockfd, const char*buffer, size_t buflen)
- {
- ssize_t tmp;
- size_t total = buflen;
- const char *p = buffer;
- while(1)
- {
- tmp = send(sockfd, p, total, 0);
- if(tmp < 0)
- {
- // 当send收到信号时,可以继续写,但这里返回-1.
- if(errno == EINTR)
- return -1;
- // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,
- // 在这里做延时后再重试.
- if(errno == EAGAIN)
- {
- usleep(1000);
- continue;
- }
- return -1;
- }
- if((size_t)tmp == total)
- return buflen;
- total -= tmp;
- p+= tmp;
- }
- return tmp;
- }
实例代码:
- #include <iostream>
- #include <sys/socket.h>
- #include <sys/epoll.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <pthread.h>
- #include <errno.h>
- #define MAXLINE 10
- #define OPEN_MAX 100
- #define LISTENQ 20
- #define SERV_PORT 5555
- #define INFTIM 1000
- //线程池任务队列结构体
- struct task
- {
- int fd; //需要读写的文件描述符
- struct task *next; //下一个任务
- };
- //用于读写两个的两个方面传递参数
- struct user_data
- {
- int fd;
- unsigned int n_size;
- char line[MAXLINE];
- };
- //线程的任务函数
- void * readtask(void *args);
- void * writetask(void *args);
- //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
- struct epoll_event ev, events[20];
- int epfd;
- pthread_mutex_t mutex;
- pthread_cond_t cond1;
- struct task *readhead = NULL, *readtail =NULL, *writehead = NULL;
- void setnonblocking(int sock)
- {
- int opts;
- opts = fcntl(sock, F_GETFL);
- if(opts < 0)
- {
- perror("fcntl(sock,GETFL)");
- exit(1);
- }
- opts= opts | O_NONBLOCK;
- if(fcntl(sock, F_SETFL, opts) < 0)
- {
- perror("fcntl(sock,SETFL,opts)");
- exit(1);
- }
- }
- int main()
- {
- int i, maxi, listenfd, connfd, sockfd, nfds;
- pthread_t tid1, tid2;
- struct task *new_task = NULL;
- struct user_data *rdata = NULL;
- socklen_t clilen;
- pthread_mutex_init(&mutex,NULL);
- pthread_cond_init(&cond1,NULL);
- //初始化用于读线程池的线程
- pthread_create(&tid1,NULL, readtask, NULL);
- pthread_create(&tid2,NULL, readtask, NULL);
- //生成用于处理accept的epoll专用的文件描述符
- epfd= epoll_create(256);
- structsockaddr_in clientaddr;
- structsockaddr_in serveraddr;
- listenfd= socket(AF_INET, SOCK_STREAM, 0);
- //把socket设置为非阻塞方式
- setnonblocking(listenfd);
- //设置与要处理的事件相关的文件描述符
- ev.data.fd= listenfd;
- //设置要处理的事件类型
- ev.events= EPOLLIN | EPOLLET;
- //注册epoll事件
- epoll_ctl(epfd,EPOLL_CTL_ADD, listenfd, &ev);
- bzero(&serveraddr,sizeof(serveraddr));
- serveraddr.sin_family= AF_INET;
- char*local_addr = "200.200.200.222";
- inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);
- serveraddr.sin_port= htons(SERV_PORT);
- bind(listenfd,(sockaddr *) &serveraddr, sizeof(serveraddr));
- listen(listenfd,LISTENQ);
- maxi= 0;
- for(;;)
- {
- //等待epoll事件的发生
- nfds= epoll_wait(epfd, events, 20, 500);
- //处理所发生的所有事件
- for(i = 0; i < nfds; ++i)
- {
- if(events[i].data.fd == listenfd)
- {
- connfd= accept(listenfd, (sockaddr *) &clientaddr, &clilen);
- if(connfd < 0)
- {
- perror("connfd<0");
- exit(1);
- }
- setnonblocking(connfd);
- char*str = inet_ntoa(clientaddr.sin_addr);
- std::cout<< "connec_ from >>" << str << std::endl;
- //设置用于读操作的文件描述符
- ev.data.fd= connfd;
- //设置用于注测的读操作事件
- ev.events= EPOLLIN | EPOLLET;
- //注册ev
- epoll_ctl(epfd,EPOLL_CTL_ADD, connfd, &ev);
- } else
- if(events[i].events & EPOLLIN)
- {
- printf("reading!\n");
- if((sockfd = events[i].data.fd) < 0) continue;
- new_task= new task();
- new_task->fd= sockfd;
- new_task->next= NULL;
- //添加新的读任务
- pthread_mutex_lock(&mutex);
- if(readhead == NULL)
- {
- readhead= new_task;
- readtail= new_task;
- }else
- {
- readtail->next= new_task;
- readtail= new_task;
- }
- //唤醒所有等待cond1条件的线程
- pthread_cond_broadcast(&cond1);
- pthread_mutex_unlock(&mutex);
- } else
- if(events[i].events & EPOLLOUT)
- {
- rdata= (struct user_data *) events[i].data.ptr;
- sockfd= rdata->fd;
- write(sockfd,rdata->line, rdata->n_size);
- deleterdata;
- //设置用于读操作的文件描述符
- ev.data.fd= sockfd;
- //设置用于注测的读操作事件
- ev.events= EPOLLIN | EPOLLET;
- //修改sockfd上要处理的事件为EPOLIN
- epoll_ctl(epfd,EPOLL_CTL_MOD, sockfd, &ev);
- }
- }
- }
- }
- void * readtask(void *args)
- {
- int fd = -1;
- unsigned int n;
- //用于把读出来的数据传递出去
- struct user_data *data = NULL;
- while(1)
- {
- pthread_mutex_lock(&mutex);
- //等待到任务队列不为空
- while(readhead == NULL)
- pthread_cond_wait(&cond1,&mutex);
- fd= readhead->fd;
- //从任务队列取出一个读任务
- struct task *tmp = readhead;
- readhead= readhead->next;
- delete tmp;
- pthread_mutex_unlock(&mutex);
- data = new user_data();
- data->fd= fd;
- if((n = read(fd, data->line, MAXLINE)) < 0)
- {
- if(errno == ECONNRESET)
- {
- close(fd);
- }else
- std::cout<< "readline error" << std::endl;
- if(data != NULL) delete data;
- }else
- if(n == 0)
- {
- close(fd);
- printf("Clientclose connect!\n");
- if(data != NULL) delete data;
- }else
- {
- data->n_size= n;
- //设置需要传递出去的数据
- ev.data.ptr= data;
- //设置用于注测的写操作事件
- ev.events= EPOLLOUT | EPOLLET;
- //修改sockfd上要处理的事件为EPOLLOUT
- epoll_ctl(epfd,EPOLL_CTL_MOD, fd, &ev);
- }
- }
- }
补充的细节知识
本文基于2.6.39内核
第一部分已经大致的介绍了linux在实现EPOLL模型所用到的主要数据结构,接下来这篇就是主要分析怎样通过这些数据结构来实现EPOLL的功能。
一。模块的加载
linux把EPOLL当做一个模块,模块入口函数的代码如下:
- /************epoll模块入口函数***********/
- static int __init eventpoll_init(void)
- {
- struct sysinfo si;
- si_meminfo(&si);
- /*
- * Allows top 4% of lomem to be allocated for epoll watches (per user).
- */
- max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /
- EP_ITEM_COST;
- BUG_ON(max_user_watches < 0);
- /*
- * Initialize the structure used to perform epoll file descriptor
- * inclusion loops checks.
- */
- ep_nested_calls_init(&poll_loop_ncalls);
- /* Initialize the structure used to perform safe poll wait head wake ups */
- ep_nested_calls_init(&poll_safewake_ncalls);
- /* Initialize the structure used to perform file's f_op->poll() calls */
- ep_nested_calls_init(&poll_readywalk_ncalls);
- /* Allocates slab cache used to allocate "struct epitem" items */
- epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
- 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
- /* Allocates slab cache used to allocate "struct eppoll_entry" */
- pwq_cache = kmem_cache_create("eventpoll_pwq",
- sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);
- return 0;
- }
- fs_initcall(eventpoll_init);
- /*fs_initcall 函数即是module_init函数*/
这个函数主要是进行一些初始化配置,同时创建了2个内核cache用于存放epitem和epoll_entry。
二 epoll_create函数的实现
epoll_create 创建一个epoll实例,即一个epoll的文件(epfd),同时创建并初始化一个struct eventpoll,其中efpd所对应的file的private_data指针即指向了eventpoll变量,因此,知道epfd就可以拿到file,即拿到了eventpoll变量。
下面我们来看具体实现:
- SYSCALL_DEFINE1(epoll_create, int, size) //epoll_create函数带一个整型参数
- {
- if (size <= 0)
- return -EINVAL;
- return sys_epoll_create1(0); //实际上是调用epoll_create1
- }
- /*
- * Open an eventpoll file descriptor.
- */
- SYSCALL_DEFINE1(epoll_create1, int, flags)
- {
- int error;
- struct eventpoll *ep = NULL;
- /* Check the EPOLL_* constant for consistency. */
- BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
- if (flags & ~EPOLL_CLOEXEC)
- return -EINVAL;
- /*
- * Create the internal data structure ("struct eventpoll").
- */
- error = ep_alloc(&ep);//分配eventpoll结构体
- if (error < 0)
- return error;
- /*
- * Creates all the items needed to setup an eventpoll file. That is,
- * a file structure and a free file descriptor.
- */
- error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
- O_RDWR | (flags & O_CLOEXEC)); //创建与eventpoll结构体相对应的file结构,ep保存在file->private_data结构中
- //eventpoll_fops 为该文件所对应的操作函数
- if (error < 0)
- ep_free(ep); //如果出错则释放该eventpoll结构体
- return error;
- }
epoll_create可能调用一组关联函数ep_alloc和ep_free函数分别负责eventpoll结构体的内存分配和释放
函数anon_inode_getfd创建与eventpoll结构体相对应的file结构,ep保存在file->private_data结构中,同时为该新文件定义操作函数
从这几行代码可以看出,epoll_create主要做了两件事:
* 创建并初始化一个eventpoll结构体变量
* 创建epoll的file结构,并指定file的private_data指针指向刚创建的eventpoll变量,这样,只要根据epoll文件描述符epfd就可以拿到file进而就拿到了eventpoll变量了,该eventpoll就是epoll_ctl和epoll_wait工作的场所
对外看来,epoll_create就做了一件事,那就是创建一个epoll文件,事实上,更关键的是,它创建了一个eventpoll结构体变量,该变量为epoll_ctl和epoll_wait的工作打下了基础。
ep_alloc,ep_free以及anon_inode_getfd的具体实现可以查看源代码。
三 epoll_ctl的实现
epoll_ctl系统调用主要是针对epfd所对应的epoll实例进行增、删、改fd的操作,一个新创建的epoll文件带有一个struct eventpoll结构,同时struct eventpoll这个结构上再挂一个红黑树,红黑树上的每个节点挂的都是struct epitem,这个红黑树就是每次epoll_ctl时fd存放的地方!对应该红黑树上节点的操作,有ep_find,ep_insert,ep_remove,ep_modify四个函数,它们都将epoll文件实例的eventpoll结构作为参数传递。
下面来看看该函数的具体实现:
- /*
- * The following function implements the controller interface for
- * the eventpoll file that enables the insertion/removal/change of
- * file descriptors inside the interest set.
- * epfd为该epoll套接字实例,op表示对应的操作,fd表示新加入的套接字,
- * 结构体epoll_event 用于注册fd所感兴趣的事件和回传在fd上所发生待处理的事件
- */
- SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
- struct epoll_event __user *, event)
- {
- int error;
- int did_lock_epmutex = 0;
- struct file *file, *tfile;
- struct eventpoll *ep;
- struct epitem *epi;
- struct epoll_event epds;
- error = -EFAULT;
- if (ep_op_has_event(op) &&
- copy_from_user(&epds, event, sizeof(struct epoll_event))) //copy_from_user将用户传入的event_poll拷贝到epds中,以供自己使用
- goto error_return;
- /* Get the "struct file *" for the eventpoll file */
- error = -EBADF;
- file = fget(epfd); //获取该epoll套接字实例所对应的文件
- if (!file)
- goto error_return;
- /* Get the "struct file *" for the target file */
- tfile = fget(fd);
- if (!tfile)
- goto error_fput;
- /* The target file descriptor must support poll */
- error = -EPERM;
- if (!tfile->f_op || !tfile->f_op->poll)
- goto error_tgt_fput;
- /*
- * We have to check that the file structure underneath the file descriptor
- * the user passed to us _is_ an eventpoll file. And also we do not permit
- * adding an epoll file descriptor inside itself.
- */
- error = -EINVAL;
- if (file == tfile || !is_file_epoll(file))
- goto error_tgt_fput;
- /*
- * At this point it is safe to assume that the "private_data" contains
- * our own data structure.
- */
- ep = file->private_data;//获取epoll实例所对应的eventpoll结构体
- /*
- * When we insert an epoll file descriptor, inside another epoll file
- * descriptor, there is the change of creating closed loops, which are
- * better be handled here, than in more critical paths.
- *
- * We hold epmutex across the loop check and the insert in this case, in
- * order to prevent two separate inserts from racing and each doing the
- * insert "at the same time" such that ep_loop_check passes on both
- * before either one does the insert, thereby creating a cycle.
- */
- if (unlikely(is_file_epoll(tfile) && op == EPOLL_CTL_ADD)) {
- mutex_lock(&epmutex);
- did_lock_epmutex = 1;
- error = -ELOOP;
- if (ep_loop_check(ep, tfile) != 0)
- goto error_tgt_fput;
- }
- mutex_lock(&ep->mtx);
- /*
- * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
- * above, we can be sure to be able to use the item looked up by
- * ep_find() till we release the mutex.
- * ep_find即从ep中的红黑树中根据tfile和fd来查找epitem
- */
- epi = ep_find(ep, tfile, fd);
- error = -EINVAL;
- switch (op) {
- case EPOLL_CTL_ADD: //对应于socket上事件注册
- if (!epi) { //红黑树中不存在这个节点
- epds.events |= POLLERR | POLLHUP; //或操作,确保“出错、连接挂起”被当做感兴趣事件,因为底层有义务将出错信息返回给应用
- error = ep_insert(ep, &epds, tfile, fd);
- } else
- error = -EEXIST;
- break;
- case EPOLL_CTL_DEL: //删除
- if (epi) //存在则删除这个节点,不存在则报错
- error = ep_remove(ep, epi);
- else
- error = -ENOENT;
- break;
- case EPOLL_CTL_MOD: //修改
- if (epi) { //存在则修改该fd所对应的事件,不存在则报错
- epds.events |= POLLERR | POLLHUP;
- error = ep_modify(ep, epi, &epds);
- } else
- error = -ENOENT;
- break;
- }
- mutex_unlock(&ep->mtx);
- error_tgt_fput:
- if (unlikely(did_lock_epmutex))
- mutex_unlock(&epmutex);
- fput(tfile);
- error_fput:
- fput(file);
- error_return:
- return error;
- }
结合上一篇博文的内容,对于往epoll实例中添加新的套接字,其实现主要通过函数ep_insert来完成,本文先分析epoll_wait再回过头来分析ep_insert
四 epoll_wait的实现
epoll_wait等待epoll文件上的I/O事件发生,其代码如下:
可以看出该函数主要时通过epfd获取对应的struct eventpoll结构,然后调用ep_poll函数,下面来看ep_poll函数的实现:
- /*
- * Implement the event wait interface for the eventpoll file. It is the kernel
- * part of the user space epoll_wait(2).
- */
- SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
- int, maxevents, int, timeout)
- {
- int error;
- struct file *file;
- struct eventpoll *ep;
- /* The maximum number of event must be greater than zero */
- if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
- return -EINVAL;
- /* Verify that the area passed by the user is writeable */
- if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {
- error = -EFAULT;
- goto error_return;
- }
- /* Get the "struct file *" for the eventpoll file */
- error = -EBADF;
- file = fget(epfd);
- if (!file)
- goto error_return;
- /*
- * We have to check that the file structure underneath the fd
- * the user passed to us _is_ an eventpoll file.
- */
- error = -EINVAL;
- if (!is_file_epoll(file))
- goto error_fput;
- /*
- * At this point it is safe to assume that the "private_data" contains
- * our own data structure.
- */
- ep = file->private_data;//获取struct eventpoll结构
- /* Time to fish for events ... */
- error = ep_poll(ep, events, maxevents, timeout);//核心代码
- error_fput:
- fput(file);
- error_return:
- return error;
- }
- /**
- * ep_poll - Retrieves ready events, and delivers them to the caller supplied
- * event buffer.
- *
- * @ep: Pointer to the eventpoll context.
- * @events: Pointer to the userspace buffer where the ready events should be
- * stored.
- * @maxevents: Size (in terms of number of events) of the caller event buffer.
- * @timeout: Maximum timeout for the ready events fetch operation, in
- * milliseconds. If the @timeout is zero, the function will not block,
- * while if the @timeout is less than zero, the function will block
- * until at least one event has been retrieved (or an error
- * occurred).
- *
- * Returns: Returns the number of ready events which have been fetched, or an
- * error code, in case of error.
- */
- static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
- int maxevents, long timeout)
- {
- int res = 0, eavail, timed_out = 0;
- unsigned long flags;
- long slack = 0;
- wait_queue_t wait;
- ktime_t expires, *to = NULL;
- /* The call waits for a maximum time of timeout milliseconds. Specifying a timeout of -1 makes epoll_wait()
- wait indefinitely,while specifying a timeout equal to zero makes epoll_wait() to return immediately even if no events
- are available (return code equal to zero).*/
- if (timeout > 0) {
- struct timespec end_time = ep_set_mstimeout(timeout);
- slack = select_estimate_accuracy(&end_time);
- to = &expires;
- *to = timespec_to_ktime(end_time);
- } else if (timeout == 0) {
- /*
- * Avoid the unnecessary trip to the wait queue loop, if the
- * caller specified a non blocking operation.
- */
- timed_out = 1;
- spin_lock_irqsave(&ep->lock, flags);
- goto check_events;
- }
- fetch_events:
- spin_lock_irqsave(&ep->lock, flags);
- // 如果rdllist中还没有epitem时,就开始等待了
- if (!ep_events_available(ep)) {
- /*
- * We don't have any available event to return to the caller.
- * We need to sleep here, and we will be wake up by
- * ep_poll_callback() when events will become available.
- */
- // 初始化等待队列,等待队列项对应的线程即为当前线程
- init_waitqueue_entry(&wait, current);
- // 不用多说,先将当前线程挂到等待队列上,之后在调用schedule_timeout
- // 时,就开始了超时等待了
- __add_wait_queue_exclusive(&ep->wq, &wait);
- for (;;) {
- /*
- * We don't want to sleep if the ep_poll_callback() sends us
- * a wakeup in between. That's why we set the task state
- * to TASK_INTERRUPTIBLE before doing the checks.
- */
- // 因为会被阻塞,这里先设置线程状态为可中断
- set_current_state(TASK_INTERRUPTIBLE);
- // 整个循环的核心,其实就在看rdllist中是否有数据,或者等待超时
- // 应征了前面的说明,epoll_wait只需要等着收集数据即可
- if (ep_events_available(ep) || timed_out)
- break;
- if (signal_pending(current)) {
- res = -EINTR;
- break;
- }
- spin_unlock_irqrestore(&ep->lock, flags);
- if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
- timed_out = 1;
- spin_lock_irqsave(&ep->lock, flags);
- }
- __remove_wait_queue(&ep->wq, &wait);
- set_current_state(TASK_RUNNING);
- }
- check_events:
- /* Is it worth to try to dig for events ? */
- eavail = ep_events_available(ep);
- spin_unlock_irqrestore(&ep->lock, flags);
- /*
- * Try to transfer events to user space. In case we get 0 events and
- * there's still timeout left over, we go trying again in search of
- * more luck.
- */
- if (!res && eavail &&
- !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
- goto fetch_events;
- return res;
- }
29-43行:主要是超时时间的处理,若超时时间为0,则直接检查有没有准备好的I/O事件,有则立即发送给用户空间去处理;若超时时间大于0,计算好精确的超时时间后,等待事件的发生,45-86行等待指定的时间直到有I/O事件出现;
54-58行:如果还没有I/O事件出现,则准备休眠。先初始化等待队列,把当前线程挂在该队列上,同时把这个队列挂在eventpoll结构的wq上,
60-82行:在指定的超时时间内循环检测有没有I/O事件发生,有事件发生、超时或者收到信号都会跳出循环。
83行:运行到此处有I/O事件发生,不用再等待,则移除该队列