概述
在阅读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