Linux Epoll IO 模型

Epoll IO 模型是 Linux 中用于 I/O 多路复用的机制,可以用于监听多个文件描述符上的事件,以及非阻塞地等待这些事件的发生。其工作机制大致如下:

  1. 首先,初始化一个 Epoll 实例,这个实例主要在内核中维护的两个数据结构,一个红黑树,用于存储被检测的文件描述符,一个链表,用于存储就绪事件
  2. 然后,我们将要监控的文件描述符放到 Epoll 实例的红黑树中,然后内核开始遍历红黑树检测文件描述符是否有注册的事件发生
  3. 当有事件发生时,内核会将这个事件添加到链表中,等待用户处理
  4. 用户执行操作后,就绪事件就会从内核的链表中拷贝到用户空间中,用户就可以取出事件进行处理

在这个过程中,我们发现内核使用了红黑树来维护和管理待监控的文件描述符集合,红黑树并没有文件描述符上限的要求,所以,只要内存足够,我们可以添加更多的文件描述符到 Epoll 的内核红黑树中。这也有利于提高 epoll 的并发量。

但是,需要注意的是,在 Linux 系统中,默认情况下,每个进程可以打开的文件描述符数量是有限制的。如果超出了文件描述符的限制,就会出现无法打开新的文件描述符的情况,影响程序的正常运行。进程可以配置最大的文件描述符数量。

另外,与 Select IO 模型相比,每次的 Select 调用都需要进行两次内核和用户空间的数据交换。而 Epoll 则是在通知用户事件时,拷贝一次。这非常有利于提高 epoll 的性能。

用户处理事件时,不需要遍历所有的文件描述符,因为 epoll 只会返回已就绪的文件描述符,这也有利于提高 epoll 的性能。

下面是 Epoll 操作相关函数:

// 创建 epoll 实例
int epoll_create1 (int __flags)
  • __flags:如果设置为 EPOLL_CLOEXEC 表示程序在退出之前关闭文件描述符

函数如果失败,则返回 -1,可通过 errno 来查看具体错误信息

int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event)
  • __epfd:epoll 实例的文件描述符。
  • __op:要执行的操作,可以是以下值之一:
    • EPOLL_CTL_ADD:向 epoll 实例注册一个新的文件描述符。
    • EPOLL_CTL_MOD:修改已注册文件描述符的事件。
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符。
  • __fd:要注册、修改或删除的文件描述符。
  • __event:指向 epoll_event 结构体的指针,用于描述要注册、修改或删除的事件。可以为 NULL(仅在执行 EPOLL_CTL_DEL 操作时)

函数成功返回 0,否则返回 -1,可通过 errno 来指示错误。

int epoll_wait (int __epfd, struct epoll_event *__events, int __maxevents, int __timeout)
  • __epfd:epoll 实例的文件描述符。
  • __events:指向 epoll_event 结构体数组的指针,用于存储就绪的文件描述符和事件。
  • __maxeventsevents 数组中能存储的最大事件数。
  • __timeout:等待就绪事件的超时时间(以毫秒为单位)。可以为以下值之一:
    • -1:无限等待。
    • 0:立即返回。
    • 大于 0 的整数:等待指定毫秒数后返回。

函数执行成功,则返回就绪的文件描述符数量,否则返回 -1,可通过 errno 指示错误。

示例代码:

#include <cstdio>
#include <sys/epoll.h>
#include <set>


#define MAX_EVENT_NUM 128

void test()
{
    // 初始化 Epoll 对象
    int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (-1 == epoll_fd)
    {
        perror("epoll_create1 error");
        return;
    }
    
    // 已连接的文件描述符
    std::set<int> fds = { 100, 101, 102, 103, 104 };

    int ret = -1;

    // 注册文件描述符
    for (auto fd : fds)
    {
        // 设置文件描述符绑定的读写事件
        struct epoll_event fd_event;
        fd_event.events = EPOLLIN;  // 写事件
        fd_event.data.fd = fd;
        // 将文件描述符和事件存储到红黑树中
        ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &fd_event);
        if (-1 == ret)
        {
            perror("epoll_ctl error");
            continue;
        }
    }
    
    // 从 Epoll 中删除某个文件描述符
    int del_fd = 101;
    ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, del_fd, nullptr);
    if (-1 == ret)
    {
        perror("epoll_ctl error");
        return;
    }
    // 从 fds 中也要删除文件描述符
    fds.erase(del_fd);

    // 开始监控文件描述符
    struct epoll_event events[MAX_EVENT_NUM];
    ret = epoll_wait(epoll_fd, events, MAX_EVENT_NUM, -1);
    if (-1 == ret)
    {
        perror("epoll_wait error");
        return;
    }
}

int main()
{
    test();
    return 0;
}
未经允许不得转载:一亩三分地 » Linux Epoll IO 模型
评论 (0)

2 + 7 =