栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux Kernel Select/Poll/Epoll源码深度历险(三)---- 探索Epoll底层运行原理

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux Kernel Select/Poll/Epoll源码深度历险(三)---- 探索Epoll底层运行原理

文章目录
    • 全系列总结博客链接
    • 前引
    • Linux Kernel Select/Poll/Epoll源码深度历险(三)---- 探索Epoll底层运行原理
      • 1、研究研究Epoll 系列系统调用
        • 1、epoll_create
        • 2、epoll_wait
        • 3、epoll_ctl
        • 4、epoll_event 结构体
      • 2、先罗列出来正常Epoll调用 逐个击破
        • 1、Epoll运行示例代码
        • 2、不妨先来开刀 epoll_create
          • 1、仔细分析 epoll_create1 核心代码(上)
          • 2、仔细分析 epoll_create1 核心代码(中)
          • 3、仔细分析 epoll_create1 核心代码(下)
        • 3、再来仔细研究研究 epoll_ctl
          • 1、庖丁解牛 epoll_ctl 核心代码解析(上)
          • 2、庖丁解牛 epoll_ctl 核心代码解析(中)
          • 3、庖丁解牛 epoll_ctl 核心代码解析(下)
        • 4、终于迎来最终函数 epoll_wait
          • 1、豁然开朗 epoll_wait 核心代码解析(上)
          • 2、豁然开朗 epoll_wait 核心代码解析(中)
          • 3、豁然开朗 epoll_wait 核心代码解析(下)
      • 3、浅浅总结一下 epoll 以及 select和poll


全系列总结博客链接

Linux Kernel Select/Poll/Epoll源码深度历险 全系列总结记录


前引

现在我还没有开始剖析Epoll代码 但是感觉 很多东西已经快来到我的面前了
很多原来 自己觉得离自己遥不可及的东西 已经就在眼前了 我只需要再蹦一下 我就要拿到属于自己的东西了

很快 真的很快 时间真的过得太快了
我原来从来没有这样的感觉 而真的直到今天 今天是2022/5/16日 我来到大学已经是大二下过了大半个学期了 原来大一第一篇写的博客 我自己看很多东西感觉是遥不可及 现在已经快来到我的眼前了

或许是原来这两年 我把一天当作两天过 也或许是这两年 我把时间作为台阶 把每一天都垫在我的脚底 不断地垫高自己

我很早很早就看过了身边人 发现有的人一开始就在地底下 玩起了杂草 有的人有想法想要垫高 但是却迟迟不付出行动 有的人刚开始垫高了自己 但之后自己畏惧了高度 自己最后也跳到了地上

我早就已经甩开了身边人不知道多少米 这也是我每天在大学过的比绝大部分大学生自由的根本吧 我从来没把我的同学当作竞争目标 也从来没把身边人当作竞争对手

上大学后 对于计算机这门学科 我一直对我自己都是以最高的要求 要求自己
不管是关于计算机的什么 只要是在我力所能及的范围 无论花多少时间 我都一定会去争取到 力所不能及 那我再花出成倍的时间 让他变成我力所能及的事情

可能真的 我感觉很多东西已经很快了 就站在我的眼前
原来觉得不可能的事情 已经快变成可能了


努力了快两年了 原来梦想触及的星星 发现自己已经在星星旁边的时候
我回头看了看大家 发现绝大部分的人都停留在原地

原来我对一句话一直不以为然 现在我才明白了一句话
道不同不相为谋

眺望眺望远方 总会让自己静下来

我写博客 是真的希望坚持写下去 能够给后面的计算机的学弟学妹们 其他学校的 不管是985 211 还是双非
我希望我写的东西 是能够带给大家一些帮助 对于一些我写的闲话 写的道理 是真的希望能够带给大家思考的

我很感谢我在大学一入学的时候 就看过 一位华中科大的微信部门的学长写的几篇文章 一直让我收益至今
如果我没有很早看过那位学长的文章 比绝大部分的人提前明白很多道理 我也不会有今天这样的水平 在这里写下这么多文字

