上述过程如下图所示:
ep_insert
在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的初始化过程:
回顾一下上述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的结构如下图所示:
注:由于是tcp socket,所以这边sock->ops=inet_stream_ops
回调函数的安装
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的结构进一步完善,如下图所示:
ep_poll_callback函数是唤醒对应epoll_wait的地方,我们将在后面一起讲述。
epoll_wait
epoll_wait主要是调用了ep_poll: