Linux 系统调用之 epoll 优点源码解析

epoll 优点

函数原型:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents; int timeout);

epoll 的三个主要优点:

  1. 只需要注册一次感兴趣的描述符,不需要每次调用都向内核空间传递;每次返回时,只需要返回就绪的描述符。
  2. 检查就绪的描述符时,使用回调函数,避免低效的遍历。
  3. 支持水平触发和边缘触发。

优点一 的实现原理

只需要注册一次感兴趣的描述符,不需要每次调用都向内核空间传递,每次返回时,只需要返回就绪的描述符。

内核中 eventpoll 实例的定义。

struct eventpoll {
	/* sys_epoll_wait() 的等待队列 */
	wait_queue_head_t wq;

	/* file->poll() 的等待队列 */
	wait_queue_head_t poll_wait;

	/* 就绪的文件描述符列表 */
	struct list_head rdllist;

	/* 使用红黑树保存目标描述符 */
	struct rb_root rbr;
	...
};

创建一个文件结构来保存 eventpoll 实例,该文件只包含一个匿名的 inode。以后所有描述符都注册到这个 eventpoll 实例中。这样在调用时不传递目标描述符。

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
	int error;
	struct eventpoll *ep = NULL;
	
	/* 创建内部数据结构 ("struct eventpoll"). */
	error = ep_alloc(&ep);
	if (error < 0)
		return error;
	/*
	 * 创建一个只包含一个匿名inode的文件结构,即 eventpoll file,
	 * 该文件包含 eventpoll 实例,并指定相应的文件操作函数
	 */
	error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
				 O_RDWR | (flags & O_CLOEXEC));
	if (error < 0)
		ep_free(ep);

	return error;
}

内核中使用红黑树保存目标描述符,这样在插入,查找,删除的时候只用 log(n) 的时间复杂度,更高效。

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
		struct epoll_event __user *, event)
{
	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);
		}
		break;
	case EPOLL_CTL_DEL:
		if (epi)
			error = ep_remove(ep, epi);
		break;
	case EPOLL_CTL_MOD:
		if (epi) {
			epds.events |= POLLERR | POLLHUP;
			error = ep_modify(ep, epi, &epds);
		} 
		break;
	}

	return error;
}

优点二的实现原理

避免了类似于 select 和 poll 中的轮询遍历,如果有就绪的描述符就立即返回;如果没有则休眠,让出CPU,一旦有描述符就绪,就能立即被唤醒并返回结果。

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
		int, maxevents, int, timeout)
{
	...
	/* Time to fish for events ... */
	error = ep_poll(ep, events, maxevents, timeout);
	
	return error;
}

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
retry:
	spin_lock_irqsave(&ep->lock, flags);

	res = 0;
	if (list_empty(&ep->rdllist)) {
		/*
		 * 如果没有任何就绪的描述符,就在此睡眠,
		 * 可被ep_poll_callback()->wake_up_locked(&ep->wq) 唤醒
		 * 唤醒后轮询遍历就绪列表
		 */
		init_waitqueue_entry(&wait, current);
		__add_wait_queue_exclusive(&ep->wq, &wait);

		for (;;) {
			/*
			 * 设置当前进程为可中断,
			 * 以便可以被 ep_poll_callback()
			 * ->ep_poll_safewake(&ep->poll_wait) 唤醒
			 */
			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);
	}
	spin_unlock_irqrestore(&ep->lock, flags);

	/*
	 * 试图向用户空间传递就绪的事件
	 * 如果没有就绪的事件,或者未超时,就再次循环尝试
	 */
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
		goto retry;

	return res;
}

回调函数实现如下,如果有描述符就绪,就唤醒 eventpoll 等待队列和 文件 poll_wait 等待队列。

static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	spin_lock_irqsave(&ep->lock, flags);

	/*
	 * 在 readylist 为空时,epoll_wait() 函数会睡眠于此,
	 * 因此唤醒 eventpoll wait list,唤醒以后便会轮询 readylist
	 */
	if (waitqueue_active(&ep->wq))
		wake_up_locked(&ep->wq);	
	if (waitqueue_active(&ep->poll_wait))
		pwake++;

	spin_unlock_irqrestore(&ep->lock, flags);

	/*
	 * 在调用 ep_insert() 时,会将目标描述符加入 poll_wait 等待队列,
	 * 因此也需要唤醒 poll_wait list,
	 * 唤醒以后如果有描述符就绪,就将其加入就绪队列
	 */
	if (pwake)
		ep_poll_safewake(&ep->poll_wait);

	return 1;
}

优点三的实现原理

首先检查是否设置 EPOLLONESHOT 标志位:

  • 如果没有设置,则将其再次加入就绪列表,以待下一次 epoll_wait() 检查,因此可以做到在就绪周期内,每次 epoll_wait() 都能返回,也就是水平触发。
  • 如果设置了,就从就绪列表删除,只触发一次,也就是边缘触发。
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv)
{
	for (eventcnt = 0, uevent = esed->events;
	     !list_empty(head) && eventcnt < esed->maxevents;) {
		epi = list_first_entry(head, struct epitem, rdllink);

		list_del_init(&epi->rdllink);

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

		if (revents) {
			if (__put_user(revents, &uevent->events) ||
			    __put_user(epi->event.data, &uevent->data)) {
				list_add(&epi->rdllink, head);
				return eventcnt ? eventcnt : -EFAULT;
			}
			eventcnt++;
			uevent++;
			/* 检查是否设置 EPOLLONESHOT 标志位 */
			if (epi->event.events & EPOLLONESHOT)	
				epi->event.events &= EP_PRIVATE_BITS;
			else if (!(epi->event.events & EPOLLET)) {
				/*
				 * 如果该文件设置了边缘触发模式,需要重新将其加入就绪列表,
				 * 以代下一次 epoll_wait() 的检查
				 */
				list_add_tail(&epi->rdllink, &ep->rdllist);
			}
		}
	}

	return eventcnt;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lylhw13_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值