epoll原理与应用详解

50 篇文章 0 订阅
28 篇文章 0 订阅

本文转自: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类似,都向用户空间提供了自己的文件描述符来进行操作。

[cpp]  view plain copy
  1. int epoll_create(int size);  

创建一个epoll的句柄,size用来告诉内核需要监听的数目一共有多大。当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close() 关闭,否则可能导致fd被耗尽。

  
  
  1. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  

epoll的事件注册函数,第一个参数是 epoll_create() 的返回值,第二个参数表示动作,使用如下三个宏来表示:

[cpp]  view plain copy
  1. EPOLL_CTL_ADD    //注册新的fd到epfd中;  
  2. EPOLL_CTL_MOD    //修改已经注册的fd的监听事件;  
  3. EPOLL_CTL_DEL    //从epfd中删除一个fd;  

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event 结构如下:

[cpp]  view plain copy
  1. typedef union epoll_data  
  2. {  
  3.   void        *ptr;  
  4.   int          fd;  
  5.   __uint32_t   u32;  
  6.   __uint64_t   u64;  
  7. } epoll_data_t;  
  8.   
  9. struct epoll_event {  
  10. __uint32_t events; /* Epoll events */  
  11. epoll_data_t data; /* User data variable */  
  12. };  

events 可以是以下几个宏的集合:

[cpp]  view plain copy
  1. EPOLLIN     //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);  
  2. EPOLLOUT    //表示对应的文件描述符可以写;  
  3. EPOLLPRI    //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);  
  4. EPOLLERR    //表示对应的文件描述符发生错误;  
  5. EPOLLHUP    //表示对应的文件描述符被挂断;  
  6. EPOLLET     //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。  
  7. EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。  

当对方关闭连接(FIN), EPOLLERR,都可以认为是一种EPOLLIN事件,在read的时候分别有0,-1两个返回值。

[cpp]  view plain copy
  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 套接字:

[cpp]  view plain copy
  1. static int  
  2. create_and_bind (char *port)  
  3. {  
  4.   struct addrinfo hints;  
  5.   struct addrinfo *result, *rp;  
  6.   int s, sfd;  
  7.   
  8.   memset (&hints, 0, sizeof (struct addrinfo));  
  9.   hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
  10.   hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  
  11.   hints.ai_flags = AI_PASSIVE;     /* All interfaces */  
  12.   
  13.   s = getaddrinfo (NULL, port, &hints, &result);  
  14.   if (s != 0)  
  15.     {  
  16.       fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));  
  17.       return -1;  
  18.     }  
  19.   
  20.   for (rp = result; rp != NULL; rp = rp->ai_next)  
  21.     {  
  22.       sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);  
  23.       if (sfd == -1)  
  24.         continue;  
  25.       s = bind (sfd, rp->ai_addr, rp->ai_addrlen);  
  26.       if (s == 0)  
  27.         {  
  28.           /* We managed to bind successfully! */  
  29.           break;  
  30.         }  
  31.   
  32.       close (sfd);  
  33.     }  
  34.   if (rp == NULL)  
  35.     {  
  36.       fprintf (stderr, "Could not bind\n");  
  37.       return -1;  
  38.     }  
  39.   freeaddrinfo (result);  
  40.   return sfd;  
  41. }  
create_and_bind () 包含了如何创建 IPv4 和 IPv6 套接字的代码块,它接受一字符串作为端口参数,并在 result 中返回一个 addrinfo 结构,

[cpp]  view plain copy
  1. struct addrinfo  
  2. {  
  3.   int              ai_flags;  
  4.   int              ai_family;  
  5.   int              ai_socktype;  
  6.   int              ai_protocol;  
  7.   size_t           ai_addrlen;  
  8.   struct sockaddr *ai_addr;  
  9.   char            *ai_canonname;  
  10.   struct addrinfo *ai_next;  
  11. };  

如果函数成功则返回套接字,如果失败,则返回 -1, 

