菜鸟笔记
提升您的技术认知

深入理解Linux IO复用之epoll

目录

select,poll, epoll的区别

epoll的基本函数 

epoll 的 lt / et 模式区别

通知

快速处理

 socket错误码和返回值

慢系统调用

EINTR错误的产生

在linux中IO复用有select,poll, epoll,但epoll是性能最好的,支持的并发量也最大。所以我们就专门来聊聊它。

select,poll, epoll的区别

epoll的基本函数 

epoll_create:创建一个 epoll 对象

epoll_ctl:对epoll实例执行控制操作,如EPOLL_CTL_ADD(注册新的fd到epfd中),EPOLL_CTL_MOD( 修改已经注册的fd的监听事件)、EPOLL_CTL_DEL(从epfd中删除一个fd)等

epoll_wait:等待其管理的连接上的 IO 事件

原理

流程图解说:

  1. 进程通过 epoll_create 创建 eventpoll 对象。
  2. 进程通过 epoll_ctl 添加关注 listen socket 的 EPOLLIN 可读事件。
  3. 接步骤 2,epoll_ctl 还将 epoll 的 socket 唤醒等待事件(唤醒函数:ep_poll_callback)通过 add_wait_queue 函数添加到 socket.wq 等待队列。

    当 listen socket 有链接资源时,内核通过 __wake_up_common 调用 epoll 的 ep_poll_callback 唤醒函数,唤醒进程。

  4. 进程通过 epoll_wait 等待就绪事件,往 eventpoll.wq 等待队列中添加当前进程的等待事件,当 epoll_ctl 监控的 socket 产生对应的事件时,被唤醒返回。
  5. 客户端通过 tcp connect 链接服务端,三次握手成功,第三次握手在服务端进程产生新的链接资源。
  6. 服务端进程根据 socket.wq 等待队列,唤醒正在等待资源的进程处理。例如 nginx 的惊群现象,__wake_up_common 唤醒等待队列上的两个等待进程,调用 ep_poll_callback 去唤醒 epoll_wait 阻塞等待的进程。
  7. ep_poll_callback 唤醒回调会检查 listen socket 的完全队列是否为空,如果不为空,那么就将 epoll_ctl 监控的 listen socket 的节点 epi 添加到 就绪队列:eventpoll.rdllist,然后唤醒 eventpoll.wq 里通过 epoll_wait 等待的进程,处理 eventpoll.rdllist 上的事件数据。
  8. 睡眠在内核的 epoll_wait 被唤醒后,内核通过 ep_send_events 将就绪事件数据,从内核空间拷贝到用户空间,然后进程从内核空间返回到用户空间。
  9. epoll_wait 被唤醒,返回用户空间,读取 listen socket 返回的 EPOLLIN 事件,然后 accept listen socket 完全队列上的链接资源。

epoll 的 lt / et 模式区别

通知

通过上面的原理可以知道,lt/et 模式区别的核心逻辑在 epoll_wait 的内核实现 ep_send_events_proc 函数里,即对就绪队列的处理方面。

从事件通知来说:

1.  et 模式,只通知用户一次,不管这个事件是否已经被用户处理完毕,直到该事件再次发生,或者用户通过 epoll_ctl 重新关注该 fd 对应的事件

2.   lt 模式,会不停地通知用户,直到用户把事件处理完毕

快速处理

1. lt 模式,epi 节点刚开始在内核被删除,然后数据从内核空间拷贝到用户空间后,内核马上将这个被删除的节点重新追加回就绪队列,这个速度很快,所以后面来的新的就绪事件很大几率会排在已经处理过的事件后面。

2. et 模式,数据从内核拷贝到用户空间后,内核不会重新将就绪事件节点添加回就绪队列,当事件在用户空间处理完后,用户空间根据需要重新将这个事件通过 epoll_ctl 添加回就绪队列(又或者这个节点因为有新的数据到来,重新触发了就绪事件而被添加)。从节点被删除到重新添加,这中间的过程是比较“漫长”的,所以新来的其它事件节点能排在旧的节点前面,能快速处理

 socket错误码和返回值

  • 返回值ret>0,则读取正确
  • 返回值ret=0,客户端连接关闭
  • 返回值ret<0,则需要看errno,当errno为EAGAIN或EWOULDBLOCK时,表明读取完毕,接受缓冲为空,在非阻塞IO下会立即返回-1.若errno不是上述标志,则说明读取数据出错,因该关闭连接,进行错误处理

代码如下:

慢系统调用

此术语适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用有可能永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就没有返回的保证。

EINTR错误的产生

EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程,当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号时,内核会致使accept返回一个EINTR错误(被中断的系统调用)。
 

在epoll_wait时,因为设置了alarm定时触发警告,导致每次返回-1,errno为EINTR,对于这种错误返回

忽略这种错误,让epoll报错误号为4时,再次做一次epoll_wait。