Linux IO多路转接之epoll

2023-12-13 04:42:02

文章目录

一、epoll初识

二、epoll的相关系统调用

1.epoll_create

2.epoll_ctl

3.epoll_wait

三、epoll工作原理

四、epoll的工作方式


本文主要介绍了epoll内部工作机制,如何达到高性能的多路转接。技术有限,如有错误请指正。参考文献:深入揭秘 epoll 是如何实现 IO 多路复用的


一、epoll初识

按照man手册:epoll是为处理大批量句柄而改进的poll,被公认为linux2.6下性能最好的io就绪通知方法

二、epoll的相关系统调用

epoll有三个相关调用

1.epoll_create

int epoll_create(int size);

创建一个epoll句柄,从Linux2.6.8之后,size参数是被忽略的,设置为>0即可。

用完之后,必须调用close()关闭

2.epoll_ctl

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

epoll的事件注册函数

  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型
  • epfd是创建的返回值
  • 第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD注册新的fd到epfd中 ;EPOLL_CTL_MOD修改已经注册的fd监听事件; EPOLL_CTL_DEL:从epfd中删除一个fd
  • 第三个参数是需要监听的fd
  • 第四个参数是告诉内核需要监听什么事件

struct epoll_event结构如下:

typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;


struct epoll_event
{
    uint32_t events;
    epoll_data_t data;
 } __EPOLL_PACKED;


  • events可以是几个宏的集合:
    EPOLLIN:表示对应的fd可以读(包括对端SOCKET正常关闭)
    EPOLLOUT:表示对应的fd可以写
    EPOLLPRI:表示对应的fd有紧急的数据可读(这里对应表示有带外数据到来)
    EPOLLERR:表示对应的fd描述符发生错误
    EPOLLHUP:表示对应的fd被挂断
    EPOLLET:将epoll设置为边缘触发(et),这是相对于水平触发(lt)来说的
    EPOLLONESHOT:只监听一次事件,当监听完这次事件后,如果还需要继续监听socket的话,需要再次把这个socket加入到epoll队列中

3.epoll_wait

int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);

收集在epoll监控的事件中已经发送的事件

  • 参数events是分配好的epll_event结构体数组
  • epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
  • maxevents告知内核这个events有多大,这个Maxevnets的值不能大于创建epoll_create()时的size
  • 参数timeout是超时事件(ms,0会立即返回,-1永久阻塞)
  • 函数调用成功,返回对应io上已经准备好的fd数目,如返回0表示已经超时,返回小于0表示函数失败

三、epoll工作原理

struct eventpoll
{
    /* 红黑树的根节点,这棵树中存储着所有添加到epoll当中需要监控的事件*/
    struct rb_tree;
    struct list_head rdlist;
};

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件

这些事件都会挂载在红黑树中,如此重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入效率是logn,其中n为树的高度)

而所有添加到epoll事件中都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时,都会调用这个回调方法

这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中

在epoll中,对于每一个事件,都会建立一个epitem结构体。

struct epitem
{
    struct rb_node rbn;          //红黑树节点
    struct list_head  rdllink;   //双向链表节点
    struct epoll_filefd ffd;     //事件句柄信息
    struct eventpoll *ep;        //指向其所属的eventpoll对象
    struct epoll_event event;    //期待发生的事件类型
}

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可

如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户,这个操作的事件复杂度为O(1)

epoll的优点

  • 接口使用方便:虽然拆分成了三个函数,但是使用起来更高效,不需要每次循环都设置关注的fd,也做到了输入输出参数分离
  • 数据拷贝轻量:只在合适的时候调用EPOLL_CTL_ADD将fd拷贝到内核中,这个操作并不频繁(而select,poll每次都需要进行拷贝)
  • 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的fd结构加入到就绪队列中,epoll_wait返回直接访问就绪队列就知道哪些fd就绪,这个操作事件复杂度O(1),即使fd很多,效率也不会收到影响
  • 没有数量限制,文件描述符数目无上限

有的地方说,epoll使用了内存映射机制,内存映射机制是:内核直接将就绪队列通过mmap的方式映射到用户态,避免了拷贝内存这样的额外性能开销。这种说法是不准确的,我们定义的struct epoll_evnet是我们在用户空间中分配好的内存,势必还是需要将内核中的数据结构拷贝到这个用户空间的内存中的。

四、epoll的工作方式

文章来源:https://blog.csdn.net/jolly0514/article/details/134648572
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。