下面,我们将一个套接字设置为非阻塞形式,函数如下:

[cpp]  view plain copy
  1. static int  
  2. make_socket_non_blocking (int sfd)  
  3. {  
  4.   int flags, s;  
  5.   
  6.   flags = fcntl (sfd, F_GETFL, 0);  
  7.   if (flags == -1)  
  8.     {  
  9.       perror ("fcntl");  
  10.       return -1;  
  11.     }  
  12.   
  13.   flags |= O_NONBLOCK;  
  14.   s = fcntl (sfd, F_SETFL, flags);  
  15.   if (s == -1)  
  16.     {  
  17.       perror ("fcntl");  
  18.       return -1;  
  19.     }  
  20.   
  21.   return 0;  
  22. }  
接下来,便是主函数代码,主要用于事件循环:

[cpp]  view plain copy
  1. #define MAXEVENTS 64  
  2.   
  3. int  
  4. main (int argc, char *argv[])  
  5. {  
  6.   int sfd, s;  
  7.   int efd;  
  8.   struct epoll_event event;  
  9.   struct epoll_event *events;  
  10.   
  11.   if (argc != 2)  
  12.     {  
  13.       fprintf (stderr, "Usage: %s [port]\n", argv[0]);  
  14.       exit (EXIT_FAILURE);  
  15.     }  
  16.   
  17.   sfd = create_and_bind (argv[1]);  
  18.   if (sfd == -1)  
  19.     abort ();  
  20.   
  21.   s = make_socket_non_blocking (sfd);  
  22.   if (s == -1)  
  23.     abort ();  
  24.   
  25.   s = listen (sfd, SOMAXCONN);  
  26.   if (s == -1)  
  27.     {  
  28.       perror ("listen");  
  29.       abort ();  
  30.     }  
  31.   
  32.   efd = epoll_create1 (0);  
  33.   if (efd == -1)  
  34.     {  
  35.       perror ("epoll_create");  
  36.       abort ();  
  37.     }  
  38.   
  39.   event.data.fd = sfd;  
  40.   event.events = EPOLLIN | EPOLLET;  
  41.   s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
  42.   if (s == -1)  
  43.     {  
  44.       perror ("epoll_ctl");  
  45.       abort ();  
  46.     }  
  47.   
  48.   /* Buffer where events are returned */  
  49.   events = calloc (MAXEVENTS, sizeof event);  
  50.   
  51.   /* The event loop */  
  52.   while (1)  
  53.     {  
  54.       int n, i;  
  55.   
  56.       n = epoll_wait (efd, events, MAXEVENTS, -1);  
  57.       for (i = 0; i < n; i++)  
  58.     {  
  59.       if ((events[i].events & EPOLLERR) ||  
  60.               (events[i].events & EPOLLHUP) ||  
  61.               (!(events[i].events & EPOLLIN)))  
  62.         {  
  63.               /* An error has occured on this fd, or the socket is not 
  64.                  ready for reading (why were we notified then?) */  
  65.           fprintf (stderr, "epoll error\n");  
  66.           close (events[i].data.fd);  
  67.           continue;  
  68.         }  
  69.   
  70.       else if (sfd == events[i].data.fd)  
  71.         {  
  72.               /* We have a notification on the listening socket, which 
  73.                  means one or more incoming connections. */  
  74.               while (1)  
  75.                 {  
  76.                   struct sockaddr in_addr;  
  77.                   socklen_t in_len;  
  78.                   int infd;  
  79.                   char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
  80.   
  81.                   in_len = sizeof in_addr;  
  82.                   infd = accept (sfd, &in_addr, &in_len);  
  83.                   if (infd == -1)  
  84.                     {  
  85.                       if ((errno == EAGAIN) ||  
  86.                           (errno == EWOULDBLOCK))  
  87.                         {  
  88.                           /* We have processed all incoming 
  89.                              connections. */  
  90.                           break;  
  91.                         }  
  92.                       else  
  93.                         {  
  94.                           perror ("accept");  
  95.                           break;  
  96.                         }  
  97.                     }  
  98.   
  99.                   s = getnameinfo (&in_addr, in_len,  
  100.                                    hbuf, sizeof hbuf,  
  101.                                    sbuf, sizeof sbuf,  
  102.                                    NI_NUMERICHOST | NI_NUMERICSERV);  
  103.                   if (s == 0)  
  104.                     {  
  105.                       printf("Accepted connection on descriptor %d "  
  106.                              "(host=%s, port=%s)\n", infd, hbuf, sbuf);  
  107.                     }  
  108.   
  109.                   /* Make the incoming socket non-blocking and add it to the 
  110.                      list of fds to monitor. */  
  111.                   s = make_socket_non_blocking (infd);  
  112.                   if (s == -1)  
  113.                     abort ();  
  114.   
  115.                   event.data.fd = infd;  
  116.                   event.events = EPOLLIN | EPOLLET;  
  117.                   s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);  
  118.                   if (s == -1)  
  119.                     {  
  120.                       perror ("epoll_ctl");  
  121.                       abort ();  
  122.                     }  
  123.                 }  
  124.               continue;  
  125.             }  
  126.           else  
  127.             {  
  128.               /* We have data on the fd waiting to be read. Read and 
  129.                  display it. We must read whatever data is available 
  130.                  completely, as we are running in edge-triggered mode 
  131.                  and won't get a notification again for the same 
  132.                  data. */  
  133.               int done = 0;  
  134.   
  135.               while (1)  
  136.                 {  
  137.                   ssize_t count;  
  138.                   char buf[512];  
  139.   
  140.                   count = read (events[i].data.fd, buf, sizeof buf);  
  141.                   if (count == -1)  
  142.                     {  
  143.                       /* If errno == EAGAIN, that means we have read all 
  144.                          data. So go back to the main loop. */  
  145.                       if (errno != EAGAIN)  
  146.                         {  
  147.                           perror ("read");  
  148.                           done = 1;  
  149.                         }  
  150.                       break;  
  151.                     }  
  152.                   else if (count == 0)  
  153.                     {  
  154.                       /* End of file. The remote has closed the 
  155.                          connection. */  
  156.                       done = 1;  
  157.                       break;  
  158.                     }  
  159.   
  160.                   /* Write the buffer to standard output */  
  161.                   s = write (1, buf, count);  
  162.                   if (s == -1)  
  163.                     {  
  164.                       perror ("write");  
  165.                       abort ();  
  166.                     }  
  167.                 }  
  168.   
  169.               if (done)  
  170.                 {  
  171.                   printf ("Closed connection on descriptor %d\n",  
  172.                           events[i].data.fd);  
  173.   
  174.                   /* Closing the descriptor will make epoll remove it 
  175.                      from the set of descriptors which are monitored. */  
  176.                   close (events[i].data.fd);  
  177.                 }  
  178.             }  
  179.         }  
  180.     }  
  181.   
  182.   free (events);  
  183.   
  184.   close (sfd);  
  185.   
  186.   return EXIT_SUCCESS;  
  187. }  

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服务器实例:
[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <sys/socket.h>  
  3. #include <sys/epoll.h>  
  4. #include <netinet/in.h>  
  5. #include <arpa/inet.h>  
  6. #include <fcntl.h>  
  7. #include <unistd.h>  
  8. #include <stdio.h>  
  9. #include <errno.h>  
  10.   
  11. using namespace std;  
  12.   
  13. #define MAXLINE 5  
  14. #define OPEN_MAX 100  
  15. #define LISTENQ 20  
  16. #define SERV_PORT 5000  
  17. #define INFTIM 1000  
  18.   
  19. void setnonblocking(int sock)  
  20. {  
  21.     int opts;  
  22.     opts=fcntl(sock,F_GETFL);  
  23.     if(opts<0)  
  24.     {  
  25.         perror("fcntl(sock,GETFL)");  
  26.         exit(1);  
  27.     }  
  28.     opts = opts|O_NONBLOCK;  
  29.     if(fcntl(sock,F_SETFL,opts)<0)  
  30.     {  
  31.         perror("fcntl(sock,SETFL,opts)");  
  32.         exit(1);  
  33.     }  
  34. }  
  35. int main(int argc, char* argv[])  
  36. {  
  37.     int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;  
  38.     ssize_t n;  
  39.     char line[MAXLINE];  
  40.     socklen_t clilen;  
  41.     if ( 2 == argc )  
  42.     {  
  43.         if( (portnumber = atoi(argv[1])) < 0 )  
  44.         {  
  45.             fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);  
  46.             return 1;  
  47.         }  
  48.     }  
  49.     else  
  50.     {  
  51.         fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);  
  52.         return 1;  
  53.     }  
  54.     //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件  
  55.     struct epoll_event ev,events[20];  
  56.     //生成用于处理accept的epoll专用的文件描述符  
  57.     epfd=epoll_create(256);  
  58.     struct sockaddr_in clientaddr;  
  59.     struct sockaddr_in serveraddr;  
  60.     listenfd = socket(AF_INET, SOCK_STREAM, 0);  
  61.     //把socket设置为非阻塞方式  
  62.   
  63.     //setnonblocking(listenfd);  
  64.   
  65.     //设置与要处理的事件相关的文件描述符  
  66.   
  67.     ev.data.fd=listenfd;  
  68.     //设置要处理的事件类型  
  69.   
  70.     ev.events=EPOLLIN|EPOLLET;  
  71.     //ev.events=EPOLLIN;  
  72.   
  73.     //注册epoll事件  
  74.   
  75.     epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);  
  76.     bzero(&serveraddr, sizeof(serveraddr));  
  77.     serveraddr.sin_family = AF_INET;  
  78.     char *local_addr="127.0.0.1";  
  79.     inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);  
  80.   
  81.     serveraddr.sin_port=htons(portnumber);  
  82.     bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));  
  83.     listen(listenfd, LISTENQ);  
  84.     maxi = 0;  
  85.     for ( ; ; ) {  
  86.         //等待epoll事件的发生  
  87.         nfds=epoll_wait(epfd,events,20,500);  
  88.         //处理所发生的所有事件  
  89.         for(i=0;i<nfds;++i)  
  90.         {  
  91.             if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。  
  92.             {  
  93.                 connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);  
  94.                 if(connfd<0){  
  95.                     perror("connfd<0");  
  96.                     exit(1);  
  97.                 }  
  98.                 //setnonblocking(connfd);  
  99.                 char *str = inet_ntoa(clientaddr.sin_addr);  
  100.                 cout << "accapt a connection from " << str << endl;  
  101.                 //设置用于读操作的文件描述符  
  102.                 ev.data.fd=connfd;  
  103.                 //设置用于注测的读操作事件  
  104.                 ev.events=EPOLLIN|EPOLLET;  
  105.                 //ev.events=EPOLLIN;  
  106.                 //注册ev  
  107.                 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);  
  108.             }  
  109.             else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。  
  110.             {  
  111.                 cout << "EPOLLIN" << endl;  
  112.                 if ( (sockfd = events[i].data.fd) < 0)  
  113.                     continue;  
  114.                 if ( (n = read(sockfd, line, MAXLINE)) < 0) {  
  115.                     if (errno == ECONNRESET) {  
  116.                         close(sockfd);  
  117.                         events[i].data.fd = -1;  
  118.                     } else  
  119.                         std::cout<<"readline error"<<std::endl;  
  120.                 } else if (n == 0) {  
  121.                     close(sockfd);  
  122.                     events[i].data.fd = -1;  
  123.                 }  
  124.                 line[n] = '/0';  
  125.                 cout << "read " << line << endl;  
  126.                 //设置用于写操作的文件描述符  
  127.                 ev.data.fd=sockfd;  
  128.                 //设置用于注测的写操作事件  
  129.                 ev.events=EPOLLOUT|EPOLLET;  
  130.                 //修改sockfd上要处理的事件为EPOLLOUT  
  131.                 //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);  
  132.             }  
  133.             else if(events[i].events&EPOLLOUT) // 如果有数据发送  
  134.             {  
  135.                 sockfd = events[i].data.fd;  
  136.                 write(sockfd, line, n);  
  137.                 //设置用于读操作的文件描述符  
  138.                 ev.data.fd=sockfd;  
  139.                 //设置用于注测的读操作事件  
  140.                 ev.events=EPOLLIN|EPOLLET;  
  141.                 //修改sockfd上要处理的事件为EPOLIN  
  142.                 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);  
  143.             }  
  144.         }  
  145.     }  
  146.     return 0;  
  147. }  


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()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:

