初步阅读EPOLL源码

数据结构

//   fs/eventpoll.c
struct eventpoll {
	/* Protect the access to this structure */
	spinlock_t lock;
 
	/*
	 * This mutex is used to ensure that files are not removed
	 * while epoll is using them. This is held during the event
	 * collection loop, the file cleanup path, the epoll file exit
	 * code and the ctl operations.
	 */
	struct mutex mtx;
 
	/* Wait queue used by sys_epoll_wait() */
	wait_queue_head_t wq;
 
	/* Wait queue used by file->poll() */
	wait_queue_head_t poll_wait;
 
	/* List of ready file descriptors */
	struct list_head rdllist;
 
	/* RB tree root used to store monitored fd structs */
	struct rb_root rbr;
 
	/*
	 * This is a single linked list that chains all the "struct epitem" that
	 * happened while transferring ready events to userspace w/out
	 * holding ->lock.
	 */
	struct epitem *ovflist;
 
	/* wakeup_source used when ep_scan_ready_list is running */
	struct wakeup_source *ws;
 
	/* The user that created the eventpoll descriptor */
	struct user_struct *user;
 
	struct file *file;
 
	/* used to optimize loop detection check */
	int visited;
	struct list_head visited_list_link;
};

上面这个是epoll主要的数据结构,包含了epoll会用到的所有信息:就绪的文件描述符列表,使用epoll的用户信息,epoll fd这个文件描述符的信息等等。

struct epitem {
	/* RB tree node used to link this structure to the eventpoll RB tree */
	struct rb_node rbn;
 
	/* List header used to link this structure to the eventpoll ready list */
	struct list_head rdllink;
 
	/*
	 * Works together "struct eventpoll"->ovflist in keeping the
	 * single linked chain of items.
	 */
	struct epitem *next;
 
	/* The file descriptor information this item refers to */
	struct epoll_filefd ffd;
 
	/* Number of active wait queue attached to poll operations */
	int nwait;
 
	/* List containing poll wait queues */
	struct list_head pwqlist;
 
	/* The "container" of this item */
	struct eventpoll *ep;
 
	/* List header used to link this item to the "struct file" items list */
	struct list_head fllink;
 
	/* wakeup_source used when EPOLLWAKEUP is set */
	struct wakeup_source __rcu *ws;
 
	/* The structure that describe the interested events and the source fd */
	struct epoll_event event;
};

上面这个数据结构可以看做是eventpoll里面红黑树的节点,只是这个节点里面还会维护自己的一些信息,最熟悉的应该就是最后一个成员了:epoll_event:这个结构体里面会包括我们感兴趣的文件描述符和其他一些东西。

函数

epoll_create

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
	int error, fd;
	struct eventpoll *ep = NULL;
	struct file *file;
 
	/* 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").
	 */
	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.
	 */
	fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
	if (fd < 0) {
		error = fd;
		goto out_free_ep;
	}
	file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
				 O_RDWR | (flags & O_CLOEXEC));
	if (IS_ERR(file)) {
		error = PTR_ERR(file);
		goto out_free_fd;
	}
	ep->file = file;
	fd_install(fd, file);
	return fd;
 
out_free_fd:
	put_unused_fd(fd);
out_free_ep:
	ep_free(ep);
	return error;
}

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;
 
	return 0;
 
free_uid:
	free_uid(user);
	return error;
}

可以看到epoll_create 会通过调用ep_alloc 函数来创建并初始化一个eventpoll结构体,之后会分配一个fd给这个eventpoll [这个就可以说明epfd就是一个普通的文件描述符],最后在文件描述表里面加入这个epfd,并返回这个epfd,所以我们就可以得到这个epfd并进行进一步的操作。

epoll_ctl

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
		struct epoll_event __user *, event)
{
	int error;
	int did_lock_epmutex = 0;
	struct file *file, *tfile;
	struct eventpoll *ep;
	struct epitem *epi;
	struct epoll_event epds;
 
	error = -EFAULT;
	if (ep_op_has_event(op) &&
	    copy_from_user(&epds, event, sizeof(struct epoll_event)))
		goto error_return;
 
	/* Get the "struct file *" for the eventpoll file */
	error = -EBADF;
	file = fget(epfd);
	if (!file)
		goto error_return;
 
	/* Get the "struct file *" for the target file */
	tfile = fget(fd);
	if (!tfile)
		goto error_fput;
 
	/* The target file descriptor must support poll */
	error = -EPERM;
	if (!tfile->f_op || !tfile->f_op->poll)
		goto error_tgt_fput;
 
	/* Check if EPOLLWAKEUP is allowed */
	if ((epds.events & EPOLLWAKEUP) && !capable(CAP_BLOCK_SUSPEND))
		epds.events &= ~EPOLLWAKEUP;
 
