【TPLI】63.4 epoll编程接口

63.4 epoll编程接口

select()poll()和信号驱动IO一样,Linux的epoll API可以检查多个文件描述符上的IO就绪状态。

  • 当检查大量的文件描述符时,epoll 的性能延展性比select()和poll()高很多。
  • epoll API既支持水平触发也支持边缘触发

epoll API是Linux系统专有的,在2.6版中新增。

epoll API的核心数据结构称作epoll实例,它和一个文件描述符相关联,这个文件描述符是内核数据结构的句柄。这些内核数据结构实现了两个目的:

  • 记录了在进程中声明过的感兴趣的文件描述符列表—interest list(兴趣列表)。
  • 维护了处于I/O 就绪态的文件描述符列表—ready list(就绪列表)。

epoll API由以下3个系统调用组成:

  • 系统调用epoll_create()创建一个epoll 实例,返回代表该实例的文件描述符。
  • 系统调用epoll_ctl()操作同epoll 实例相关联的兴趣列表。通过参数指定宏完成不同操作。
  • 系统调用epoll_wait()返回与epoll 实例相关联的就绪列表中的成员个数。值-参数返回对应信息。

63.4.1 创建epoll实例:epoll_create()

系统调用epoll_create()创建了一个新的epoll 实例,其对应的兴趣列表初始化为空。

#include <sys/epoll.h>
int epoll_create(int size);
// 成功返回新创建的epoll实例的文件描述符,失败返回-1并设置errno
  • 参数size现已无意义,只要指定一个大于0的数字即可。

  • epoll_create()返回了代表新创建的epoll实例的文件描述符,这个文件描述符在其它几个epoll系统调用中用来表示epoll实例。当这个文件描述符不再需要时,**应该通过close()来关闭。**当所有与epoll 实例相关的文件描述符都被关闭时,实例被销毁,相关的资源都返还给系统。(有争议,TPLI P1114上说要关闭epollfd)。

63.4.2 修改epoll实例:epoll_ctl()

系统调用epoll_ctl()能够修改由文件描述符epfd 所代表的epoll 实例中的兴趣列表。

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
// 成功返回0, 失败返回-1并设置errno
  • 参数fd指明要修改兴趣列表中的哪一个文件描述符的设定。如果fd是普通文件或目录的文件描述符,会设置EPERM

  • 参数op用来指定需要执行的操作,如下:

    • EPOLL_CTL_ADD:将描述符fd 添加到epoll 实例epfd 中的兴趣列表中去。对于fd 上我们感兴趣的事件,都
      指定在ev 所指向的结构体中。

    • EPOLL_CTL_MOD:修改描述符fd 上设定的事件,需要用到由ev 所指向的结构体中的信息。

    • EPOLL_CTL_DEL:将文件描述符fd 从epfd 的兴趣列表中移除。该操作忽略参数ev关闭一个文件描述符会自动将其从所有的epoll实例的兴趣列表中移除。(注意要看关闭的这个文件描述符的引用计数是否为0,为0才会被移除)。

  • 参数ev是指向结构体epoll_event的指针,结构体定义如下:

    struct epoll_event{
        uint32_t events;     // epoll events(bits mask)
        epoll_data_t data;   // User data 
    }
    
    typedef union epoll_data{
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
    
    • events字段是一个位掩码,它指定了我们为待检查的描述符fd上所感兴趣的事件集合。
    • data字段是一个联合体,当描述符fd稍后成为就绪态时,联合体的成员可用来指定传回给调用进程的信息。

63.4.3 事件等待:epoll_wait()

系统调用epoll_wait()返回epoll 实例中处于就绪态的文件描述符信息。

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
// 成功返回数组evlist中的元素个数,失败返回-1并设置errno
  • 参数evlist所指向的结构体数组中返回的是有关就绪文件描述符的信息,是一个**“值-参数”**。数组evlist的空间由调用者负责申请,所包含的元素个数在参数maxevents中指定。
  • 返回的数组evlist中,每个元素返回的都是单个就绪态文件描述符的信息。
    • events字段返回了在该描述符上已经发生的事件掩码。
    • data字段返回的是我们在描述符上使用epoll_ctl注册感兴趣的事件时在ev.data中所指定的值。data字段是唯一可获知同这个事件相关的文件描述符的途径,因此当我们调用epoll_ctl时,应该要么将ev.data.fd设为文件描述符号,要么将ev.data.ptr设为指向包含文件描述符号的结构体。
  • 参数timeout用来确定epoll_wait的阻塞行为。
    • timeout == -1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件发生。
    • timeout == 0,执行一次非阻塞式的检查
    • timeout > 0,调用将阻塞至多timeout毫秒。
epoll事件

在这里插入图片描述

63.4.4 深入探究epoll的语义

当我们通过epoll_create()创建一个epoll实例时,内核在内存中创建了一个新的inode并打开文件描述,随后在调用进程中为这个打开的文件描述分配一个新的文件描述符。同epoll实例的兴趣列表相关联的是打开的文件描述或者说文件表象,而不是epoll文件描述符。因此:dup()fork()复制的文件描述符和原来的文件描述符所指向的epoll数据结构是相同的。

63.4.5 epoll与select、poll的性能对比

在这里插入图片描述

epoll性能好的原因:

  • 每次调用select()poll()时,内核必须线性检查所有在调用中指定的文件描述符。而epoll采用的的回调机制,每当执行IO操作使得文件描述符成为就绪态时,内核就在epoll描述符的就绪列表中添加一个元素。
  • 每次调用select()poll()时,我们都要传递一个标记了所有待监视的文件描述符的数据结构给内核。而epoll中这是两个操作epoll_ctl可以修改兴趣列表,稍后每次调用epoll_wait就不需要在传递任何与文件描述符有关的信息给内核了。
  • 除此之外,select()每次调用前都要初始化输入数据。而且无论是select()还是poll(),我们都要对返回的数据结构做检查,以此找出这些文件描述符中有哪些是处于就绪态的。不过相比起来,系统同时监视N个文件描述符花费的时间大得多。

63.4.6 边缘触发通知

边缘触发通知通常和非阻塞的文件描述符结合使用。采用epoll的边缘触发通知机制的程序框架如下:

  • 将监视的文件描述符设置为非阻塞的。
  • 通过epoll_ctl()构建epoll的兴趣列表。
  • 通过如下的循环处理IO事件。
    • 调用epoll_wait()取得处于就绪态的描述符列表。
    • 针对每一个处于就绪态的文件描述符,不断进行IO处理直到相关的系统调用返回EAGAINEWOULDBLOCK错误。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值