阅读之前请先了解Linux内核的wakeup&callback机制以及前文的select与poll技术分析
epoll技术
为了解决select&poll技术存在的两个性能问题,对于大内存数据拷贝问题,epoll通过epoll_create函数创建epoll空间(相当于一个容器管理),在内核中只存储一份数据来维护N个socket事件的变化,通过epoll_ctl函数来实现对socket事件的增删改操作,并且在内核底层使用虚拟内存的管理方式保证用户空间与内核空间对该内存是具备可见性,直接通过指针引用的方式进行操作,避免了大内存数据的拷贝导致的空间切换性能问题,对于轮询等待事件通过epoll_wait的方式来实现对socket事件的监听,将不断轮询等待高频事件wait与低频socket注册事件两个操作分离开,同时会对监听就绪的socket事件添加到就绪队列中,也就保证唤醒轮询的事件都是具备可读的,现对epoll技术分析如下:
epoll技术定义
// 创建保存epoll文件描述符的空间,该空间也称为“epoll例程”
int epoll_create(int size); // 使用链表,现在已经弃用
int epoll_create(int flag); // 使用红黑树的数据结构
// epoll注册/修改/删除 fd的操作
long epoll_ctl(int epfd, // 上述epoll空间的fd索引值
int op, // 操作识别,EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL
int fd, // 注册的fd
struct epoll_event *event); // epoll监听事件的变化
struct epoll_event {
__poll_t events;
__u64 data;
} EPOLL_PACKED;
// epoll等待,与select/poll的逻辑一致
epoll_wait(int epfd, // epoll空间
struct epoll_event *events, // epoll监听事件的变化
int maxevents, // epoll可以保存的最大事件数
int timeout); // 超时时间
epoll技术实现细节
- epoll_ctl函数处理socket描述符fd注册问题,关注epoll_ctl的ADD方法
// 摘取核心代码
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
bool nonblock)
{
// ...
// 在红黑树中查找存储file对应的epitem,添加的时候会将epitem加到红黑树节点中
epi = ep_find(ep, tf.file, fd);
// 对于EPOLL_CTL_ADD模式,使用mtx加锁添加到wakeup队列中
switch (op) {
case EPOLL_CTL_ADD:
// fd注册操作
// epds->events |= EPOLLERR | EPOLLHUP;
// error = ep_insert(ep, epds, tf.file, fd, full_check);
break;
case EPOLL_CTL_DEL:
// // 删除操作:存储epitem容器移除epitem信息
break;
// 对注册的fd进行修改,但epoll的模式为EPOLLEXCLUSIVE是无法进行操作的
case EPOLL_CTL_MOD:
// 修改操作,内核监听到事件变化执行修改
//error = ep_modify(ep, epi, epds);
break;
}
// 释放资源逻辑
}
- EPOLL_CTL_ADD核心代码逻辑
// 添加逻辑
static int ep_insert(struct eventpoll *ep, const struct epoll_event *event,
struct file *tfile, int fd, int full_check)
{
// ...
struct epitem *epi;
struct ep_pqueue epq;
// 将fd包装在epitem的epollfile中
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
epi->nwait = 0;
epi->next = EP_UNACTIVE_PTR;
// 如果当前监听到事件变化,那么创建wakeup执行的source
if (epi->event.events & EPOLLWAKEUP) {
error = ep_create_wakeup_source(epi);
if (error)
goto error_create_wakeup_source;
} else {
RCU_INIT_POINTER(epi->ws, NULL);
}
// 初始化回调函数并与当前的epitem进行绑定添并将callback添加到poll table中,每一个epitem都有对应的callback,并添加到等待队列ep_pqueue