码字不易,转载请标明出处
前言
Linux内核提供了3个关键函数供用户来操作epoll,分别是:
- epoll_create(), 创建eventpoll对象
- epoll_ctl(), 操作eventpoll对象
- epoll_wait(), 从eventpoll对象中返回活跃的事件
而操作系统内部会用到一个名叫epoll_event_callback()的回调函数来调度epoll对象中的事件,这个函数非常重要,故本文将会对上述4个函数进行源码分析。
源码来源
由于epoll的实现内嵌在内核中,直接查看内核源码的话会有一些无关代码影响阅读。为此笔者在GitHub上找到一个大神写的简化版TCP/IP协议栈,里面实现了epoll逻辑。链接为:https://github.com/wangbojing/NtyTcp
存放着以上4个关键函数的文件是[src\nty_epoll_rb.c],本文接下来通过分析该程序的代码来探索epoll能支持高并发连接的秘密。
两个核心数据结构
(1)epitem
如图所示,epitem是中包含了两个主要的成员变量,分别是rbn和rdlink,前者是红黑树的节点,而后者是双链表的节点,也就是说一个epitem对象即可作为红黑树中的一个节点又可作为双链表中的一个节点。并且每个epitem中存放着一个event,对event的查询也就转换成了对epitem的查询。
struct epitem {
RB_ENTRY(epitem) rbn;
/* RB_ENTRY(epitem) rbn等价于
struct {
struct type *rbe_left; //指向左子树
struct type *rbe_right; //指向右子树
struct type *rbe_parent; //指向父节点
int rbe_color; //该节点的颜色
} rbn
*/
LIST_ENTRY(epitem) rdlink;
/* LIST_ENTRY(epitem) rdlink等价于
struct {
struct type *le_next; //指向下个元素
struct type **le_prev; //前一个元素的地址
}*/
int rdy; //判断该节点是否同时存在与红黑树和双向链表中
int sockfd; //socket句柄
struct epoll_event event; //存放用户填充的事件
};
(2)eventpoll
如图所示,eventpoll中包含了两个主要的成员变量,分别是rbr和rdlist,前者指向红黑树的根节点,后者指向双链表的头结点。即一个eventpoll对象对应二个epitem的容器。对epitem的检索,将发生在这两个容器上(红黑树和双链表)。
struct eventpoll {
/*
struct ep_rb_tree {
struct epitem *rbh_root;
}
*/
ep_rb_tree rbr; //rbr指向红黑树的根节点
int rbcnt; //红黑树中节点的数量(也就是添加了多少个TCP连接事件)
LIST_HEAD( ,epitem) rdlist; //rdlist指向双向链表的头节点;