美文网首页Linux
epoll的使用与源码分析

epoll的使用与源码分析

作者: 爱秋刀鱼的猫 | 来源:发表于2018-04-25 12:02 被阅读11次
什么是epoll?

epoll是linux中IO多路复用的一种机制,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。IO多路复用的方法,处理有select,poll,epoll。

epoll是为处理大批量句柄而作了改进的poll,被认为Linux下性能最好的多路I/O就绪通知方法。

  • epoll的相关系统调用
    epoll只有epoll_create , epoll_ctl , epoll_wait 3个系统调用。
    #创建epoll fd
    int epoll_create(int size);
    
    #添加epoll需要监听的事件
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    
    #收集在epoll监控的事件中已经发送的事件
    #当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符
    #而是一个代表就绪描述符数量的值,
    #你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,
    int epoll_wait(int epfd, struct epoll_event * events,
          int maxevents, int timeout);
    
程序的大体框架:
  • epoll的工作原理
  1. select/poll 每次调用都要传递所要监控的所有fd给系统调用(这意味着每次调用都要将fd列表从用户态拷贝到内核态,当fd数目很多时,这会造成低效)。

    而每次调用epoll_wait时(作用相当于调用select/poll),不需要再传递fd列表给内核,因为已经在epoll_ctl中将需要监控的fd告诉了内核(epoll_ctl不需要每次都拷贝所有的fd,只需要进行增量式操作)。所以,在调用epoll_create之后,内核已经在内核态开始准备数据结构存放要监控的fd了。每次epoll_ctl只是对这个数据结构进行简单的维护。

  1. select/poll一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。
  1. 当我们调用epoll_ctl往里塞入百万个fd时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的fd给我们用户。

    这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的fd外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。

    而且,通常情况下即使我们要监控百万计的fd,大多一次也只返回很少量的准备就绪fd而已,所以,epoll_wait仅需要从内核态copy少量的fd到用户态而已。那么,这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把fd放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个fd的中断到了,就把它放到准备就绪list链表里。所以,当一个fd(例如socket)上有数据到了,内核在把设备(例如网卡)上的数据copy到内核中后就来把fd(socket)插入到准备就绪list链表里了。

  • epoll比select 和 poll 好在哪里?

    1. 支持打开大数目的socket描述符(FD)不同

      a. select 方法是通过开辟一个数组来存储和维护fd,这个数组的长度是2048,也就是说select支持的fd是有限的。

      b. poll 是通过链表来维护fd的,所有数量上面没有限制。

    2. IO效率不随FD数目增加而线性下降
      传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。

      但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数。

    3. 不需要在用户空间和内核空间之间频繁的拷贝fd

epoll源码分析

epoll相关的内核代码在fs/eventpoll.c文件中,下面分别分析epoll_create、epoll_ctl和epoll_wait三个函数在内核中的实现。

# epoll_create用于创建一个epoll的句柄
# 其在内核的系统实现如下:

SYSCALL_DEFINE1(epoll_create, int, size)
{
    if (size <= 0)
        return -EINVAL;

    return sys_epoll_create1(0);
    //我们在调用epoll_create时,
    //传入的size参数,仅仅是用来判断是否小于等于0
    //之后再也没有其他用处。
}

感觉这个人写得更好: https://www.jianshu.com/p/aa486512e989


  • Epoll的2种工作方式 — 水平触发(LT)和边缘触发(ET)
    epoll 的默认模式是水平触发模式,但是也可以设置为边缘触发模式,在边缘触发模式的效率更高。

具体的区别在于:

如上图所示,0表示文件还没有准备好,1表示文件描述符已经准备好了。

如果在水平模式下面,有一个文件描述符从0变成1,那么表示的是这个描述符已经准备就绪了,可以对它进行io操作了。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。

边缘模式下面,如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。


源码

epoll使用的源码放在了Github上 :
https://github.com/GreenGitHuber/code_something/tree/master/epoll%2Breactor%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8

相关文章

网友评论

    本文标题:epoll的使用与源码分析

    本文链接:https://www.haomeiwen.com/subject/vigjlftx.html