受之于他人 那我也一样付之于他人
那位学长今天依旧 写这些博客 也算是对那位学长 分享精神的一种延续吧

总有人需要写 总有人需要分享
就写到这里

感恩我现在所拥有的一切


Linux Kernel Select/Poll/Epoll源码深度历险(三)---- 探索Epoll底层运行原理
1、研究研究Epoll 系列系统调用

Epoll可是我们的老朋友了 没有epoll 我们的WebServer也出不来了 哈哈
感谢epoll的鼎力支持

Epoll的话 函数就不止一个了 咱们也就还是挨个挨个分析吧


1、epoll_create

1、epoll_create
int epoll_create(int size);
int epoll_create1(int flags);


2、epoll_wait

2、epoll_wait
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);


3、epoll_ctl

epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

EPOLL_CTL_ADD
添加文件描述符 进入epoll监视队列

EPOLL_CTL_MOD
已经存在的描述符 例如只添加了文件描述符看是否可读 想要再检测是否可写的话 可以再MOD一下POLLOUT就好了 文件描述符就被监视上了可写

EPOLL_CTL_DEL
从监视队列移除文件描述符 epoll则不再监视这个fd


4、epoll_event 结构体

上面三个就构成了epoll的系列函数了 那我们还是对传入的参数结构看看吧 也就是我们核心的struct epoll_event

由于我这个下载版本是2.6.28 看了看内核对于struct epoll_event定义还只是个u64_t… 还不存在struct epoll_data 所以我们就看稍微新版一点的吧


对于第二个参数 data 我写的服务器就用到了 由于是union节约空间
我们fd和ptr是重合的 反正是为了方便我们用的 一个指针可以指向我们想存储关联的数据结构 相当的好用
比如我们想看一下 返回的事件的fd是哪个 我们就可以利用第二个变量我们存储进去即可 返回的时候 我们一看第二个数据就知道了

第一个events就是epoll返回的事件 相当于poll中的retenv
也是我们注册的时候 填入的注册event

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;      
    epoll_data_t data;        
};

2、先罗列出来正常Epoll调用 逐个击破
1、Epoll运行示例代码

这个示例代码是从man epoll抄过来的 我们见按照下面的来逐个逐个函数来分析

正常运行流程
epoll_create -> epoll_ctl(add) -> epoll_wait -> 开始循环

那我们先从epoll_create开始 挨个挨个来看

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;



epollfd = epoll_create1(0);
if (epollfd == -1) {
    perror("epoll_create1");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_wait");
        exit(EXIT_FAILURE);
    }

	for (n = 0; n < nfds; ++n) {
	    if (events[n].data.fd == listen_sock) {
	        conn_sock = accept(listen_sock,
	                           (struct sockaddr *) &addr, &addrlen);
	        if (conn_sock == -1) {
	            perror("accept");
	            exit(EXIT_FAILURE);
	        }
	        setnonblocking(conn_sock);
	        ev.events = EPOLLIN | EPOLLET;
	        ev.data.fd = conn_sock;
	        if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
	                    &ev) == -1) {
	            perror("epoll_ctl: conn_sock");
	            exit(EXIT_FAILURE);
	        }
		} else {
		    do_use_fd(events[n].data.fd);
		}
}

2、不妨先来开刀 epoll_create

在写道这段话的时候 基本上Epoll的全局实现我已经理清楚了 所以也才敢来写下这段话 下面的话 我们也就不像Select Poll那样细枝末节的一行一行分析了 我们来抓主干 把主干思路实现来看看就好了

在写博客的时候 我也顺带再理解一下一些之前可能没有留意的细节 那咱们就来吧


1、仔细分析 epoll_create1 核心代码(上)

不妨先看看Epoll_create
结果发现 原来epoll_create其实是在调用epoll_create1啊 哈哈 而且我看了看注释 size参数好像在2.6版本后就没有任何意义了 所以可以忽略
再来看看epoll_create1

