概述

在阅读Android的Looper代码时需要对epoll进行了解,这里整理了一下epoll相关信息.
epoll提供了一种IO访问服务,能够同时监听多个文件描述符,监听文件数据变化,并且不会因监听的文件数量增加而导致效率急剧下降的情况,在绝大多数情况下,优于select和poll。(在监听数量小于一定数值时,select在内存占用和速度上是比epoll好的)

epoll接口

在sys/epoll.h文件中可以看到epoll提供的五个接口

int epoll_create(int);
int epoll_create1(int);
int epoll_ctl(int, int, int, struct epoll_event*);
int epoll_wait(int, struct epoll_event*, int, int);
int epoll_pwait(int, struct epoll_event*, int, int, const sigset_t*);

这些接口说明如下

epoll_create和epoll_create1

int epoll_create(int size)接口用来创建一个epoll实例。参数size在老版本内核中,用来指定所能监视文件描述符的最大值,目前所能监听文件描述符的最大值由系统动态分配(不依赖size的大小),但为了兼容老版本size需要指定一个大于0的值。
int epoll_create1(int flags)接口与epoll_create接口功能一致,额外提供一个可以设定的标志,例如EPOLL_CLOEXEC,这个标志与open接口中O_CLOEXEC的标志相同的功能。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)是epoll中的控制接口。参数epfd是通过epoll_create创建的epoll实例,参数op是epoll操作符,参数fd是将要被监视的文件描述符,参数event是fd的配置。其中第二个参数op有如下三个有效值

  • EPOLL_CTL_ADD:注册新的文件描述符fd到epfd中。
  • EPOLL_CTL_MOD:修改已经注册的fd。
  • EPOLL_CTL_DEL:将fd从epfd中删除。

参数event用来配置fd,相关结构体如下

··· 
 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 */ 
 };


···
data用来指向数据,events可以是下面几个中的一个或多个的组合
···
#define EPOLLIN 0x00000001 //fd可以被read接口操作
#define EPOLLPRI 0x00000002 //有特殊的情况发生,多数是指有数据到来
#define EPOLLOUT 0x00000004 //fd可以被write接口操作
#define EPOLLERR 0x00000008 //fd发生错误
#define EPOLLHUP 0x00000010 //fd被挂起
#define EPOLLWAKEUP 0x20000000 //添加此标志用来防止epoll时间准备就绪时休眠
#define EPOLLONESHOT 0x40000000 //设置fd为单次触发
#define EPOLLET 0x80000000 //设置为ET模式
···

epoll_wait与epoll_pwait

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout)接口用来等待epfd所监控的文件发生变化,epfd表示epoll实例,events包含了所有返回的事件,maxevents表示有多少个事件,timeout表示阻塞的超时时间,单位毫秒,-1表示永远等待。
int epoll_pwait(int epfd, struct epoll_event *events,int maxevents, int timeout,const sigset_t *sigmask)接口比epoll_wait多了最后一个参数sigmask,在等待所监控的文件发生变化的同时等待指定的系统信号。

epoll实例

epoll的使用非常简单,只需要三部曲(创建,添加,监听)即可完成,可以man一下epoll.下面是一个测试例子.

/*    test in ubuntu14.04 by helenxr
build:
    gcc epoll.c -lpthread
run:
    ./a.out
    main enter while.
    thread_loop:thread_1
    [1526290930077129] write num=2
    [1526290930077147] get poll data[1/1],events:1,num:2
    [1526290935077602] write num=3
    [1526290935077649] get poll data[1/1],events:1,num:3
    [1526290940079050] write num=4
    [1526290940079100] get poll data[1/1],events:1,num:4
*/

#include <stdio.h>
#include <pthread.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <inttypes.h>
#include <sys/time.h>
#define EPOLL_MAX_SIZE        50

#define RET_ERROR_RETURN(x)    \
if(ret)                \
{                \
  printf("%s:error ret = %d\n",x,ret); \
  return -1;                    \
}                              \

int g_event_fd = -1;
void *thread_loop(void* param){
  uint64_t num = 2;
  struct timeval tv;
  printf("thread_loop:%s\n",(char *)param);
  while(1){
    if(g_event_fd < 0){
        printf("%s:g_event_fd < 0,wait...\n",__FUNCTION__);
        sleep(1);
        continue;
    }
    //向文件描述符里写入数据
    if(write(g_event_fd,&num,sizeof(uint64_t)) != sizeof(uint64_t)){
      printf("%s:write error!\n",__FUNCTION__);    
      return ;
    }
    gettimeofday(&tv,NULL);
    printf("[%ld] write num=%"PRIu64"\n",tv.tv_sec*1000000 + tv.tv_usec,num++);    
    sleep(5);
  }
}

int main(int argc,char* argv[]){
  pthread_t thread_1,thread_2;
  struct epoll_event event,event_array[EPOLL_MAX_SIZE];
  int epoll_fd;
  int ret;
  struct timeval tv;

  //创建文件描述符
  g_event_fd =  eventfd(0,EFD_NONBLOCK | EFD_CLOEXEC);
  if(g_event_fd < 0){
    printf("g_event_fd error\n");
    return -1;
  }

  //步骤一:创建epoll
  epoll_fd = epoll_create(EPOLL_MAX_SIZE);
  if(epoll_fd < 0){
    printf("epoll fd error\n");
    return -1;
  }
  event.events = EPOLLIN;
  event.data.fd = g_event_fd;
  //步骤二:将文件描述符添加到epoll当中.
  if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,g_event_fd,&event) < 0){
    printf("epoll ctl:add g_event_fd error");
    return -1;
  }
  ret = pthread_create(&thread_1,NULL,thread_loop,"thread_1");
  if(ret){
    printf("create thread_1 error\n");
    return -1;
  }
  printf("main enter while.\n");
  while(1){
    //步骤三:开始监听该文件描述符
    int event_count = epoll_wait(epoll_fd,event_array,EPOLL_MAX_SIZE,-1); 
    if(event_count == -1){
    printf("epoll_wait error\n");
        return -1;
    }else if (event_count == 0){
      printf("epoll_wait: time out.\n");
    }
    int i;
    uint64_t num;
    for(i=0;i<event_count;i++){
    if(event_array[i].data.fd == g_event_fd){
          if(read(g_event_fd,&num,sizeof(uint64_t)) != sizeof(uint64_t)){
            printf("read:error\n");
            return -1;
          }
            gettimeofday(&tv,NULL);
            printf("[%ld] get poll data[%d/%d],events:%"PRIu32",num:%"PRIu64"\n",tv.tv_sec*1000000 + tv.tv_usec,i+1,event_count,event_array[i].events,num);
          }else{
            printf("epoll:warning get other fd(%d)\n",event_array[i].data.fd);
          }
    }
  }

  ret = pthread_join(thread_1,NULL);
  RET_ERROR_RETURN("thread_1 join");
  close(g_event_fd);
  close(epoll_fd);
  printf("main:end\n");
  return 0;
}

参考资料

epoll
caveats-of-select-poll-vs-epoll-reactors-in-twisted