	/*
	 * We have to check that the file structure underneath the file descriptor
	 * the user passed to us _is_ an eventpoll file. And also we do not permit
	 * adding an epoll file descriptor inside itself.
	 */
	error = -EINVAL;
	if (file == tfile || !is_file_epoll(file))
		goto error_tgt_fput;
 
	/*
	 * At this point it is safe to assume that the "private_data" contains
	 * our own data structure.
	 */
	ep = file->private_data;
 
	/*
	 * When we insert an epoll file descriptor, inside another epoll file
	 * descriptor, there is the change of creating closed loops, which are
	 * better be handled here, than in more critical paths. While we are
	 * checking for loops we also determine the list of files reachable
	 * and hang them on the tfile_check_list, so we can check that we
	 * haven't created too many possible wakeup paths.
	 *
	 * We need to hold the epmutex across both ep_insert and ep_remove
	 * b/c we want to make sure we are looking at a coherent view of
	 * epoll network.
	 */
	if (op == EPOLL_CTL_ADD || op == EPOLL_CTL_DEL) {
		mutex_lock(&epmutex);
		did_lock_epmutex = 1;
	}
	if (op == EPOLL_CTL_ADD) {
		if (is_file_epoll(tfile)) {
			error = -ELOOP;
			if (ep_loop_check(ep, tfile) != 0) {
				clear_tfile_check_list();
				goto error_tgt_fput;
			}
		} else
			list_add(&tfile->f_tfile_llink, &tfile_check_list);
	}
 
	mutex_lock_nested(&ep->mtx, 0);
 
	/*
	 * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
	 * above, we can be sure to be able to use the item looked up by
	 * ep_find() till we release the mutex.
	 */
	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;
		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) {
			epds.events |= POLLERR | POLLHUP;
			error = ep_modify(ep, epi, &epds);
		} else
			error = -ENOENT;
		break;
	}
	mutex_unlock(&ep->mtx);
 
error_tgt_fput:
	if (did_lock_epmutex)
		mutex_unlock(&epmutex);
 
	fput(tfile);
error_fput:
	fput(file);
error_return:
 
	return error;
}

先看下epoll_ctl 函数原型:

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

epoll_ctl 这个函数 首先会copy_from_user :把函数原型的最后一个参数拷贝进来,再获得epfd 和 fd 的详细信息,并且判断他们两个是否指向了同一个文件。前面都是断言判断。

  • 之后才是真正的对eventpoll数据结构进行操作:如果我们是对eventpoll内部的红黑树进行增加或者删除操作的话,我们需要先对eventpoll进行上锁保护,避免出现多个线程同时对同一个数据结构进行操作会破坏数据结构的这种问题。
  • 上锁之后,如果是EPOLL_CTL_ADD 那么就把fd和对应的文件insert到eventpoll内部的红黑树里面。
  • 这里的EPOLL_CTL_ADD,EPOLL_CTL_DEL 和 EPOLL_CTL_MOD 都是对红黑树进行操作。
  • 操作完成之后,释放锁并返回。

ep_insert

static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
		     struct file *tfile, int fd)
 
{
	int error, revents, pwake = 0;
	unsigned long flags;
	long user_watches;
	struct epitem *epi;
	struct ep_pqueue epq;
 
	user_watches = atomic_long_read(&ep->user->epoll_watches);
	if (unlikely(user_watches >= max_user_watches))
		return -ENOSPC;
	if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
		return -ENOMEM;
 
	/* Item initialization follow here ... */
	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;
	if (epi->event.events & EPOLLWAKEUP) {
		error = ep_create_wakeup_source(epi);
		if (error)
			goto error_create_wakeup_source;
	} else {
		RCU_INIT_POINTER(epi->ws, NULL);
	}
 
	epq.epi = epi;
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
 
	revents = ep_item_poll(epi, &epq.pt);
 

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

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

	ep_rbtree_insert(ep, epi);
 

	error = -EINVAL;
	if (reverse_path_check())
		goto error_remove_epi;
 

	spin_lock_irqsave(&ep->lock, flags);
 

	if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
		list_add_tail(&epi->rdllink, &ep->rdllist);
		ep_pm_stay_awake(epi);
 
		/* Notify waiting tasks that events are available */
		if (waitqueue_active(&ep->wq))
			wake_up_locked(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}
 
	spin_unlock_irqrestore(&ep->lock, flags);
 
	atomic_long_inc(&ep->user->epoll_watches);
	if (pwake)
		ep_poll_safewake(&ep->poll_wait);
 
	return 0;
 
error_remove_epi:
	spin_lock(&tfile->f_lock);
	if (ep_is_linked(&epi->fllink))
		list_del_init(&epi->fllink);
	spin_unlock(&tfile->f_lock);
 
	rb_erase(&epi->rbn, &ep->rbr);
 
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);
 
	wakeup_source_unregister(ep_wakeup_source(epi));
 
