从linux源码看epoll (2)

在ep_insert中初始化了epitem,然后初始化了本文关注的焦点,即事件就绪时候的回调函数,代码如下所示:

static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd) { /* 初始化epitem */ // &epq.pt->qproc = ep_ptable_queue_proc init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); // 在这里将回调函数注入 revents = tfile->f_op->poll(tfile, &epq.pt); // 如果当前有事件已经就绪,那么一开始就会被加入到ready list // 例如可写事件 // 另外,在tcp内部ack之后调用tcp_check_space,最终调用sock_def_write_space来唤醒对应的epoll_wait下的进程 if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) { list_add_tail(&epi->rdllink, &ep->rdllist); // wake_up ep对应在epoll_wait下的进程 if (waitqueue_active(&ep->wq)){ wake_up_locked(&ep->wq); } ...... } // 将epitem插入红黑树 ep_rbtree_insert(ep, epi); ...... } tfile->f_op->poll的实现

向kernel更底层注册回调函数的是tfile->f_op->poll(tfile, &epq.pt)这一句,我们来看一下对于对应的socket文件描述符,其fd=>file->f_op->poll的初始化过程:

// 将accept后的事件加入到对应的epoll fd中 int client_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&client_len))); // 将连接描述符注册到对应的worker里面 epoll_ctl(reactor->client_fd,EPOLL_CTL_ADD,epifd,&event);

回顾一下上述user space代码,fd即client_fd是由tcp的listen_fd通过accept调用而来,那么我们看下accept调用链的关键路径:

accept |->accept4 |->sock_attach_fd(newsock, newfile, flags & O_NONBLOCK); |->init_file(file,...,&socket_file_ops); |->file->f_op = fop; /* file->f_op = &socket_file_ops */ |->fd_install(newfd, newfile); // 安装fd

那么,由accept获得的client_fd的结构如下图所示:

从linux源码看epoll


(注:由于是tcp socket,所以这边sock->ops=inet_stream_ops,这个初始化的过程在我的另一篇博客<<从linux源码看socket的阻塞和非阻塞>>中,博客地址如下:
https://my.oschina.net/alchemystar/blog/1791017)
既然知道了tfile->f_op->poll的实现,我们就可以看下此poll是如何将安装回调函数的。

回调函数的安装

kernel的调用路径如下:

sock_poll /*tfile->f_op->poll(tfile, &epq.pt)*/; |->sock->ops->poll |->tcp_poll /* 这边重要的是拿到了sk_sleep用于KSE(进程/线程)的唤醒 */ |->sock_poll_wait(file, sk->sk_sleep, wait); |->poll_wait |->p->qproc(filp, wait_address, p); /* p为&epq.pt,而且&epq.pt->qproc= ep_ptable_queue_proc*/ |-> ep_ptable_queue_proc(filp,wait_address,p);

绕了一大圈之后,我们的回调函数的安装其实就是调用了eventpoll.c中的ep_ptable_queue_proc,而且向其中传递了sk->sk_sleep作为其waitqueue的head,其源码如下所示:

static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) { // 取出当前client_fd对应的epitem struct epitem *epi = ep_item_from_epqueue(pt); // &pwq->wait->func=ep_poll_callback,用于回调唤醒 // 注意,这边不是init_waitqueue_entry,即没有将当前KSE(current,当前进程/线程)写入到 // wait_queue当中,因为不一定是从当前安装的KSE唤醒,而应该是唤醒epoll\_wait的KSE init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); // 这边的whead是sk->sk_sleep,将当前的waitqueue链入到socket对应的sleep列表 add_wait_queue(whead, &pwq->wait); }

这样client_fd的结构进一步完善,如下图所示:

从linux源码看epoll


ep_poll_callback函数是唤醒对应epoll_wait的地方,我们将在后面一起讲述。

epoll_wait

epoll_wait主要是调用了ep_poll:

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout) { /* 检查epfd是否是epoll\_create创建的fd */ // 调用ep_poll error = ep_poll(ep, events, maxevents, timeout); ... }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzjxd.html