概述
对于Linux内核多路复用技术相比大家都有一定的了解,从select、poll到epoll,无一不是对前者的升级,这篇文建将要简单讲解epoll在内核中的实现。本文中提及的内核版本是 4.20.11.注意这里的epoll并不是evolution poll,而是event poll,二者没有必要的联系,epoll也并不是针对poll进行的改进,再加上select,三者为单线程多任务的模拟。比如我们了解的redis、nginx都有用到epoll技术,感兴趣的朋友可以自行谷歌或必应。
图片来自网络,侵删。
Table of Contents
介绍
用户态server-client epoll实现
内核中的epoll追踪
内核中的epoll_create
内核中的epoll_ctl
内核中的epoll_wait
介绍
在Linux内核4.20.111 include/linux/syscalls.h里有这样的声明。
asmlinkage long sys_epoll_create(int size);
在用户态中他长这样(参见man epoll_create)
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);
如man手册所说,size参数从Linux 2.6.8被废弃
epoll_create() creates a new epoll(7) instance. Since Linux 2.6.8,
the size argument is ignored, but must be greater than zero; see
NOTES.
用户态server-client epoll实现
这里有一篇关于epoll的socket S-C代码实现:
https://rtoax.blog.csdn.net/article/details/81047943
内核中的epoll追踪
就像上面服务端-客户端(S-C)实现一样,很多小伙伴认为epoll是属于内核中的网络,实则不然,其实它属于文件系统,别忘了,epoll_create的函数返回值是个fd(文件描述符),再看一下代码的存放路径:
linux-4.20.11fseventpoll.c
而对于用户态的epoll_create,在改代码中的系统调用为
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
return do_epoll_create(flags);
}
SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= 0)
return -EINVAL;
return do_epoll_create(0);
}
当然,参数 size 被弃用。
我们已经找到了epoll_create的系统调用原型
/*
* Open an eventpoll file descriptor.
*/
static int do_epoll_create(int flags);
内核中的epoll_create
当然是先看结构体(为简化只保留关键代码)
struct file;
struct eventpoll;
struct epitem;
struct epoll_event;
struct eppoll_entry;
struct ep_pqueue;
struct epoll_filefd;
简化的流程如下图
内核中的epoll_ctl
系统调用原型为
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
struct epoll_event __user *, event);
再看这一段代码
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
epds.events |= EPOLLERR | EPOLLHUP;
error = ep_insert(ep, &epds, tf.file, fd, full_check);
} else
error = -EEXIST;
if (full_check)
clear_tfile_check_list();
break;
case EPOLL_CTL_DEL:
if (epi)
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
case EPOLL_CTL_MOD:
if (epi) {
if (!(epi->event.events & EPOLLEXCLUSIVE)) {
epds.events |= EPOLLERR | EPOLLHUP;
error = ep_modify(ep, epi, &epds);
}
} else
error = -ENOENT;
break;
}
这段代码告诉我们ADD和DEL两个操作是如何操作的。
首先看下epoll_ctl涉及到的关键全局变量。
然后,开始进行slab分配,关注一句
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
return -ENOMEM;
其中kmem_cache_alloc 涉及到slab的只是,此处不做详述,功能是从缓存epi_cache中申请固定页的内存。当然这个类型是struct epitem *epi;这是epoll的一项,也就是一个fd(文件描述符)。接下来初始化一系列链表头
/* Item initialization follow here ... */
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
设定fd,用于红黑树节点的索引。
/* Setup the structure that is used as key for the RB tree */
static inline void ep_set_ffd(struct epoll_filefd *ffd,
struct file *file, int fd)
{
ffd->file = file;
ffd->fd = fd;
}
将fllink节点插入tfile的f_ep_links为头结点的双向链表中。
list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
将item插入红黑树(红黑树:一种平衡二叉树,可用于快速索引,一个整形的遍历最高也就十几次的计算复杂度)
/*
* Add the current item to the RB tree. All RB tree operations are
* protected by "mtx", and ep_insert() is called with "mtx" held.
*/
ep_rbtree_insert(ep, epi);
反过来的remove操作
/*
* Removes a "struct epitem" from the eventpoll RB tree and deallocates
* all the associated resources. Must be called with "mtx" held.
*/
static int ep_remove(struct eventpoll *ep, struct epitem *epi)
当然,从链表中删除item
list_del_rcu(&epi->fllink)
list_del_init(&epi->rdllink);
从红黑树中删除叶子
rb_erase_cached(&epi->rbn, &ep->rbr);
这里当然涉及到资源释放“RCU-read count use”
call_rcu(&epi->rcu, epi_rcu_free);
讲了ADD,DEL,MOD也就好解释了,无非是索引+修改。
/*
* Modify the interest event mask by dropping an event if the new mask
* has a match in the current file status. Must be called with "mtx" held.
*/
static int ep_modify(struct eventpoll *ep, struct epitem *epi,
const struct epoll_event *event)
然后再将其添加到队尾
list_add_tail(&epi->rdllink, &ep->rdllist);
如下图给出了epoll的create和ctrl的结构体图。
内核中的epoll_wait
wait有两种,用户态接口为epoll_wait和epoll_pwait,二者区别在于一个signal,此处不做讲解,以epoll_wait为例。
epoll_wait的系统调用为
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
return do_epoll_wait(epfd, events, maxevents, timeout);
}
do_epoll_wait函数原型为
/*
* Implement the event wait interface for the eventpoll file. It is the kernel
* part of the user space epoll_wait(2).
*/
static int do_epoll_wait(int epfd, struct epoll_event __user *events,
int maxevents, int timeout);
我们主要关注函数主要做了哪些工作。注意之类将使用wait_queue,可以简单将其理解为一方read阻塞,一方write后,read阻塞被打断,读取缓冲区,当然这也是一种比喻,实际请读者自己脑补,他的数据类型为
wait_queue_entry_t wait;
对于wait只针对关键函数进行调用追踪,先下面这个“事件发生”判断
/* Is it worth to try to dig for events ? */
eavail = ep_events_available(ep);
以及这个事件真的发生了
/*
* Try to transfer events to user space. In case we get 0 events and
* there's still timeout left over, we go trying again in search of
* more luck.
*/
if (!res && eavail &&
!(res = ep_send_events(ep, events, maxevents)) && !timed_out)
goto fetch_events;
提到一嘴,这里的宏current 代表了运行了这段代码的内核进程,也就是结构task_struct(详情请自行学习进程管理相关知识)。
下面我们就关注ep_send_events这个函数,里面就只scan了一个ready list
static int ep_send_events(struct eventpoll *ep,
struct epoll_event __user *events, int maxevents)
{
struct ep_send_events_data esed;
esed.maxevents = maxevents;
esed.events = events;
ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
return esed.res;
}
初始化一个链表,并肩所有ready的节点拼接到这个链表上
LIST_HEAD(txlist);
list_splice_init(&ep->rdllist, &txlist);
然后调用这个回调proc函数
/*
* Now call the callback function.
*/
res = (*sproc)(ep, &txlist, priv);
这个回调函数中将该ready的节点删除,函数原型为
static __poll_t ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
void *priv);
然后就是这一段epoll_wait将返回的代码
if (!list_empty(&ep->rdllist)) {
/*
* Wake up (if active) both the eventpoll wait list and
* the ->poll() wait list (delayed after we release the lock).
*/
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
spin_unlock_irq(&ep->wq.lock);
if (!ep_locked)
mutex_unlock(&ep->mtx);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&ep->poll_wait);
当然,其中可能涉及到中断的知识,此文也不做讲解,
static void ep_poll_safewake(wait_queue_head_t *wq)
{
int this_cpu = get_cpu();
ep_call_nested(&poll_safewake_ncalls, EP_MAX_NESTS,
ep_poll_wakeup_proc, NULL, wq, (void *) (long) this_cpu);
put_cpu();
}
本文就知识简单讲解epoll的实现机制。感兴趣的朋友可以继续阅读源码了解。
最后
以上就是内向纸飞机为你收集整理的浅谈epoll介绍用户态server-client epoll实现内核中的epoll追踪内核中的epoll_create内核中的epoll_ctl内核中的epoll_wait的全部内容,希望文章能够帮你解决浅谈epoll介绍用户态server-client epoll实现内核中的epoll追踪内核中的epoll_create内核中的epoll_ctl内核中的epoll_wait所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复