[cpp]  view plain copy
  1. while(rs)  
  2. {  
  3.  buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);  
  4.  if(buflen < 0)  
  5.   {  
  6.    // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读  
  7.    // 在这里就当作是该次事件已处理处.  
  8.    if(errno == EAGAIN)  
  9.     break;  
  10.    else  
  11.     return;  
  12.    }  
  13.   else if(buflen == 0)  
  14.    {  
  15.     // 这里表示对端的socket已正常关闭.  
  16.    }  
  17.   if(buflen == sizeof(buf)  
  18.     rs = 1;   // 需要再次读取  
  19.   else  
  20.     rs = 0;  
  21. }  

还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法. 

[cpp]  view plain copy
  1. ssize_t socket_send(int sockfd, const char*buffer, size_t buflen)  
  2. {  
  3.  ssize_t tmp;  
  4.  size_t total = buflen;  
  5.  const char *p = buffer;  
  6.  while(1)  
  7.   {  
  8.    tmp = send(sockfd, p, total, 0);  
  9.    if(tmp < 0)  
  10.     {  
  11.      // 当send收到信号时,可以继续写,但这里返回-1.  
  12.      if(errno == EINTR)  
  13.        return -1;  
  14.       // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,  
  15.      // 在这里做延时后再重试.  
  16.      if(errno == EAGAIN)  
  17.      {  
  18.        usleep(1000);  
  19.        continue;  
  20.      }  
  21.      return -1;  
  22.     }  
  23.    if((size_t)tmp == total)  
  24.      return buflen;  
  25.    total -= tmp;  
  26.     p+= tmp;  
  27.   }  
  28.    
  29.  return tmp;  
  30. }  