SYSCALL_DEFINE1(epoll_create, int, size)
{
	if (size < 0)
		return -EINVAL;

	return sys_epoll_create1(0);
}

由于代码较长 而且可用信息其实也就只有三行 我们就以截图形式来看看吧
现在我们的主人公 struct eventpoll终于出来了 注意这个结构体 之后会在之后的函数中基本上都会出现 因为这是内核管理epoll的核心数据结构 这个数据结构管理着所有的数据 包括ready_list(fd已经准备好的队列) rb_root(内核采用红黑树有序高效管理epollfd相关的数据结构) wait_queue(等待队列)等等 这个数据结构会在下方给出来



-----------------------------------------------------------------------------------------------------
我们再看看第二个红箭头
这个也是之后我们要去仔细看的函数 每当我们调用一个epoll_create都会在内核中创建一个关于epoll管理的数据结构 其实也就是上面的struct eventpoll
ep_alloc其实就是去干初始化的事情去了
待会我们下面还会去看看ep_alloc去干嘛了

最后就是为了我们的eventfd初始化eventpoll file了 别忘记了 在Linux系统下 所有的东西都是以文件格式存在的 万物皆文件 关于其中参数eventpoll_fops 也就是eventpoll文件相关的两个操作 初始化和释放之前分配的内核数据结构而已 没什么其他的了 代码在下方放了 大家感兴趣的话自己去看看 咱们这部分再转到ep_alloc去看看去


eventpoll_fops函数补充

static const struct file_operations eventpoll_fops = {
	.release	= ep_eventpoll_release,
	.poll		= ep_eventpoll_poll
};

static int ep_eventpoll_release(struct inode *inode, struct file *file)
{
	struct eventpoll *ep = file->private_data;

	if (ep)
		ep_free(ep);

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: close() ep=%pn", current, ep));
	return 0;
}

static unsigned int ep_eventpoll_poll(struct file *file, poll_table *wait)
{
	unsigned int pollflags = 0;
	unsigned long flags;
	struct eventpoll *ep = file->private_data;

	
	poll_wait(file, &ep->poll_wait, wait);

	
	spin_lock_irqsave(&ep->lock, flags);
	if (!list_empty(&ep->rdllist))
		pollflags = POLLIN | POLLRDNORM;
	spin_unlock_irqrestore(&ep->lock, flags);

	return pollflags;
}

2、仔细分析 epoll_create1 核心代码(中)

说实话 这部分其实也没啥看的 也就是再看看怎么初始化的
kzalloc其实也就是kmalloc + 内存清零操作 user是我们目前进程调用这个函数的user信息而已

后面的一些变量 其实看struct eventpoll结构体的注释就看得懂了
反正就是在初始化eventpoll的各个成员
最后传递进来的二级指针取地址 赋值一下就结束了

static int ep_alloc(struct eventpoll **pep)
{
	int error;
	struct user_struct *user;
	struct eventpoll *ep;

	user = get_current_user();
	error = -ENOMEM;
	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
	if (unlikely(!ep))
		goto free_uid;

	spin_lock_init(&ep->lock);
	mutex_init(&ep->mtx);
	init_waitqueue_head(&ep->wq);
	init_waitqueue_head(&ep->poll_wait);
	INIT_LIST_HEAD(&ep->rdllist);
	ep->rbr = RB_ROOT;
	ep->ovflist = EP_UNACTIVE_PTR;
	ep->user = user;

	*pep = ep;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_alloc() ep=%pn",
		     current, ep));
	return 0;

free_uid:
	free_uid(user);
	return error;
}

3、仔细分析 epoll_create1 核心代码(下)

这个函数是上面最后提到的函数 我们还是来看一眼吧
顺带来感受感受 在linux中 万物皆文件的这个概念

