linux 内核中epoll实现

目录

epoll_create

epoll_ctl

epoll_wait


epoll_create

struct eventpoll {

	/* 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_cached rbr;
}

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

    //申请epollevent内存
	ep = kzalloc(sizeof(*ep), GFP_KERNEL);

    //初始化等待队列
	init_waitqueue_head(&ep->wq);
	init_waitqueue_head(&ep->poll_wait);
    //初始化就绪列表    
	INIT_LIST_HEAD(&ep->rdllist);
    //初始化红黑树指针
	ep->rbr = RB_ROOT_CACHED;
...

}
  • wq等待队列链表。软中断数据就绪的时候会通过 wq 来找到阻塞在 epoll 对象上的⽤户进程。
  • rbr红⿊树。为了⽀持对海量连接的⾼效查找、插⼊和删除,eventpoll 内部使⽤了⼀棵红⿊树。通过这棵树来管理⽤户进程下添加进来的所有 socket 连接。
  • rdllist就绪的描述符的链表。当有连接就绪的时候,内核会把就绪的连接放到 rdllist链表⾥。应⽤进程只需要判断链表就能找出就绪进程,⽽不⽤去遍历整棵树。

epoll_ctl

添加 socket

1,分配一个红黑树节点对象epitem

2,添加等待事件到socket的等待队列中,其回调函数是ep_poll_callback

3,将epitem插入到epoll对象的红黑树里


int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
		 bool nonblock)
{

    //根据epfd找到eventpoll对象
	f = fdget(epfd);

	/* Get the "struct file *" for the target file */
    //根据socket句柄号,找到tf
	tf = fdget(fd);

	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;
	}

	return error;
}
static int ep_insert(struct eventpoll *ep, const struct epoll_event *event,
		     struct file *tfile, int fd, int full_check)
{
	//分配epitem
	if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))

	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;
	epq.epi = epi;//设置监听的fd事件

    //设置socket等待队列 并初始化ep_pqueue对象
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);//注册回调函数

    //实际调用ep_ptable_queue_proc,注册函数为ep_poll_callback
	revents = ep_item_poll(epi, &epq.pt, 1);

	//插入红黑树
	ep_rbtree_insert(ep, epi);

}
  • 分配epitem,它会被挂载到红黑树上;
  • 设置 socket 对象上的等待任务队列
  • ep_poll_callback 设置为数据就绪时候的回调函数

ep_ptable_queue_proc->ep_poll_callback

init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

将当前被监听的socket fd加入到等待队列中

static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
				 poll_table *pt)
{

	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;
        //如果设置了EPOLLEXCLUSIVE
		if (epi->event.events & EPOLLEXCLUSIVE)
			add_wait_queue_exclusive(whead, &pwq->wait);
		else
			add_wait_queue(whead, &pwq->wait);
		list_add_tail(&pwq->llink, &epi->pwqlist);
		epi->nwait++;
	}
}

 如果一个socket fd被添加到了多个epoll中进行监控,设置了这个参数后,这个fd上有事件发生时,只会唤醒被添加到的第一个epoll里,避免惊群。

static inline void
init_waitqueue_func_entry(struct wait_queue_entry *wq_entry, wait_queue_func_t func)
{
	wq_entry->flags		= 0;
	wq_entry->private	= NULL;
	wq_entry->func		= func;
}
  • private =NULL, socket 是交给epoll 来管理的,不需要在⼀个 socket 就绪的时候就唤醒进程。在前面分析recvfrom时,此处设置成当前进程描述符current。
  • ep_poll_callback注册到wait_queue_entry上,有数据到达时调用。

什么时候调用?软中断将数据收到 socket 的接收队列后,会通过注册的这个ep_poll_callback 函数来回调,进⽽通知到 epoll 对象。

插入红黑树

epoll 在查找效率、插⼊效率、内存开销等等多个⽅⾯⽐较均衡

ep_rbtree_insert(ep, epi);


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);
}


static int do_epoll_wait(int epfd, struct epoll_event __user *events,
			 int maxevents, int timeout)
{
	ep = f.file->private_data;

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

    ...
}


static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
     //是否有就绪事件
	eavail = ep_events_available(ep);

    do {
        //Internally init_wait() uses autoremove_wake_function()
        //初始化wait,保存上下文,当前进程,即当前进程插入epoll等待队列
        init_wait(&wait);


    	//是否有就绪队列
		eavail = ep_events_available(ep);
		if (!eavail) {//没有就绪队列,但是有信号需要处理
			if (signal_pending(current))
				res = -EINTR;
			else
				__add_wait_queue_exclusive(&ep->wq, &wait);//添加到等待队列
		}


		//阻塞当前进程
		if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) {
			timed_out = 1;
			break;
		}

	} while (0);

    }

}
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;

    //扫描就绪列表,调用f_op->poll()
	ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
	return esed.res;
}


看一下ep_send_events_proc

static __poll_t ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
			       void *priv)
{

	//遍历就绪队列
	list_for_each_entry_safe(epi, tmp, head, rdllink) {
		if (esed->res >= esed->maxevents)
			break;

		//移出就绪队列
		list_del_init(&epi->rdllink);

		revents = ep_item_poll(epi, &pt, 1);
		if (!revents)
			continue;
		//写入调用方传入的结构体,返回0则说明成功
		if (__put_user(revents, &uevent->events) ||
		    __put_user(epi->event.data, &uevent->data)) {
				//失败则插入队列中
			list_add(&epi->rdllink, head);

		}
		esed->res++;
		uevent++;
		//设置了EPOLLONESHOT 则清除订阅的事件
		if (epi->event.events & EPOLLONESHOT)
			epi->event.events &= EP_PRIVATE_BITS;
			//水平触发则重新插入,下次epoll_wait继续触发
		else if (!(epi->event.events & EPOLLET)) {

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

	return 0;
}

static __poll_t ep_item_poll(const struct epitem *epi, poll_table *pt,
				 int depth)
{
	struct eventpoll *ep;
	bool locked;

	pt->_key = epi->event.events;
	if (!is_file_epoll(epi->ffd.file))
		return vfs_poll(epi->ffd.file, pt) & epi->event.events;

	ep = epi->ffd.file->private_data;
	poll_wait(epi->ffd.file, &ep->poll_wait, pt);
	locked = pt && (pt->_qproc == ep_ptable_queue_proc);

	return ep_scan_ready_list(epi->ffd.file->private_data,
				  ep_read_events_proc, &depth, depth,
				  locked) & epi->event.events;
}

static inline __poll_t vfs_poll(struct file *file, struct poll_table_struct *pt)
{
	if (unlikely(!file->f_op->poll))
		return DEFAULT_POLLMASK;
	return file->f_op->poll(file, pt);
}

调用了 file->f_op->poll 实际上调用sock_poll

static __poll_t sock_poll(struct file *file, poll_table *wait)
{
	struct socket *sock = file->private_data;

	return sock->ops->poll(file, sock, wait) | flag;
}
__poll_t tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
	struct sock *sk = sock->sk;

	sock_poll_wait(file, sock, wait);
}

参考

https://zhuanlan.zhihu.com/p/384098769

GitHub - yanfeizhang/coder-kung-fu: 开发内功修炼


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

为了维护世界和平_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值