文章

linux epoll

libevent_benchmark

1. epoll 与 select/poll 区别

  1. select由于采用轮询的方式,即轮询所有文件描述符。现实情况中,并发活跃的连接数远小于总连接数(文件描述符列表),select的效率较低。
  2. pollselect类似,也是采用轮询的方式,但是poll没有最大文件描列表长度述符限制(默认是FD_SETSIZE = 1024)。

例如,poll函数使用pollfd数组来查询事件,每次都需要将该pollfd数组传递给内核,内核再遍历该数组,查找有事件发生的fd,特别是数组比较大的时候,效率低下。

相比较而言,epoll只在有事件发生时才通知用户程序,效率较高。epoll使用三个高效的数据结构:

  • mmap: 内核空间和用户空间共享一块内存(epoll_event数组)。epoll_wait过程中,内核将等到的event数据直接写入到epoll_event数组中。
  • 红黑树: epoll_ctl将需要监听的文件描述符(针对网络通信就是套接字)时,保存在红黑树中。添加/删除/索引的时间复杂度为O(log n)
  • rdlist: rdlist是内核存储的就绪事件列表,当有事件发生时比如套接字数据可读,驱动将fd对应的epitem加入到rdlist中(通过fd上的回调函数ep_poll_callback)。epoll_waitrdlist中取出epitem,更新对应的epoll_event数据,并返回给用户程序。

具体流程如下:

eventpoll_epitem

  1. epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。
  2. 文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。
  3. ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
  4. ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
  5. ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。

2. ET模式与LT模式区别

LT模式:当fd就绪时,epoll_wait 会一直返回该fd,直到事件被处理。例如,如果一个socket连接有数据可读,epoll_wait 会每次都返回该socket fd,直到数据被完全读取。

ET模式: 当fd从未就绪变为就绪时,epoll_wait 只会返回一次该fd。例如,如果一个socket有数据可读,epoll_wait 只会在数据第一次到达时返回该套接字,之后即使有更多数据到达,也不会再次返回,直到所有数据被读取完并且socket fd再次变为未就绪状态

3. 设置socket为非阻塞

Blocking readNon-blocking read
blocking_readnonblocking_read

ET模式下,使用阻塞模式socket,如果数据量较大,需要多次read,最后一次可能没有数据可读,此时read将一直阻塞。使用非阻塞模式socketread返回EWOULDBLOCK即代表数据读完。

4. epoll 调用流程图

graph TD
    A[启动服务器] --> B[创建 epoll 实例]
    B --> C[创建监听套接字]
    C --> D[设置监听套接字为非阻塞]
    D --> E[绑定监听套接字到指定端口]
    E --> F[监听连接请求]
    F --> G[将监听套接字添加到 epoll 实例中]
    G --> H[进入事件循环]
    H --> I[调用 epoll_wait 等待事件发生]
    I --> J{有事件发生吗?}
    J -->|是| K[处理就绪事件]
    J -->|否| I
    K --> L{事件类型}
    L -->|新连接| M[接受新连接]
    M --> N[设置新连接套接字为非阻塞]
    N --> O[将新连接套接字添加到 epoll 实例中]
    L -->|可读事件| P[读取数据]
    P --> Q{读取成功吗?}
    Q -->|是| R[处理读取的数据]
    Q -->|否| S[关闭套接字]
    R --> T[将套接字修改为可写事件]
    L -->|可写事件| U[写入数据]
    U --> V[将套接字修改为可读事件]
    S --> H
    O --> H
    T --> H
    V --> H

参考

本文由作者按照 CC BY 4.0 进行授权