其实我们来看这个函数 我觉得有用的 就可以看最后的那一行代码
file->private_data = priv; 这行代码 注意我们的参数priv 之前我们传递进来的 就是我们的struct eventpoll* ep 也就是我们的epoll的数据结构

看到这个 我觉得其他的也就没什么必要看了 感兴趣大家自己看看就好了 之后我们通过文件指针 就可以取出来我们的eventpoll指针了

int anon_inode_getfd(const char *name, const struct file_operations *fops,
		     void *priv, int flags)
{
	struct qstr this;
	struct dentry *dentry;
	struct file *file;
	int error, fd;

	if (IS_ERR(anon_inode_inode))
		return -ENODEV;

	error = get_unused_fd_flags(flags);
	if (error < 0)
		return error;
	fd = error;

	
	error = -ENOMEM;
	this.name = name;
	this.len = strlen(name);
	this.hash = 0;
	dentry = d_alloc(anon_inode_mnt->mnt_sb->s_root, &this);
	if (!dentry)
		goto err_put_unused_fd;

	
	atomic_inc(&anon_inode_inode->i_count);

	dentry->d_op = &anon_inodefs_dentry_operations;
	
	dentry->d_flags &= ~DCACHE_UNHASHED;
	d_instantiate(dentry, anon_inode_inode);

	error = -ENFILE;
	file = alloc_file(anon_inode_mnt, dentry,
			  FMODE_READ | FMODE_WRITE, fops);
	if (!file)
		goto err_dput;
	file->f_mapping = anon_inode_inode->i_mapping;

	file->f_pos = 0;
	file->f_flags = O_RDWR | (flags & O_NONBLOCK);
	file->f_version = 0;
	file->private_data = priv; // 在这里 把核心数据结构指针存在了private_data里面

	fd_install(fd, file);

	return fd;

err_dput:
	dput(dentry);
err_put_unused_fd:
	put_unused_fd(fd);
	return error;
}

3、再来仔细研究研究 epoll_ctl
1、庖丁解牛 epoll_ctl 核心代码解析(上)

这部分其实核心函数还是只有几个 本来想用红箭头标出来的 但代码确实有点长 就全部放出来吧 然后我们再来讲讲需要重点关注的函数

首先 我们关注一个新的struct epitem 这也是epoll中非常关键的成员
先简单介绍一下这个数据结构 之后我们每ADD都会分配一个这样的结构体 并且内部有rb_node 红黑树中通过rb_node就可以找到相对应的epitem 也就可以找到其fd相关信息了
至于剩下的list_head 也就是各种各样队列的所需要的成员了 详细的大家看注释就能明白是什么意思了
struct eventpoll是针对内核管理epoll的 struct epitem则是每个fd信息集合独有的存储结构 之后这个结构体也会存在在各处


-----------------------------------------------------------------------------------------------------
然后就是很常规的 复制event到内核空间
至于最下面的几行 我简单介绍一下在干嘛
1、得到epollfile file指针
2、得到target file file指针
3、对file进行各种检查 是否符合函数调用规则
4、通过epollfd而得到的file指针 还记得上面说过的private_data存储的ep吗 这里我们就通过file指针取出来了
5、我们通过ep_find来找epoll红黑树中是否有我们的epollfd ep_find很简单 这段代码的后面会放 感兴趣的自己去看看
6、switch判断op ADD->ep_insert DEL->ep_remove MOD->ep_modify

