原文地址 http://hi.baidu.com/firefly0510/blog/item/1ae20a4ac2fe01f883025cfc.html
epoll的实现原理
2009-04-17 13:05
1 功能介绍
epoll与select/poll不同的一点是,它是由一组系统调用组成。
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll相关系统调用是在Linux 2.5.44开始引入的。该系统调用针对传统的select/poll系统调用的不足,设计上作了很大的改动。select/poll的缺点在于:
1.每次调用时要重复地从用户态读入参数。
2.每次调用时要重复地扫描文件描述符。
3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll显得不够高效。epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个wait。此外,
内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们
是一对多的关系。
由于在执行epoll_create和epoll_ctrl时,已经把用户态的信息保存到内核态了,所以之后即使反复地调用epoll_wait,也不会重复地拷贝参数,扫描文件描述符,反复地把当前进程放入/放出等待队列。这样就避免了以上的三个缺点。
接下去看看它们的实现:
2 关键结构体:
/* Wrapper struct used by poll queueing */
struct ep_pqueue {
poll_table pt;
struct epitem *epi;
};
这个结构体类似于select/poll中的struct poll_wqueues。由于epoll需要在内核态保存大量信息,所以光光一个回调函数指针已经不能满足要求,所以在这里引入了一个新的结构体struct epitem。
/*
* Each file descriptor added to the eventpoll interface will
* have an entry of this type linked to the hash.
*/
struct epitem {
/* RB-Tree node used to link this structure to the eventpoll rb-tree */
struct rb_node rbn;红黑树,用来保存eventpoll
/* List header used to link this structure to the eventpoll ready list */
struct list_head rdllink;双向链表,用来保存已经完成的eventpoll
/* The file descriptor information this item refers to */
struct epoll_filefd ffd;这个结构体对应的被监听的文件描述符信息
/* Number of active wait queue attached to poll operations */
int nwait;poll操作中事件的个数
/* List containing poll wait queues */
struct list_head pwqlist;双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table
/* The "container" of this item */
struct eventpoll *ep;指向eventpoll,多个epitem对应一个eventpoll
/* The structure that describe the interested events and the source fd */
struct epoll_event event;记录发生的事件和对应的fd
/*
* Used to keep track of the usage count of the structure. This avoids
* that the structure will desappear from underneath our processing.
*/
atomic_t usecnt;引用计数
/* List header used to link this item to the "struct file" items list */
struct list_head fllink;双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,用来保存所有监视这个文件的epoll节点
/* List header used to link the item to the transfer list */
struct list_head txlink;双向链表,用来保存传输队列
/*
* This is used during the collection/transfer of events to userspace
* to pin items empty events set.
*/
unsigned int revents;文件描述符的状态,在收集和传输时用来锁住空的事件集合
};
该结构体用来保存与epoll节点关联的多个文件描述符,保存的方式是使用红黑树实现的hash表。至于为什么要保存,下文有详细解释。它与被监听的文件描述符一一对应。
struct eventpoll {
/* Protect the this structure access */
rwlock_t lock;读写锁
/*
* This semaphore is used to ensure that files are not removed
* while epoll is using them. This is read-held during the event
* collection loop and it is write-held during the file cleanup
* path, the epoll file exit code and the ctl operations.
*/
struct rw_semaphore sem;读写信号量
/* Wait queue used by sys_epoll_wait() */
wait_queue_head_t wq;
/* Wait queue used by file->poll() */
wait_queue_head_t poll_wait;
/* List of ready file descriptors */
struct list_head rdllist;已经完成的操作事件的队列。
/* RB-Tree root used to store monitored fd structs */
struct rb_root rbr;保存epoll监视的文件描述符
};
这个结构体保存了epoll文件描述符的扩展信息,它被保存在file结构体的private_data中。它与epoll文件节点一一对应。通常一个epoll文件节点对应多个被监视的文件描述符。所以一个eventpoll结构体会对应多个epitem结构体。 那么,epoll中的等待事件放在哪里呢?见下面