实例代码:

[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <sys/socket.h>  
  3. #include <sys/epoll.h>  
  4. #include <netinet/in.h>  
  5. #include <arpa/inet.h>  
  6. #include <fcntl.h>  
  7. #include <unistd.h>  
  8. #include <stdio.h>  
  9. #include <pthread.h>  
  10. #include <errno.h>  
  11.    
  12. #define MAXLINE 10   
  13. #define OPEN_MAX 100   
  14. #define LISTENQ 20   
  15. #define SERV_PORT 5555   
  16. #define INFTIM 1000  
  17.    
  18. //线程池任务队列结构体   
  19. struct task  
  20. {  
  21.      int fd; //需要读写的文件描述符  
  22.      struct task *next; //下一个任务  
  23. };  
  24.    
  25. //用于读写两个的两个方面传递参数  
  26. struct user_data  
  27. {  
  28.      int fd;  
  29.      unsigned int n_size;  
  30.      char line[MAXLINE];  
  31. };  
  32.    
  33. //线程的任务函数  
  34. void * readtask(void *args);  
  35. void * writetask(void *args);  
  36. //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件  
  37. struct epoll_event ev, events[20];  
  38. int epfd;  
  39. pthread_mutex_t mutex;  
  40. pthread_cond_t cond1;  
  41. struct task *readhead = NULL, *readtail =NULL, *writehead = NULL;  
  42.   
  43. void setnonblocking(int sock)  
  44. {  
  45.      int opts;  
  46.      opts = fcntl(sock, F_GETFL);  
  47.      if(opts < 0)  
  48.      {  
  49.                perror("fcntl(sock,GETFL)");  
  50.                exit(1);  
  51.      }  
  52.      opts= opts | O_NONBLOCK;  
  53.      if(fcntl(sock, F_SETFL, opts) < 0)  
  54.      {  
  55.                perror("fcntl(sock,SETFL,opts)");  
  56.                exit(1);  
  57.      }  
  58. }  
  59. int main()  
  60. {  
  61.      int i, maxi, listenfd, connfd, sockfd, nfds;  
  62.      pthread_t tid1, tid2;  
  63.   
  64.      struct task *new_task = NULL;  
  65.      struct user_data *rdata = NULL;  
  66.      socklen_t clilen;  
  67.   
  68.      pthread_mutex_init(&mutex,NULL);  
  69.      pthread_cond_init(&cond1,NULL);  
  70.      //初始化用于读线程池的线程  
  71.   
  72.      pthread_create(&tid1,NULL, readtask, NULL);  
  73.      pthread_create(&tid2,NULL, readtask, NULL);  
  74.   
  75.      //生成用于处理accept的epoll专用的文件描述符  
  76.      epfd= epoll_create(256);  
  77.   
  78.      structsockaddr_in clientaddr;  
  79.      structsockaddr_in serveraddr;  
  80.      listenfd= socket(AF_INET, SOCK_STREAM, 0);  
  81.      //把socket设置为非阻塞方式  
  82.      setnonblocking(listenfd);  
  83.      //设置与要处理的事件相关的文件描述符  
  84.      ev.data.fd= listenfd;  
  85.      //设置要处理的事件类型  
  86.      ev.events= EPOLLIN | EPOLLET;  
  87.      //注册epoll事件  
  88.      epoll_ctl(epfd,EPOLL_CTL_ADD, listenfd, &ev);  
  89.      bzero(&serveraddr,sizeof(serveraddr));  
  90.      serveraddr.sin_family= AF_INET;  
  91.      char*local_addr = "200.200.200.222";  
  92.      inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);  
  93.      serveraddr.sin_port= htons(SERV_PORT);  
  94.      bind(listenfd,(sockaddr *) &serveraddr, sizeof(serveraddr));  
  95.      listen(listenfd,LISTENQ);  
  96.      maxi= 0;  
  97.   
  98.      for(;;)  
  99.      {  
  100.        //等待epoll事件的发生  
  101.        nfds= epoll_wait(epfd, events, 20, 500);  
  102.        //处理所发生的所有事件  
  103.        for(i = 0; i < nfds; ++i)  
  104.        {  
  105.             if(events[i].data.fd == listenfd)  
  106.             {  
  107.                  connfd= accept(listenfd, (sockaddr *) &clientaddr, &clilen);  
  108.                  if(connfd < 0)  
  109.                  {  
  110.                            perror("connfd<0");  
  111.                            exit(1);  
  112.                  }  
  113.                  setnonblocking(connfd);  
  114.                  char*str = inet_ntoa(clientaddr.sin_addr);  
  115.                  std::cout<< "connec_ from >>" << str << std::endl;  
  116.                  //设置用于读操作的文件描述符  
  117.                  ev.data.fd= connfd;  
  118.                  //设置用于注测的读操作事件  
  119.                  ev.events= EPOLLIN | EPOLLET;  
  120.                  //注册ev  
  121.                  epoll_ctl(epfd,EPOLL_CTL_ADD, connfd, &ev);  
  122.             } else  
  123.                  if(events[i].events & EPOLLIN)  
  124.                  {  
  125.                        printf("reading!\n");  
  126.                        if((sockfd = events[i].data.fd) < 0) continue;  
  127.                        new_task= new task();  
  128.                        new_task->fd= sockfd;  
  129.                        new_task->next= NULL;  
  130.                        //添加新的读任务  
  131.                        pthread_mutex_lock(&mutex);  
  132.                        if(readhead == NULL)  
  133.                        {  
  134.                             readhead= new_task;  
  135.                             readtail= new_task;  
  136.                        }else  
  137.                        {  
  138.                             readtail->next= new_task;  
  139.                             readtail= new_task;  
  140.                        }  
  141.                        //唤醒所有等待cond1条件的线程  
  142.                        pthread_cond_broadcast(&cond1);  
  143.                        pthread_mutex_unlock(&mutex);  
  144.                  } else  
  145.                        if(events[i].events & EPOLLOUT)  
  146.                        {  
  147.                             rdata= (struct user_data *) events[i].data.ptr;  
  148.                             sockfd= rdata->fd;  
  149.                             write(sockfd,rdata->line, rdata->n_size);  
  150.                             deleterdata;  
  151.                             //设置用于读操作的文件描述符  
  152.                             ev.data.fd= sockfd;  
  153.                             //设置用于注测的读操作事件  
  154.                             ev.events= EPOLLIN | EPOLLET;  
  155.                             //修改sockfd上要处理的事件为EPOLIN  
  156.                             epoll_ctl(epfd,EPOLL_CTL_MOD, sockfd, &ev);  
  157.                        }  
  158.        }  
  159.  }  
  160. }  
  161. void * readtask(void *args)  
  162. {  
  163.      int fd = -1;  
  164.      unsigned int n;  
  165.      //用于把读出来的数据传递出去  
  166.      struct user_data *data = NULL;  
  167.      while(1)  
  168.      {  
  169.        pthread_mutex_lock(&mutex);  
  170.        //等待到任务队列不为空  
  171.        while(readhead == NULL)  
  172.                 pthread_cond_wait(&cond1,&mutex);  
  173.        fd= readhead->fd;  
  174.        //从任务队列取出一个读任务  
  175.        struct task *tmp = readhead;  
  176.        readhead= readhead->next;  
  177.        delete tmp;  
  178.        pthread_mutex_unlock(&mutex);  
  179.        data = new user_data();  
  180.        data->fd= fd;  
  181.        if((n = read(fd, data->line, MAXLINE)) < 0)  
  182.        {  
  183.             if(errno == ECONNRESET)  
  184.             {  
  185.                      close(fd);  
  186.             }else  
  187.             std::cout<< "readline error" << std::endl;  
  188.             if(data != NULL) delete data;  
  189.        }else  
  190.             if(n == 0)  
  191.             {  
  192.                  close(fd);  
  193.                  printf("Clientclose connect!\n");  
  194.                  if(data != NULL) delete data;  
  195.             }else  
  196.             {  
  197.                  data->n_size= n;  
  198.                  //设置需要传递出去的数据  
  199.                  ev.data.ptr= data;  
  200.                  //设置用于注测的写操作事件  
  201.                  ev.events= EPOLLOUT | EPOLLET;  
  202.                  //修改sockfd上要处理的事件为EPOLLOUT  
  203.                  epoll_ctl(epfd,EPOLL_CTL_MOD, fd, &ev);  
  204.             }  
  205.      }  
  206. }  

补充的细节知识

本文基于2.6.39内核

第一部分已经大致的介绍了linux在实现EPOLL模型所用到的主要数据结构,接下来这篇就是主要分析怎样通过这些数据结构来实现EPOLL的功能。

一。模块的加载

linux把EPOLL当做一个模块,模块入口函数的代码如下:

[cpp]  view plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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事件发生,不用再等待,则移除该队列



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值