上面就是全过程了 后面的话 我们主要去看看ep_insert ep_modify工作其实也就是做了差不多的工作 而且ep_insert里面才是最重要的核心工作 所以我们必须去看一看 干了什么

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
		struct epoll_event __user *, event)
{
	int error;
	struct file *file, *tfile;
	struct eventpoll *ep;
	struct epitem *epi;
	struct epoll_event epds;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)n",
		     current, epfd, op, fd, event));

	error = -EFAULT;
	if (ep_op_has_event(op) &&
	    copy_from_user(&epds, event, sizeof(struct epoll_event)))
		goto error_return;

	
	error = -EBADF;
	file = fget(epfd);
	if (!file)
		goto error_return;

	
	tfile = fget(fd);
	if (!tfile)
		goto error_fput;

	
	error = -EPERM;
	if (!tfile->f_op || !tfile->f_op->poll)
		goto error_tgt_fput;

	
	error = -EINVAL;
	if (file == tfile || !is_file_epoll(file))
		goto error_tgt_fput;

	
	ep = file->private_data;

	mutex_lock(&ep->mtx);

	
	epi = ep_find(ep, tfile, fd);

	error = -EINVAL;
	switch (op) {
	case EPOLL_CTL_ADD:
		if (!epi) {
			epds.events |= POLLERR | POLLHUP;

			error = ep_insert(ep, &epds, tfile, fd);
		} else
			error = -EEXIST;
		break;
	case EPOLL_CTL_DEL:
		if (epi)
			error = ep_remove(ep, epi);
		else
			error = -ENOENT;
		break;
	case EPOLL_CTL_MOD:
		if (epi) {
			epds.events |= POLLERR | POLLHUP;
			error = ep_modify(ep, epi, &epds);
		} else
			error = -ENOENT;
		break;
	}
	mutex_unlock(&ep->mtx);

error_tgt_fput:
	fput(tfile);
error_fput:
	fput(file);
error_return:
	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %dn",
		     current, epfd, op, fd, event, error));

	return error;
}

ep_find补充函数 以及相关联的结构体

struct epoll_filefd {
	struct file *file;
	int fd;
};

static inline int ep_cmp_ffd(struct epoll_filefd *p1,
			     struct epoll_filefd *p2)
{
	return (p1->file > p2->file ? +1:
	        (p1->file < p2->file ? -1 : p1->fd - p2->fd));
}

static struct epitem *ep_find(struct eventpoll *ep, struct file *file, int fd)
{
	int kcmp;
	struct rb_node *rbp;
	struct epitem *epi, *epir = NULL;
	struct epoll_filefd ffd;

	ep_set_ffd(&ffd, file, fd);
	for (rbp = ep->rbr.rb_node; rbp; ) {
		epi = rb_entry(rbp, struct epitem, rbn);
		kcmp = ep_cmp_ffd(&ffd, &epi->ffd);
		if (kcmp > 0)
			rbp = rbp->rb_right;
		else if (kcmp < 0)
			rbp = rbp->rb_left;
		else {
			epir = epi;
			break;
		}
	}

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_find(%p) -> %pn",
		     current, file, epir));

	return epir;
}

2、庖丁解牛 epoll_ctl 核心代码解析(中)

这里我先把代码全给出来 后面以红箭头形式把核心代码指出来 下面再来分析

static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
		     struct file *tfile, int fd)
{
	int error, revents, pwake = 0;
	unsigned long flags;
	struct epitem *epi;
	struct ep_pqueue epq;

	if (unlikely(atomic_read(&ep->user->epoll_watches) >=
		     max_user_watches))
		return -ENOSPC;
	if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
		return -ENOMEM;

	
	INIT_LIST_HEAD(&epi->rdllink);
	INIT_LIST_HEAD(&epi->fllink);
	INIT_LIST_HEAD(&epi->pwqlist);
	epi->ep = ep;
	ep_set_ffd(&epi->ffd, tfile, fd);
	epi->event = *event;
	epi->nwait = 0;
	epi->next = EP_UNACTIVE_PTR;

	
	epq.epi = epi;
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

	
	revents = tfile->f_op->poll(tfile, &epq.pt);

	
	error = -ENOMEM;
	if (epi->nwait < 0)
		goto error_unregister;

	
	spin_lock(&tfile->f_ep_lock);
	list_add_tail(&epi->fllink, &tfile->f_ep_links);
	spin_unlock(&tfile->f_ep_lock);

	
	ep_rbtree_insert(ep, epi);

	
	spin_lock_irqsave(&ep->lock, flags);

	
	if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
		list_add_tail(&epi->rdllink, &ep->rdllist);

		
		if (waitqueue_active(&ep->wq))
			wake_up_locked(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}

