epoll原理与应用详解

来源:互联网 发布:微信js接口安全域名 编辑:程序博客网 时间:2024/05/22 04:45

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 intcreate_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;}
create_and_bind() 包含了如何创建 IPv4 和 IPv6 套接字的代码块,它接受一字符串作为端口参数,并在 result 中返回一个 addrinfo 结构,

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 intmake_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 64intmain (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, 这意味着所有的数据已经被读取,可以返回主循环了。
Epoll服务器实例:
#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 1000void 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;}


1、epoll_create函数
函数声明: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当做一个模块,模块入口函数的代码如下:

[cpp] view plaincopy
  1. /************epoll模块入口函数***********/  
  2. static int __init eventpoll_init(void)  
  3. {  
  4.     struct sysinfo si;  
  5.   
  6.     si_meminfo(&si);  
  7.     /* 
  8.      * Allows top 4% of lomem to be allocated for epoll watches (per user). 
  9.      */  
  10.     max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /  
  11.         EP_ITEM_COST;  
  12.     BUG_ON(max_user_watches < 0);  
  13.   
  14.     /* 
  15.      * Initialize the structure used to perform epoll file descriptor 
  16.      * inclusion loops checks. 
  17.      */  
  18.     ep_nested_calls_init(&poll_loop_ncalls);  
  19.   
  20.     /* Initialize the structure used to perform safe poll wait head wake ups */  
  21.     ep_nested_calls_init(&poll_safewake_ncalls);  
  22.   
  23.     /* Initialize the structure used to perform file's f_op->poll() calls */  
  24.     ep_nested_calls_init(&poll_readywalk_ncalls);  
  25.   
  26.     /* Allocates slab cache used to allocate "struct epitem" items */  
  27.     epi_cache = kmem_cache_create("eventpoll_epi"sizeof(struct epitem),  
  28.             0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);  
  29.   
  30.     /* Allocates slab cache used to allocate "struct eppoll_entry" */  
  31.     pwq_cache = kmem_cache_create("eventpoll_pwq",  
  32.             sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);  
  33.   
  34.     return 0;  
  35. }  
  36. fs_initcall(eventpoll_init);  
  37. /*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变量。

下面我们来看具体实现:

[cpp] view plaincopy
  1. SYSCALL_DEFINE1(epoll_create, int, size)    //epoll_create函数带一个整型参数  
  2. {  
  3.     if (size <= 0)  
  4.         return -EINVAL;  
  5.   
  6.     return sys_epoll_create1(0);    //实际上是调用epoll_create1  
  7. }  
  8. /* 
  9.  * Open an eventpoll file descriptor. 
  10.  */  
  11. SYSCALL_DEFINE1(epoll_create1, int, flags)  
  12. {  
  13.     int error;  
  14.     struct eventpoll *ep = NULL;  
  15.   
  16.     /* Check the EPOLL_* constant for consistency.  */  
  17.     BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);  
  18.   
  19.     if (flags & ~EPOLL_CLOEXEC)  
  20.         return -EINVAL;  
  21.     /* 
  22.      * Create the internal data structure ("struct eventpoll"). 
  23.      */  
  24.     error = ep_alloc(&ep);//分配eventpoll结构体  
  25.     if (error < 0)  
  26.         return error;  
  27.     /* 
  28.      * Creates all the items needed to setup an eventpoll file. That is, 
  29.      * a file structure and a free file descriptor. 
  30.      */  
  31.     error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,  
  32.                  O_RDWR | (flags & O_CLOEXEC)); //创建与eventpoll结构体相对应的file结构,ep保存在file->private_data结构中  
  33.                                 //eventpoll_fops 为该文件所对应的操作函数  
  34.     if (error < 0)  
  35.         ep_free(ep);        //如果出错则释放该eventpoll结构体  
  36.   
  37.     return error;  
  38. }  

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结构作为参数传递。

下面来看看该函数的具体实现:

[cpp] view plaincopy
  1. /* 
  2.  * The following function implements the controller interface for 
  3.  * the eventpoll file that enables the insertion/removal/change of 
  4.  * file descriptors inside the interest set. 
  5.  * epfd为该epoll套接字实例,op表示对应的操作,fd表示新加入的套接字, 
  6.  * 结构体epoll_event 用于注册fd所感兴趣的事件和回传在fd上所发生待处理的事件 
  7.  */  
  8. SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,  
  9.         struct epoll_event __user *, event)  
  10. {  
  11.     int error;  
  12.     int did_lock_epmutex = 0;  
  13.     struct file *file, *tfile;  
  14.     struct eventpoll *ep;  
  15.     struct epitem *epi;  
  16.     struct epoll_event epds;  
  17.   
  18.     error = -EFAULT;  
  19.     if (ep_op_has_event(op) &&  
  20.         copy_from_user(&epds, event, sizeof(struct epoll_event)))  //copy_from_user将用户传入的event_poll拷贝到epds中,以供自己使用  
  21.         goto error_return;  
  22.   
  23.     /* Get the "struct file *" for the eventpoll file */  
  24.     error = -EBADF;  
  25.     file = fget(epfd); //获取该epoll套接字实例所对应的文件  
  26.     if (!file)  
  27.         goto error_return;  
  28.   
  29.     /* Get the "struct file *" for the target file */  
  30.     tfile = fget(fd);  
  31.     if (!tfile)  
  32.         goto error_fput;  
  33.   
  34.     /* The target file descriptor must support poll */  
  35.     error = -EPERM;  
  36.     if (!tfile->f_op || !tfile->f_op->poll)  
  37.         goto error_tgt_fput;  
  38.   
  39.     /* 
  40.      * We have to check that the file structure underneath the file descriptor 
  41.      * the user passed to us _is_ an eventpoll file. And also we do not permit 
  42.      * adding an epoll file descriptor inside itself. 
  43.      */  
  44.     error = -EINVAL;  
  45.     if (file == tfile || !is_file_epoll(file))  
  46.         goto error_tgt_fput;  
  47.   
  48.     /* 
  49.      * At this point it is safe to assume that the "private_data" contains 
  50.      * our own data structure. 
  51.      */  
  52.     ep = file->private_data;//获取epoll实例所对应的eventpoll结构体  
  53.   
  54.     /* 
  55.      * When we insert an epoll file descriptor, inside another epoll file 
  56.      * descriptor, there is the change of creating closed loops, which are 
  57.      * better be handled here, than in more critical paths. 
  58.      * 
  59.      * We hold epmutex across the loop check and the insert in this case, in 
  60.      * order to prevent two separate inserts from racing and each doing the 
  61.      * insert "at the same time" such that ep_loop_check passes on both 
  62.      * before either one does the insert, thereby creating a cycle. 
  63.      */  
  64.     if (unlikely(is_file_epoll(tfile) && op == EPOLL_CTL_ADD)) {  
  65.         mutex_lock(&epmutex);  
  66.         did_lock_epmutex = 1;  
  67.         error = -ELOOP;  
  68.         if (ep_loop_check(ep, tfile) != 0)  
  69.             goto error_tgt_fput;  
  70.     }  
  71.   
  72.   
  73.     mutex_lock(&ep->mtx);  
  74.   
  75.     /* 
  76.      * Try to lookup the file inside our RB tree, Since we grabbed "mtx" 
  77.      * above, we can be sure to be able to use the item looked up by 
  78.      * ep_find() till we release the mutex. 
  79.      * ep_find即从ep中的红黑树中根据tfile和fd来查找epitem 
  80.      */  
  81.     epi = ep_find(ep, tfile, fd);  
  82.       
  83.     error = -EINVAL;  
  84.     switch (op) {  
  85.     case EPOLL_CTL_ADD: //对应于socket上事件注册  
  86.         if (!epi) {     //红黑树中不存在这个节点  
  87.             epds.events |= POLLERR | POLLHUP;   //或操作,确保“出错、连接挂起”被当做感兴趣事件,因为底层有义务将出错信息返回给应用  
  88.             error = ep_insert(ep, &epds, tfile, fd);  
  89.         } else  
  90.             error = -EEXIST;  
  91.         break;  
  92.     case EPOLL_CTL_DEL: //删除  
  93.         if (epi)        //存在则删除这个节点,不存在则报错  
  94.             error = ep_remove(ep, epi);  
  95.         else  
  96.             error = -ENOENT;  
  97.         break;  
  98.     case EPOLL_CTL_MOD: //修改  
  99.         if (epi) {      //存在则修改该fd所对应的事件,不存在则报错  
  100.             epds.events |= POLLERR | POLLHUP;  
  101.             error = ep_modify(ep, epi, &epds);  
  102.         } else  
  103.             error = -ENOENT;  
  104.         break;  
  105.     }  
  106.     mutex_unlock(&ep->mtx);  
  107.   
  108. error_tgt_fput:  
  109.     if (unlikely(did_lock_epmutex))  
  110.         mutex_unlock(&epmutex);  
  111.   
  112.     fput(tfile);  
  113. error_fput:  
  114.     fput(file);  
  115. error_return:  
  116.   
  117.     return error;  
  118. }  

