这样,我们就看下最终唤醒epoll_wait的ep_poll_callback函数:
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key) { // 获取wait对应的epitem struct epitem *epi = ep_item_from_wait(wait); // epitem对应的eventpoll结构体 struct eventpoll *ep = epi->ep; // 获取自旋锁,保护ready_list等结构 spin_lock_irqsave(&ep->lock, flags); // 如果当前epi没有被链入ep的ready list,则链入 // 这样,就把当前的可用事件加入到epoll的可用列表了 if (!ep_is_linked(&epi->rdllink)) list_add_tail(&epi->rdllink, &ep->rdllist); // 如果有epoll_wait在等待的话,则唤醒这个epoll_wait进程 // 对应的&ep->wq是在epoll_wait调用的时候通过init_waitqueue_entry(&wait, current)而生成的 // 其中的current即是对应调用epoll_wait的进程信息task_struct if (waitqueue_active(&ep->wq)) wake_up_locked(&ep->wq); }上述过程如下图所示:
最后wake_up_locked调用__wake_up_common,然后调用了在init_waitqueue_entry注册的default_wake_function,调用路径为: wake_up_locked |->__wake_up_common |->default_wake_function |->try_wake_up (wake up a thread) |->activate_task |->enqueue_task running
将epoll_wait进程推入可运行队列,等待内核重新调度进程,然后epoll_wait对应的这个进程重新运行后,就从schedule恢复,继续下面的ep_send_events(向用户空间拷贝事件并返回)。
wake_up过程如下图所示:
可写事件到来
可写事件的运行过程和可读事件大同小异:
首先,在epoll_ctl_add的时候预先会调用一次对应文件描述符的poll,如果返回事件里有可写掩码的时候直接调用wake_up_locked以唤醒对应的epoll_wait进程。 然后,在tcp在底层驱动有数据到来的时候可能携带了ack从而可以释放部分已经被对端接收的数据,于是触发可写事件,这一部分的调用链为: tcp_input.c tcp_v4_rcv |-tcp_v4_do_rcv |-tcp_rcv_state_process |-tcp_data_snd_check |->tcp_check_space |->tcp_new_space |->sk->sk_write_space /* tcp下即是sk_stream_write_space*/
最后在此函数里面sk_stream_write_space唤醒对应的epoll_wait进程
void sk_stream_write_space(struct sock *sk) { // 即有1/3可写空间的时候才触发可写事件 if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) && sock) { clear_bit(SOCK_NOSPACE, &sock->flags); if (sk->sk_sleep && waitqueue_active(sk->sk_sleep)) wake_up_interruptible_poll(sk->sk_sleep, POLLOUT | POLLWRNORM | POLLWRBAND) ...... } }关闭描述符(close fd)
值得注意的是,我们在close对应的文件描述符的时候,会自动调用eventpoll_release将对应的file从其关联的epoll_fd中删除,kernel关键路径如下:
所以我们在关闭对应的文件描述符后,并不需要通过epoll_ctl_del来删掉对应epoll中相应的描述符。
总结
epoll作为linux下非常优秀的事件触发机制得到了广泛的运用。其源码还是比较复杂的,本文只是阐述了epoll读写事件的触发机制,探究linux kernel源码的过程非常快乐^_^。
Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx