欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

epoll源码剖析

程序员文章站 2022-06-14 11:22:27
...

epoll多路实现模型相比selecte、poll避免了出现文件描述符越多性能越差的情况,并且省去了大量内核/用户空间的拷贝和轮询所有文件描述符的系统消耗。为了理解epoll实现高并发,本文从源码了解epoll的实现机制。

epoll的用法:

int epoll_create(int size);//创建一个epoll fd
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);//监控文件描述符

epoll_create()


创建一系列数据结构并建立的联系。

int fd;//文件描述符
struct inode* inode;//内核用该结构在内部表示一个文件
struct file* file;//代表一个打开的文件
struct eventpoll;//epoll的描述符
epoll源码剖析
源码摘录

sys_epoll_create(int size);
>>	int error, fd;
	struct inode *inode;
	struct file *file;
	error = ep_getfd(&fd, &inode, &file);
		>>  file = get_empty_filp();//分配一个文件索引节点并初始化
			inode = ep_eventpoll_inode();//从eventpoll文件系统分配一个inode
			dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);
			d_add(dentry, inode);
			>>	d_instantiate(entry, inode);
				>>	list_add(&entry->d_alias, &inode->i_dentry);//file.inode指向inode
			fd_install(fd, file);
			>>	files->fd[fd] = file;//将文件索引点添加到进程中
			error = ep_file_init(file);//设置文件内部数据结构(“struct eventpoll”)
			>>		memset(ep, 0, sizeof(*ep));//初始化epoll描述符
					rwlock_init(&ep->lock);
					init_rwsem(&ep->sem);
					init_waitqueue_head(&ep->wq);
					init_waitqueue_head(&ep->poll_wait);
					INIT_LIST_HEAD(&ep->rdllist);
					ep->rbr = RB_ROOT;

					file->private_data = ep;//file.private_data指向epoll 描述符
return fd;//epfd
创建struct file、strcut inode 、struct eventpoll等结构体并初始化,建立file与inode的联系,task_struct与file之间的联系,

file与eventpolld之间的联系。


epoll_ctl()


插入、删除、更改epoll描述符中的内核文件描述符,下面以插入为例:

epoll源码剖析

源码摘录:

sys_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;//当前节点
	file = fget(epfd);//根据进程文件描述符获得文件对象的地址。并增加其引用计数。
	tfile = fget(fd);
	ep = file->private_data;
	epi = ep_find(ep, tfile, fd);//在eventpoll.rbr中查找fd

	ep_insert(ep, &epds, tfile, fd);//给fd注册事件
	>>		struct epitem *epi;
			struct ep_pqueue epq;
			/*struct ep_pqueue {
					poll_table pt;//poll_queue_proc qproc;
					struct epitem *epi;
					};
			*/
			epi->ep = ep;
			epi->event = *event;
			epq.epi = epi;
			init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
			>>	pt->qproc = qproc;
				/*
				static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)	//监听的fd生改变时,指定的调用函数会被调用
				unsigned int (*poll) (struct file *, struct poll_table_struct *);
				*/
			revents = tfile->f_op->poll(tfile, &epq.pt);//回调函数
			ep_rbtree_insert(ep, epi);//添加当前项到eventpoll.rbr
				/* If the file is already "ready" we drop it inside the ready list */
				if ((revents & event->events) && !EP_IS_LINKED(&epi->rdllink)) {
				list_add_tail(&epi->rdllink, &ep->rdllist);//如果当前事件就绪,把他放到就绪队列

				/* Notify waiting tasks that events are available */
				if (waitqueue_active(&ep->wq))
					wake_up(&ep->wq);
				if (waitqueue_active(&ep->poll_wait))
					pwake++;
				}
将用户空间的epoll_event拷贝到内核,只拷贝一次;

在eventpoll->rbr中查找fd是否存在;

设置回调函数并且插入节点。


epoll_wait()


向用户空间拷贝就绪描述符。
epoll源码剖析
源码摘录:
sys_epoll_wait()
	struct file *file;
	struct eventpoll *ep;
	file = fget(epfd);
	ep = file->private_data;
	ep_epoll(ep,events,maxevents,timeout);

	>>	if (list_empty(&ep->rdllist)) {//如果就绪队列为空
		init_waitqueue_entry(&wait, current);//初始化等待队列
		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;
			}

			write_unlock_irqrestore(&ep->lock, flags);
			jtimeout = schedule_timeout(jtimeout);//如果此时回调函数被调用,直接被唤醒
			write_lock_irqsave(&ep->lock, flags);
		}
		>>		if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {//收集就绪的描述符
				eventcnt = ep_send_events(ep, &txlist, events);
						revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
						epi->revents = revents & epi->event.events;//只返回相关的fd
				if (!res && eavail &&!(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)//向用户空间拷贝


如果epi->event.events 是 ET,epitem就不会在进入到就绪队列,除非fd再次发生了改变ep_poll_callback被调用
如果是LT,无论是否状态发生改变都会重新插入就绪队列,下次调用返回值为0。


参考资料:

epoll内核源码详解+自己总结的流程_技术交流_牛客网