您的位置 首页 golang

epoll的使用与源码分析

什么是epoll?

更多c/c++ Linux 服务器高阶知识请后台私信【架构】获取

知识点有C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。

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

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

  • epoll的相关系统调用
    epoll只有epoll_create , epoll_ctl , epoll_wait 3个系统调用。

程序的大体框架:

  • 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 好在哪里? 支持打开大数目的socket描述符(FD)不同 a. select 方法是通过开辟一个数组来存储和维护fd,这个数组的长度是2048,也就是说select支持的fd是有限的。 b. poll 是通过链表来维护fd的,所有数量上面没有限制。 IO效率不随FD数目增加而线性下降 传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是”活跃”的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。 但是epoll不存在这个问题,它只会对”活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数。 不需要在用户空间和内核空间之间频繁的拷贝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
    //之后再也没有其他用处。
}
  

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

具体的区别在于:

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

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

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

文章来源:智云一二三科技

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

文章地址:https://www.zhihuclub.com/100007.shtml

关于作者: 智云科技

热门文章

网站地图