结合上一篇博文的内容,对于往epoll实例中添加新的套接字,其实现主要通过函数ep_insert来完成,本文先分析epoll_wait再回过头来分析ep_insert


四 epoll_wait的实现

epoll_wait等待epoll文件上的I/O事件发生,其代码如下:

[cpp] view plaincopy
  1. /* 
  2.  * Implement the event wait interface for the eventpoll file. It is the kernel 
  3.  * part of the user space epoll_wait(2). 
  4.  */  
  5. SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,  
  6.         int, maxevents, int, timeout)  
  7. {  
  8.     int error;  
  9.     struct file *file;  
  10.     struct eventpoll *ep;  
  11.   
  12.     /* The maximum number of event must be greater than zero */  
  13.     if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)  
  14.         return -EINVAL;  
  15.   
  16.     /* Verify that the area passed by the user is writeable */  
  17.     if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {  
  18.         error = -EFAULT;  
  19.         goto error_return;  
  20.     }  
  21.   
  22.     /* Get the "struct file *" for the eventpoll file */  
  23.     error = -EBADF;  
  24.     file = fget(epfd);  
  25.     if (!file)  
  26.         goto error_return;  
  27.   
  28.     /* 
  29.      * We have to check that the file structure underneath the fd 
  30.      * the user passed to us _is_ an eventpoll file. 
  31.      */  
  32.     error = -EINVAL;  
  33.     if (!is_file_epoll(file))  
  34.         goto error_fput;  
  35.   
  36.     /* 
  37.      * At this point it is safe to assume that the "private_data" contains 
  38.      * our own data structure. 
  39.      */  
  40.     ep = file->private_data;//获取struct eventpoll结构  
  41.   
  42.     /* Time to fish for events ... */  
  43.     error = ep_poll(ep, events, maxevents, timeout);//核心代码  
  44.   
  45. error_fput:  
  46.     fput(file);  
  47. error_return:  
  48.   
  49.     return error;  
  50. }  
可以看出该函数主要时通过epfd获取对应的struct eventpoll结构,然后调用ep_poll函数,下面来看ep_poll函数的实现:

