参考学长博客和网上的资料
http://blog.csdn.net/kongkongkkk/article/details/77418092
epoll
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
以上的话可以总结为几个方面
首先相比于select和poll而言epoll性能更为出众,并且cpu的利用率低。
并不需要遍历整个fd只有被事件唤醒的fd才需要便利,并且不需要每次重新从内核空间进行添加.。只需要第一次添加因为他是分开的,并不像select那样。其次它没有fd的限制,select默认1024;
好了首先先看一片知乎。先大体了解epoll及其工作原理。
golang数据结构内部实现 - linux下非阻塞io库 epoll(分享自知乎网)https://zhuanlan.zhihu.com/p/27050330?utm_source=qq&utm_medium=social
基本概念如下首先要用熟,几个api不进行介绍直接进行源码剖析。
struct epitem {
/* RB tree node used to link this structure to the eventpoll RB tree */
//每个epitem都存放在eventpoll中以rbr为根的红黑树中
//rbn记录epitem在红黑树中的结点
struct rb_node rbn;
/* List header used to link this structure to the eventpoll ready list */
//每个就绪事件所对应的epitem都链入了eventpoll中的rdllink
//rdllink记录就绪链表头
struct list_head rdllink;
/*
* Works together "struct eventpoll"->ovflist in keeping the
* single linked chain of items.
*/
//记录每个epitem在eventpoll数据结构中的ovflist的下一个epitem
struct epitem *next;
/* The file descriptor information this item refers to */
//epoll_filefd数据结构记录epitem所对应的struct file和fd文件描述符
struct epoll_filefd ffd;
/* Number of active wait queue attached to poll operations */
//poll操作上的等待队列个数
int nwait;
/* List containing poll wait queues */
//包含等待队列对头的单链表
struct list_head pwqlist;
/* The "container" of this item */
//记录epitem所属哪一个eventpoll数据结构
struct eventpoll *ep;
/* List header used to link this item to the "struct file" items list */
//记录epitem所对应的struct file的单链表
struct list_head fllink;
/* The structure that describe the interested events and the source fd */
//记录epitem对应的epoll_event数据结构,epoll_event是epoll_ctl函数传入的参数
struct epoll_event event;
};
struct eventpoll {
spinlock_t lock;
struct mutex mtx;
wait_queue_head_t wq; // sys_epoll_wait() 等待在这里
// f_op->poll() 使用的, 被其他事件通知机制利用的wait_address
wait_queue_head_t poll_wait;
/* 已就绪的需要检查的epitem 列表 */
struct list_head rdllist;
/* 保存所有加入到当前epoll的文件对应的epitem*/
struct rb_root rbr;
// 当正在向用户空间复制数据时, 产生的可用文件
struct epitem *ovflist;
/* The user that created the eventpoll descriptor */
struct user_struct *user;
struct file *file;
/*优化循环检查,避免循环检查中重复的遍历 */
int visited;
struct list_head visited_list_link;
}
truct epoll_filefd {
struct file *file;
int fd;
};
// 与一个文件上的一个wait_queue_head 相关联,因为同一文件可能有多个等待的事件,这些事件可能使用不同的等待队列
struct eppoll_entry {
// List struct epitem.pwqlist
struct list_head llink;
// 所有者
struct epitem *base;
// 添加到wait_queue 中的节点
wait_queue_t wait;
// 文件wait_queue 头
wait_queue_head_t *whead;
};
// 用户使用的epoll_event
struct epoll_event {
__u32 events;
__u64 data;
} EPOLL_PACKED;
对与epoll所有api而言主要是对这五个结构体的一系列操作。
对于epoll_create函数每个epollfd都会对应一个叫做eventpoll 事件池的东西,执行epoll_create 最主要的是创建了就绪链表和红黑树,采用红黑树结构的好处是查找效率高并且增加和删除的效率也不错相比于其他数据结构而言。
还有一个很重要的东西struct file 变量他的作用是:
eventpoll中维持了一个 结构体 epoll向内核注册文件系统
用于存储
述
监控socket
调用epoll_create
虚拟
epoll文件系统
创建
file结点
file
普通文件
服务于epoll
epoll
内核初始化
(操作系统启
)
同
辟
epoll自
内核高速cache区
用于安置每
我
想监控
socket
些socket
红黑树
形式保存
内核cache
支持快速
查找、插入、删除
内核高速cache区
建立连续
物理内存页
建立slab层
简单
说
物理
配
想要
size
内存
象
每
使用
都
使用空闲
已
配
象。所谓返回的fd并不是对应真正的文件他只是对应了一个虚拟的struct file。epoll独立的文件管理系统,添加一些对struct file的一些操作。
效于
我
调用epoll_ctl往
塞入百万
句柄
epoll_wait仍
飞快
返
并
效
发
事件
句柄给我
用户
由于我
调用epoll_create
内核除
帮我
epoll文件系统
建
file结点
内核cache
建
红黑树用于存储
epoll_ctl传
socket外
再建立
list链表
用于存储准备
绪
事件
epoll_wait调用
仅仅观察
list链表
没
数据即
数据
返
没
数据
sleep
等
timeout
间
即使链表没数据
返
所
epoll_wait非
高效。(访问上的高效)
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
int error;
//eventpoll是epoll中非常重要的数据结构!每一个epollfd都有一个对应的eventpoll数据结构
//eventpoll数据结构定义在上面
struct eventpoll *ep = NULL;
/* Check the EPOLL_* constant for consistency. */
BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;
/*
* Create the internal data structure ("struct eventpoll").
*/
//初始化一个eventpoll数据结构
//ep_alloc定义在下面
error = ep_alloc(&ep);
if (error < 0)
return error;
/*
* Creates all the items needed to setup an eventpoll file. That is,
* a file structure and a free file descriptor.
*/
//创建epollfd
//因为epollfd并不存在真正对应的文件,所以内核创建了一个虚拟的文件,并为这个虚拟文件分配struct file数据结构
//参数eventpoll_fops就是file operations,即文件支持的操作
//关于file operations在之前的poll机制内核源代码剖析一文中已经做了非常深入的解释
//这里简单解释一下,file operations中的每一个成员都是回调函数指针,对应每一种操作的具体实现
//epollfd文件实现了三种操作,即release、poll、llseek
//eventpoll_fops数据结构定义在下面
//参数ep就是epollfd所对应的eventpoll数据结构,在anon_inode_getfd中,将struct file的private_data成员赋值为ep的地址
//anon_inode_getfd定义在下面
error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
if (error < 0)
ep_free(ep);
//返回epollfd的值
return error;
}
接下来主要看一下这个东西(epoll_create具体分配的东西也就是event_poll 结构体中的东西)
static int ep_alloc(struct eventpoll **pep)
{
int error;
struct user_struct *user;
struct eventpoll *ep;
//获取当前用户信息
user = get_current_user();
error = -ENOMEM;
//通过kmalloc为eventpoll数据结构分配内存空间
ep = kzalloc(sizeof(*ep), GFP_KERNEL);
if (unlikely(!ep))
goto free_uid;
spin_lock_init(&ep->lock);
mutex_init(&ep->mtx);//对互斥锁进行初始化
//初始化eventpoll中的wq
init_waitqueue_head(&ep->wq);
//初始化eventpoll中的poll_wait
init_waitqueue_head(&ep->poll_wait);
//初始化存放就绪事件所对应的epitem的双向链表
INIT_LIST_HEAD(&ep->rdllist);
//初始化存放所有事件对应的epiitem的红黑树,初始值为NULL
//#define RB_ROOT (struct rb_root) { NULL, }
ep->rbr = RB_ROOT;
//初始化转移到用户空间之前,存放就绪事件所对应的epitem的双向链表,初始值为-1L
//#define EP_UNACTIVE_PTR ((void *) -1L)
ep->ovflist = EP_UNACTIVE_PTR;
//初始化用户信息
ep->user = user;
//为eventpoll数据结构指针赋值
*pep = ep;
return 0;
free_uid:
free_uid(user);
return error;
}
epollfd和event_poll 的绑定操作代码如下
//由此可见epollfd所对应的的匿名文件只实现了三种操作
//release操作为释放epollfd所对应的eventpoll数据结构
//ep_eventpoll_release定义在下面
//poll操作为事件就绪时,调用poll操作对应的回调函数对当前进程进行一些列操作
//ep_eventpoll_poll定义先放一边,在epoll_wait中会详细解释
//llseek操作为获取匿名文件的游标偏移
//noop_llseek定义在下面
static const struct file_operations eventpoll_fops = {
.release = ep_eventpoll_release,
.poll = ep_eventpoll_poll,
.llseek = noop_llseek,
};
static int ep_eventpoll_release(struct inode *inode, struct file *file)
{
//通过struct file中的成员private_data得到epollfd所对应的eventpoll数据结构
struct eventpoll *ep = file->private_data;
//释放eventpoll数据结构
if (ep)
ep_free(ep);
return 0;
}
1
int anon_inode_getfd(const char *name, const struct file_operations *fops,
void *priv, int flags)
{
int error, fd;
struct file *file;
//分配文件描述符,即epollfd
error = get_unused_fd_flags(flags);
if (error < 0)
return error;
fd = error;
//创建匿名文件
file = anon_inode_getfile(name, fops, priv, flags);
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto err_put_unused_fd;
}
//将文件描述符fd和匿名文件绑定,即将file_struct中的fdtable的成员fd[fd]赋值为file
fd_install(fd, file);
//返回epollfd的值
return fd;
err_put_unused_fd:
put_unused_fd(fd);
return error;
}