	spin_unlock_irqrestore(&ep->lock, flags);

	atomic_inc(&ep->user->epoll_watches);

	
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)n",
		     current, ep, tfile, fd));

	return 0;

error_unregister:
	ep_unregister_pollwait(ep, epi);

	
	spin_lock_irqsave(&ep->lock, flags);
	if (ep_is_linked(&epi->rdllink))
		list_del_init(&epi->rdllink);
	spin_unlock_irqrestore(&ep->lock, flags);

	kmem_cache_free(epi_cache, epi);

	return error;
}

这是上半部分的图 我把核心的过程都以批注的形式弄出来了
这里又出现了一个新的结构体 struct ep_pqueue 我下面直接先给出来定义吧

struct ep_pqueue {
poll_table pt;
struct epitem *epi;
};

还记得poll_table吗 如果不清楚poll_table 或者不清楚Select/Poll是怎么调用的 还是搞忘了 烦请再回头看看之前我写过的解析Select/Poll调用中Poll_table的作用 有前置知识 后面理解起来稍微好理解一点
Linux Kernel Select/Poll/Epoll源码深度历险(一)---- 探索Select底层运行原理
Linux Kernel Select/Poll/Epoll源码深度历险(二)---- 探索Poll底层运行原理

Poll_table在这里的作用 核心就是里面的回调函数 之后的驱动程序在调用各自的Poll后 在调用回调Poll_wait后 就会通过poll_wait中的指针而调用回调
之前的Select/Poll回调是__pollwait 而这里的回调则变为了ep_ptable_queue_proc

看这张截图的最后一行代码 tfile->f_op->poll 这个作用和之前在Select/Poll中是一样的 去调用各自驱动实现的Poll调用去了
那我们后面来看看ep_ptable_queue_proc在干嘛


ep_insert下半部分

这张图是随着上面的代码下来的
下面就是ep_insert的下半部分 感兴趣可以看看
核心代码我已经做了注释


ep_ptable_queue_proc

看下图 这是之后各个设备调用poll后回调函数 里面则是向struct file中的sk_sleep中等待队列中注册而已 和Select/Poll中一样的 详情可以见tcp_poll 这个部分在Select/Poll中已经解析的很详细了 这里就不再多废话

可以在关注的是 这里又出现了个函数ep_poll_callback 不难想象 这个应该是进程被唤醒后 去调用的函数
ep_poll_callback 那我们在转去这个函数去看看


3、庖丁解牛 epoll_ctl 核心代码解析(下)

这个是最关键的函数 这也是后面也会用到的函数
先把代码给出来

static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int pwake = 0;
	unsigned long flags;
	struct epitem *epi = ep_item_from_wait(wait);
	struct eventpoll *ep = epi->ep;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%pn",
		     current, epi->ffd.file, epi, ep));

	spin_lock_irqsave(&ep->lock, flags);

	
	if (!(epi->event.events & ~EP_PRIVATE_BITS))
		goto out_unlock;

	
	if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
		if (epi->next == EP_UNACTIVE_PTR) {
			epi->next = ep->ovflist;
			ep->ovflist = epi;
		}
		goto out_unlock;
	}

	
	if (ep_is_linked(&epi->rdllink))
		goto is_linked;

	list_add_tail(&epi->rdllink, &ep->rdllist);

is_linked:
	
	if (waitqueue_active(&ep->wq))
		wake_up_locked(&ep->wq);
	if (waitqueue_active(&ep->poll_wait))
		pwake++;

out_unlock:
	spin_unlock_irqrestore(&ep->lock, flags);

	
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);

	return 1;
}

其实这里面 最核心的就两行代码 最核心的 如图
添加进ready_list 这个就不说了
剩下一个唤醒poll_wait 这个在下面会给代码 其实核心也就是这样
那这部分我们已经解析完了 最后轮到epoll_wait了 epoll_wait很简单