error_create_wakeup_source:
	kmem_cache_free(epi_cache, epi);
 
	return error;
}

这个是EPOLL_CTL_ADD会调用的函数

  • 这个函数开始会先初始化epitem节点里面的三个链表:就绪链表,等待链表,文件链表。
  • 紧接着会调用init_poll_funcptr 来设置回调函数:ep_ptable_queue_proc
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
				 poll_table *pt)
{
	struct epitem *epi = ep_item_from_epqueue(pt);
	struct eppoll_entry *pwq;
 
	if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
		init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
		pwq->whead = whead;
		pwq->base = epi;
		add_wait_queue(whead, &pwq->wait);
		list_add_tail(&pwq->llink, &epi->pwqlist);
		epi->nwait++;
	} else {
		/* We have to signal that an error occurred */
		epi->nwait = -1;
	}
}
  • ep_ptable_queue_proc这个函数里面又会调用init_waitqueue_func_entry函数来注册另外一个回调函数: ep_poll_callback,这个回调函数会激活当前线程,让当前进程从睡眠状态变成active状态。
  • 接下来把当前的epitem加入eventpoll 监听队列,并插入红黑树。
  • 如果当前item已经就绪,那么我们直接把这个item加入到active队列里面。并唤醒当前epfd。

epoll_wait

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
		int, maxevents, int, timeout)
{
	int error;
	struct fd f;
	struct eventpoll *ep;
 
	/* The maximum number of event must be greater than zero */
	if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
		return -EINVAL;
 
	/* Verify that the area passed by the user is writeable */
	if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event)))
		return -EFAULT;
 
	/* Get the "struct file *" for the eventpoll file */
	f = fdget(epfd);
	if (!f.file)
		return -EBADF;
 
	/*
	 * We have to check that the file structure underneath the fd
	 * the user passed to us _is_ an eventpoll file.
	 */
	error = -EINVAL;
	if (!is_file_epoll(f.file))
		goto error_fput;
 
	/*
	 * At this point it is safe to assume that the "private_data" contains
	 * our own data structure.
	 */
	ep = f.file->private_data;
 
	/* Time to fish for events ... */
	error = ep_poll(ep, events, maxevents, timeout);
 
error_fput:
	fdput(f);
	return error;
}

可以看到epoll_wait会调用ep_poll函数

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
	int res = 0, eavail, timed_out = 0;
	unsigned long flags;
	long slack = 0;
	wait_queue_t wait;
	ktime_t expires, *to = NULL;
 
	if (timeout > 0) {
		struct timespec end_time = ep_set_mstimeout(timeout);
 
		slack = select_estimate_accuracy(&end_time);
		to = &expires;
		*to = timespec_to_ktime(end_time);
	} else if (timeout == 0) {
		timed_out = 1;
		spin_lock_irqsave(&ep->lock, flags);
		goto check_events;
	}
 
fetch_events:
	spin_lock_irqsave(&ep->lock, flags);
 
	if (!ep_events_available(ep)) {
		/*
		 * We don't have any available event to return to the caller.
		 * We need to sleep here, and we will be wake up by
		 * ep_poll_callback() when events will become available.
		 */
		init_waitqueue_entry(&wait, current);
		__add_wait_queue_exclusive(&ep->wq, &wait);
 
		for (;;) {
			set_current_state(TASK_INTERRUPTIBLE);
			if (ep_events_available(ep) || timed_out)
				break;
			if (signal_pending(current)) {
				res = -EINTR;
				break;
			}
 
			spin_unlock_irqrestore(&ep->lock, flags);
			if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
				timed_out = 1;
 
			spin_lock_irqsave(&ep->lock, flags);
		}
		__remove_wait_queue(&ep->wq, &wait);
 
		set_current_state(TASK_RUNNING);
	}
check_events:
	eavail = ep_events_available(ep);
 
	spin_unlock_irqrestore(&ep->lock, flags);
 
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
		goto fetch_events;
 
	return res;
}
 
static inline int ep_events_available(struct eventpoll *ep)
{
	return !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;
}

fetch_event : 如果还没有消息就绪的话,那么就阻塞在__add_wait_queue_exclusive这里,当前线程会被ep_poll_callback激活。
线程被重新激活之后,会进入死循环:如果已经有消息就绪的话 或者 超时 或者 出现错误的话,都会退出这个死循环。最后调用ep_send_events函数把ep就绪队列里面的事件拷贝到events——即我们调用epoll_wait传入的最后一个参数里面。

所以epoll的核心就是
1.红黑树:用来管理所有加入到epfd里面的epoll_event
2.epitem这个是用来衔接内核和用户态的数据结构。
3.回调机制:当有事件发生时,会调用回调函数把事件加入到ep->rdllist链表里面,如果链表不为空,那么就把就绪的事件拷贝到用户态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值