从源码角度来领略一下内核的轮询机制
kernel/fs/eventpoll.c
kernel/include/linux/poll.h
kernel/include/uapi/linux/eventpoll.h
一、概述
在linux还没有epoll机制前,select和poll作为IO多路复用的机制实现并发程序,但这两种方式有着如下缺点:
- 通过select方式单个进程能够监控的文件描述符不得超过 进程可打开的文件个数上限,默认为1024, 即便强行修改了这个上限,还会遇到性能问题;
- select轮询效率随着监控个数的增加而性能变差
- select从内核空间返回到用户空间的是整个文件描述符数组,应用程序还需要额外再遍历整个数组才知道哪些文件描述符触发了相应事件。
本文要介绍epoll机制,有不少人可能都知道相比select/poll之下,epoll有着明显优势,这些优势的底层实现原理又是什么呢?
epoll函数
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);
struct epoll_event {
__uint32_t events;
epoll_data_t data;
};
接下来从源码角度剖析这3个方法。
二、epoll_create
2.1 sys_epoll_create
SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= 0)
return -EINVAL;
return sys_epoll_create1(0);
}
size仅仅用来检测是否大于0,并没有真正使用。sys_epoll_create1过程检查参数,然后再调用epoll_create1。
2.2 sys_epoll_create1
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
int error, fd;
struct eventpoll *ep = NULL;
struct file *file;
// 创建内部数据结构eventpoll 【小节2.3】
error = ep_alloc(&ep);
//查询未使用的fd
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
//创建file实例,以及匿名inode节点和dentry等数据结构
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
ep->file = file;
fd_install(fd, file); //建立fd和file的关联关系
return fd;
out_free_fd:
put_unused_fd(fd);
out_free_ep:
ep_free(ep);
return error;
}
epoll_create的过程主要是创建并初始化数据结构eventpoll,以及创建file实例,并将ep放入file->private。
2.3 ep_alloc
static int ep_alloc(struct eventpoll **pep)
{
int error;
struct user_struct *user;
struct eventpoll *ep; //【小节2.4.1】
user = get_current_user();
error = -ENOMEM;
ep = kzalloc(sizeof(*ep), GFP_KERNEL);
spin_lock_init(&ep->lock);
mutex_init(&ep->mtx);
init_waitqueue_head(&ep->wq); //初始化epoll文件的等待队列
init_waitqueue_head(&ep->poll_wait); //初始化eventpoll文件的等待队列
INIT_LIST_HEAD(&ep->rdllist);
ep->rbr = RB_ROOT;
ep->ovflist = EP_UNACTIVE_PTR;
ep->user = user;
*pep = ep;
return 0;
free_uid:
free_uid(user);
return error;
}
2.4 相关结构体
为了方便后续源码的阅读,这里列举前后文所涉及到的核心struct
2.4.1 struct eventpoll
struct eventpoll {
spinlock_t lock;
struct mutex mtx;
wait_queue_head_t wq; //sys_epoll_wait()使用的等待队列
wait_queue_head_t poll_wait; //file->poll()使用的等待队列
struct list_head rdllist; //所有准备就绪的文件描述符列表
struct rb_root rbr; //用于储存已监控fd的红黑树根节点
// 当正在向用户空间传递事件,则就绪事件会临时放到该队列,否则直接放到rdllist
struct epitem *ovflist;
struct wakeup_source *ws; // 当ep_scan_ready_list运行时使用wakeup_source
struct user_struct *user; //创建eventpoll描述符的用户
struct file *file;
int visited; //用于优化循环检测检查
struct list_head visited_list_link;
};
2.4.2 struct epitem
struct epitem {
union {
struct rb_node rbn; //RB树节点将此结构链接到eventpoll RB树
struct rcu_head rcu; //用于释放结构体epitem
};
struct list_head rdllink; //用于将此结构链接到eventpoll就绪列表的列表标头
struct epitem *next; //配合ovflist一起使用来保持单向链的条目
struct epoll_filefd ffd; //此条目引用的文件描述符信息
int nwait; //附加到poll轮询中的活跃等待队列数
struct list_head pwqlist;
struct eventpoll *ep; //epi所属的ep
struct list_head fllink; //链接到file条目列表的列表头
struct wakeup_source __rcu *ws; //设置EPOLLWAKEUP时使用的wakeup_source
struct epoll_event event; //监控的事件和文件描述符
};