[cpp] view plaincopy
  1. /** 
  2.  * ep_poll - Retrieves ready events, and delivers them to the caller supplied 
  3.  *           event buffer. 
  4.  * 
  5.  * @ep: Pointer to the eventpoll context. 
  6.  * @events: Pointer to the userspace buffer where the ready events should be 
  7.  *          stored. 
  8.  * @maxevents: Size (in terms of number of events) of the caller event buffer. 
  9.  * @timeout: Maximum timeout for the ready events fetch operation, in 
  10.  *           milliseconds. If the @timeout is zero, the function will not block, 
  11.  *           while if the @timeout is less than zero, the function will block 
  12.  *           until at least one event has been retrieved (or an error 
  13.  *           occurred). 
  14.  * 
  15.  * Returns: Returns the number of ready events which have been fetched, or an 
  16.  *          error code, in case of error. 
  17.  */  
  18. static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,  
  19.            int maxevents, long timeout)  
  20. {  
  21.     int res = 0, eavail, timed_out = 0;  
  22.     unsigned long flags;  
  23.     long slack = 0;  
  24.     wait_queue_t wait;  
  25.     ktime_t expires, *to = NULL;  
  26.     /* The call waits for a maximum time of timeout milliseconds.  Specifying a timeout of -1 makes epoll_wait()  
  27.      wait indefinitely,while specifying a timeout equal to zero makes epoll_wait() to return immediately even if no events 
  28.      are available (return code equal to zero).*/  
  29.     if (timeout > 0) {  
  30.         struct timespec end_time = ep_set_mstimeout(timeout);  
  31.   
  32.         slack = select_estimate_accuracy(&end_time);  
  33.         to = &expires;  
  34.         *to = timespec_to_ktime(end_time);  
  35.     } else if (timeout == 0) {  
  36.         /* 
  37.          * Avoid the unnecessary trip to the wait queue loop, if the 
  38.          * caller specified a non blocking operation. 
  39.          */  
  40.         timed_out = 1;  
  41.         spin_lock_irqsave(&ep->lock, flags);  
  42.         goto check_events;  
  43.     }  
  44.   
  45. fetch_events:  
  46.     spin_lock_irqsave(&ep->lock, flags);  
  47.     // 如果rdllist中还没有epitem时,就开始等待了  
  48.     if (!ep_events_available(ep)) {  
  49.         /* 
  50.          * We don't have any available event to return to the caller. 
  51.          * We need to sleep here, and we will be wake up by 
  52.          * ep_poll_callback() when events will become available. 
  53.          */  
  54.         // 初始化等待队列,等待队列项对应的线程即为当前线程  
  55.         init_waitqueue_entry(&wait, current);  
  56.         // 不用多说,先将当前线程挂到等待队列上,之后在调用schedule_timeout  
  57.             // 时,就开始了超时等待了  
  58.         __add_wait_queue_exclusive(&ep->wq, &wait);  
  59.   
  60.         for (;;) {  
  61.             /* 
  62.              * We don't want to sleep if the ep_poll_callback() sends us 
  63.              * a wakeup in between. That's why we set the task state 
  64.              * to TASK_INTERRUPTIBLE before doing the checks. 
  65.              */   
  66.              // 因为会被阻塞,这里先设置线程状态为可中断  
  67.             set_current_state(TASK_INTERRUPTIBLE);  
  68.             // 整个循环的核心,其实就在看rdllist中是否有数据,或者等待超时  
  69.                     // 应征了前面的说明,epoll_wait只需要等着收集数据即可  
  70.             if (ep_events_available(ep) || timed_out)  
  71.                 break;  
  72.             if (signal_pending(current)) {  
  73.                 res = -EINTR;  
  74.                 break;  
  75.             }  
  76.   
  77.             spin_unlock_irqrestore(&ep->lock, flags);  
  78.             if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))  
  79.                 timed_out = 1;  
  80.   
  81.             spin_lock_irqsave(&ep->lock, flags);  
  82.         }  
  83.         __remove_wait_queue(&ep->wq, &wait);  
  84.   
  85.         set_current_state(TASK_RUNNING);  
  86.     }  
  87. check_events:  
  88.     /* Is it worth to try to dig for events ? */  
  89.     eavail = ep_events_available(ep);  
  90.   
  91.     spin_unlock_irqrestore(&ep->lock, flags);  
  92.   
  93.     /* 
  94.      * Try to transfer events to user space. In case we get 0 events and 
  95.      * there's still timeout left over, we go trying again in search of 
  96.      * more luck. 
  97.      */  
  98.     if (!res && eavail &&  
  99.         !(res = ep_send_events(ep, events, maxevents)) && !timed_out)  
  100.         goto fetch_events;  
  101.   
  102.     return res;  
  103. }  

29-43行:主要是超时时间的处理,若超时时间为0,则直接检查有没有准备好的I/O事件,有则立即发送给用户空间去处理;若超时时间大于0,计算好精确的超时时间后,等待事件的发生,45-86行等待指定的时间直到有I/O事件出现;

54-58行:如果还没有I/O事件出现,则准备休眠。先初始化等待队列,把当前线程挂在该队列上,同时把这个队列挂在eventpoll结构的wq上,

60-82行:在指定的超时时间内循环检测有没有I/O事件发生,有事件发生、超时或者收到信号都会跳出循环。

83行:运行到此处有I/O事件发生,不用再等待,则移除该队列