static void ep_poll_safewake(struct poll_safewake *psw, wait_queue_head_t *wq)
{
	int wake_nests = 0;
	unsigned long flags;
	struct task_struct *this_task = current;
	struct list_head *lsthead = &psw->wake_task_list;
	struct wake_task_node *tncur;
	struct wake_task_node tnode;

	spin_lock_irqsave(&psw->lock, flags);

	
	list_for_each_entry(tncur, lsthead, llink) {

		if (tncur->wq == wq ||
		    (tncur->task == this_task && ++wake_nests > EP_MAX_POLLWAKE_NESTS)) {
			
			spin_unlock_irqrestore(&psw->lock, flags);
			return;
		}
	}

	
	tnode.task = this_task;
	tnode.wq = wq;
	list_add(&tnode.llink, lsthead);

	spin_unlock_irqrestore(&psw->lock, flags);

	
	wake_up_nested(wq, 1 + wake_nests);

	
	spin_lock_irqsave(&psw->lock, flags);
	list_del(&tnode.llink);
	spin_unlock_irqrestore(&psw->lock, flags);
}

4、终于迎来最终函数 epoll_wait
1、豁然开朗 epoll_wait 核心代码解析(上)

老规矩 先贴代码吧

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
		int, maxevents, int, timeout)
{
	int error;
	struct file *file;
	struct eventpoll *ep;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d)n",
		     current, epfd, events, maxevents, timeout));

	
	if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
		return -EINVAL;

	
	if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {
		error = -EFAULT;
		goto error_return;
	}

	
	error = -EBADF;
	file = fget(epfd);
	if (!file)
		goto error_return;

	
	error = -EINVAL;
	if (!is_file_epoll(file))
		goto error_fput;

	
	ep = file->private_data;

	
	error = ep_poll(ep, events, maxevents, timeout);

error_fput:
	fput(file);
error_return:
	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d) = %dn",
		     current, epfd, events, maxevents, timeout, error));

	return error;
}

我们还是看看核心代码吧
给的注释已经很清晰了 咱直接去看看ep_poll吧


2、豁然开朗 epoll_wait 核心代码解析(中)

先贴代码 代码相对select/poll简单得多

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
	int res, eavail;
	unsigned long flags;
	long jtimeout;
	wait_queue_t wait;

	
	jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?
		MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;

retry:
	spin_lock_irqsave(&ep->lock, flags);

	res = 0;
	if (list_empty(&ep->rdllist)) {
		
		init_waitqueue_entry(&wait, current);
		wait.flags |= WQ_FLAG_EXCLUSIVE;
		__add_wait_queue(&ep->wq, &wait);

		for (;;) {
			
			set_current_state(TASK_INTERRUPTIBLE);
			if (!list_empty(&ep->rdllist) || !jtimeout)
				break;
			if (signal_pending(current)) {
				res = -EINTR;
				break;
			}

			spin_unlock_irqrestore(&ep->lock, flags);
			jtimeout = schedule_timeout(jtimeout);
			spin_lock_irqsave(&ep->lock, flags);
		}
		__remove_wait_queue(&ep->wq, &wait);

		set_current_state(TASK_RUNNING);
	}

	
	eavail = !list_empty(&ep->rdllist);

	spin_unlock_irqrestore(&ep->lock, flags);

	
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
		goto retry;

	return res;
}

图片上已经写的非常非常清楚了
我发现这样好像还更直观一点 最后用文字写一下 最后的最后去看看ep_send_events 就可以结束本篇啦
看完ep_send_events 也可以知道ET 和 EPOLLONESHOT的工作原理啦


3、豁然开朗 epoll_wait 核心代码解析(下)

最后一个函数了 有点不舍啊
但写完就去吃午饭了 还是早点写完好一点
老样子 贴个代码

