epoll
is a scalable I/O event notification . 它在内核2.5.44首次引入, 用来取代POSIX select和poll系统调用。
说到select和poll,也就是IO多路转接,在APUE 14.5节有详细介绍:
转自http://blog.youkuaiyun.com/delphiwcdj/article/details/8827659
------------------------------------------------
当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞I/O:
但是如果需要从多个描述符读,上面的方法是不行的,如果仍旧使用阻塞I/O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。针对这个问题,有哪些处理方法呢?
while ( (n = read(STDIN_FILENO, buf,BUFSIZ)) > 0 ) { if( write(STDOUT_FILENO, buf, n) != n ) { printf("write error\n"); return1; } }
方法1:
用fork将一个进程变成两个进程,每个进程处理一条数据通路,即每个进程都执行阻塞read。
问题是:在进程终止时,使程序变得复杂。
方法2:
使用多线程单进程,这避免了终止的复杂性。
问题是:但却要求处理线程之间的同步,依然没有摆脱复杂性。
方法3:
使用单进程非阻塞I/O读数据,即轮询(polling)的方式。将多个描述符都设置为非阻塞的,对第一个描述符发一个read,如果该输入上有数 据,则读数据并处理它;如果无数据可读,则read立即返回。然后对第二个描述符做同样的处理。所有描述符处理完之后,等待若干秒然后再进行轮询。
问题是:浪费CPU时间,因为大多数时间实际是无数据可读的,但仍花费时间不断反复执行read系统调用,在多任务系统中应当避免使用这种方法。
方法4:
异步I/O(asynchronous I/O),其思想是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。即,异步I/O模型是由内核通知我们I/O操作合适完成。
问题是:并非所有系统都支持这种机制。
方法5:
使用I/O多路转接(I/Omultiplexing)。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I /O时,该函数才返回,在返回时,此函数告诉进程哪些描述符已准备好可以进行I/O。poll、pselect和select这三个函数使我们能够执行I /O多路转接。即,我们使用select可以等待多个描述符就绪。
-----------------------------------
在监控文件数量n很大的时候,select和poll的算法复杂度为O(n),而epoll的算法复杂度为O(1)。因而性能会提高很多。
epoll只有三个API[1] :
int epoll_create(int size);
Creates an epoll object and returns its file descriptor.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
Controls (configures) which file descriptors are watched by this object, and for which events.
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Waits for any of the registered events, until at least one occurs or the timeout elapses.
Glusterfs使用event_pool封装了epoll。相关代码在libglusterfs/src/event-epoll.c
主要有如下几个函数:
.new = event_pool_new_epoll,
.event_register = event_register_epoll,
.event_select_on = event_select_on_epoll,
.event_unregister = event_unregister_epoll,
.event_dispatch = event_dispatch_epoll
static struct event_pool * event_pool_new_epoll (int count) { struct event_pool *event_pool = NULL; int epfd = -1; //申请空间,event_pool封装了下epoll event_pool = GF_CALLOC (1, sizeof (*event_pool), gf_common_mt_event_pool); event_pool->count = count; event_pool->reg = GF_CALLOC (event_pool->count, sizeof (*event_pool->reg), gf_common_mt_reg); //获得epoll的文件描述符 epfd = epoll_create (count); event_pool->fd = epfd; event_pool->count = count; pthread_mutex_init (&event_pool->mutex, NULL); pthread_cond_init (&event_pool->cond, NULL); out: return event_pool; }
int event_register_epoll (struct event_pool *event_pool, int fd, event_handler_t handler, void *data, int poll_in, int poll_out) { .... //如果满了,就扩容1倍 if (event_pool->count == event_pool->used) { event_pool->count *= 2; event_pool->reg = GF_REALLOC (event_pool->reg, event_pool->count * sizeof (*event_pool->reg)); if (!event_pool->reg) { gf_log ("epoll", GF_LOG_ERROR, "event registry re-allocation failed"); goto unlock; } } idx = event_pool->used; event_pool->used++; //向event_pool中加入一个注册信息 event_pool->reg[idx].fd = fd; event_pool->reg[idx].events = EPOLLPRI; event_pool->reg[idx].handler = handler; event_pool->reg[idx].data = data; .... event_pool->changed = 1; epoll_event.events = event_pool->reg[idx].events; ev_data->fd = fd; ev_data->idx = idx; //注册到epoll ret = epoll_ctl (event_pool->fd, EPOLL_CTL_ADD, fd, &epoll_event); ....