static int ep_send_events(struct eventpoll *ep, struct epoll_event __user *events,
			  int maxevents)
{
	int eventcnt, error = -EFAULT, pwake = 0;
	unsigned int revents;
	unsigned long flags;
	struct epitem *epi, *nepi;
	struct list_head txlist;

	INIT_LIST_HEAD(&txlist);

	
	mutex_lock(&ep->mtx);

	
	spin_lock_irqsave(&ep->lock, flags);
	list_splice(&ep->rdllist, &txlist);
	INIT_LIST_HEAD(&ep->rdllist);
	ep->ovflist = NULL;
	spin_unlock_irqrestore(&ep->lock, flags);

	
	for (eventcnt = 0; !list_empty(&txlist) && eventcnt < maxevents;) {
		epi = list_first_entry(&txlist, struct epitem, rdllink);

		list_del_init(&epi->rdllink);

		
		revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
		revents &= epi->event.events;

		
		if (revents) {
			if (__put_user(revents,
				       &events[eventcnt].events) ||
			    __put_user(epi->event.data,
				       &events[eventcnt].data))
				goto errxit;
			if (epi->event.events & EPOLLONESHOT)
				epi->event.events &= EP_PRIVATE_BITS;
			eventcnt++;
		}
		
		if (!(epi->event.events & EPOLLET) &&
		    (revents & epi->event.events))
			list_add_tail(&epi->rdllink, &ep->rdllist);
	}
	error = 0;

errxit:

	spin_lock_irqsave(&ep->lock, flags);
	
	for (nepi = ep->ovflist; (epi = nepi) != NULL;
	     nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {
		
		if (!ep_is_linked(&epi->rdllink))
			list_add_tail(&epi->rdllink, &ep->rdllist);
	}
	
	ep->ovflist = EP_UNACTIVE_PTR;

	
	list_splice(&txlist, &ep->rdllist);

	if (!list_empty(&ep->rdllist)) {
		
		if (waitqueue_active(&ep->wq))
			wake_up_locked(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}
	spin_unlock_irqrestore(&ep->lock, flags);

	mutex_unlock(&ep->mtx);

	
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);

	return eventcnt == 0 ? error: eventcnt;
}

下面图中注释很多 写的很详细
个人觉得理解应该是没有问题的 如有大牛觉得我理解有误 感谢在下方指针
总的运行流程就是这样了 最后在下文中总结一下吧


3、浅浅总结一下 epoll 以及 select和poll

epoll的运行机制
就是不再像select 和 poll一样 每次调用都会把fd重新复制到内核区 然后再添加到设备的等待队列
epoll则是 对于每一个注册监视的fd 都分配一块内存 放入rb_tree进行管理 在内核区管理

epoll_wait每次调用 其实做的事情只有两件 一个是睡眠等待被唤醒 而是检查ready_list是否有描述符准备好了 准备好了每次从ready_list取出来即可
而select和poll 则是每次调用 都会去对应的fd底下去注册 在调用结束后 又去销毁对应的等待结构体

epoll之所以高效 是因为不会多次拷贝fd监视队列进入内核 只会在刚开始注册的时候 才会拷贝 之后就一直在内核中的红黑树处 而且监视队列也一直在相应的等待队列中 不会在每次调用结束后就回收

如果在调用结束后 有设备准备好了 也会异步的传入内核中的ready_list 下次调用epoll_wait的时候 我们只需要检查ready_list即可 不需要做额外的任何动作
更何况采用的是红黑树管理相关数据结构 查找和定位某个描述符也是相当高效

总的来说也就是这样了 呼 终于写完了 不容易啊
那么这个系列 也就宣告完结啦 关于Select/Poll/Epoll的解析也都全部结束啦

希望我 分析源码 写博客所花的两天时间 是有价值的
能够帮助到大家 同时也是我对Select/Poll/Epoll的一次直接接触

那大家江湖再见 ヾ( ̄▽ ̄)Bye~Bye~

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/914566.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号