您的位置:首页 > 运维架构 > Linux

Linux epoll机制初识

2016-10-12 14:41 190 查看
文章仅仅用于个人的学习记录,基本上内容都是网上各个大神的杰作,此处摘录过来以自己的理解学习方式记录一下。

参考文献:

     http://www.zhihu.com/question/32163005

     http://www.zhihu.com/question/28594409

     http://blog.csdn.net/zhang_shuai_2011/article/details/7675797

     http://blog.csdn.net/xiajun07061225/article/details/9250579

     
1、前言。
      a、IO相关认识。
         I/O操作比如最常见的read和write,通常来说I/O操作是阻塞I/O的,也就是说当你调用read时,如果没有收到数据,那么线程或者进程就会被挂起直到收到数据(再往下执行).这样当服务器处理很多链接的时候,对内存的消耗(线程太多)太大和CPU的时间占用(线程之间切换)很多。后来就引入了非阻塞I/O概念。Linux通过调用系统的函数ioctl把其设置为非阻塞模式即可。这时当你调用read时,如果有数据收到就返回,如果没有数据收到,就立刻返回一个错误。这样就不会阻塞线程了,但是还是需要不断的轮训来读取或者写入。此时就引来了I/O多路复用概念。调用如:select、poll等函数,传入多个文件描述符,如果有描述符就绪则返回(去做自己的处理),否则阻塞直到超时。这样在处理很多连接的时候,只需要一个线程监控就绪状态,然后对相应状态的每个连接再单独开线程处理即可。这样大大减少内存开销。
      b、IO多路复用概念。
         传统的并发处理模型:没有一个新的I/O流我就新建一个进程管理去处理它,随时跟踪它的状态。这样就会导致,资源浪费、管理混乱等等问题。
      epoll机制是一种I/O多路复用技术。I/O多路复用。其实这个复用是指的复用某个线程的意思。
      整体的意思:单个线程通过记录跟踪每个I/O流的状态,来管理多个I/O流.一般来说这些机制都是监控的多个描述符fd的状态(如时候准备读、写等)。

   当它处于某种状态时候去顺序的做相应的处理。(配合Socket的时候需要是非阻塞的,只有当系统通知谁有有效数据才取处理)
      好处:提高服务器的吞吐能力,一个进程就可以管理多个任务要求等等。
      select、poll、epoll都是这种模型。(按照这个顺序依次出现改进的)
      个人认为网上比较形象的例子:
        一个酒吧服务员(一个线程),前面趴了一群醉汉,突然一个吼一声“倒酒”(事件),你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务员处于空闲状态,可以干点别的玩玩手机。至于epoll与select,poll的区别在于后两者的场景中醉汉不说话,你要挨个问要不要酒,没时间玩手机了。io多路复用大概就是指这几个醉汉共用一个服务员。这样来看epoll除了可以复用服务员,还能有效避免很多无用的操作。

2、select、poll、epoll优缺点。
     epoll可以说是select和poll的增强版。暂未查找学习select、poll的具体用法和实现,此处先做个概念上的理解记录。
     select的缺点:
        a、每次调用select都需要把用户空间的fd集合拷贝到内核空间。这样当fd很多事内存开销较大。
        b、每次调用select时当有任何一个I/O流出现了数据,都需要在内核中遍历传进来所有的fd。也会导致开销很大。
        c、select支持的文件描述符只有默认的1024.
        d、select不是线程安全的,无法回收已经加入但后来不需要的socket.
     由于当时硬件比较弱,select已经很长一段时间能满足需求了。后来就该进程了poll。
     poll优缺点:
        a、poll改进了select的最多1024个连接的限制。可以自己设定。
        b、改进来fd描述的方式。
        c、但是仍然线程不安全,且有时间发生时需要循环全部的fd。
     然后就出来了epoll,修复了绝大部分上面的两种实现的问题。最主要的如:
        a、epoll现在不仅告诉我们监控的I/O流里面有数据,还会告诉我们具体实那个I/O流,不需要重复的循环去找了。
           大概是每个fd注册一个回调,fd就绪时调用这个回调函数,把fd假如导游一个就绪列表中。epoll_wait其实是在查询这个链表。
        b、解决了查询时重复赋值fd到内核这个问题,每次注册新事件到epoll句柄时,会把所有的fd拷贝到内核,这样保证只拷贝一次。大概是采用了mmap方式。
        c、epoll现在是线程安全的。
     但是epoll好像只有linux支持。
     总的来说:
         select、poll实现需要不断地轮训所有的fd,直到设备就绪。期间可能要睡眠唤醒多次。而epoll也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒,但是它是当设备就绪时,调用回调函数把就绪的fd放入就绪列表中,并唤醒在epoll_wait中进入睡眠的进程。相比如另外两种这个只需要遍历就绪列表。 select、poll每次都要调用把fd集合从用户态往内核态拷贝一次,并且把current往设备等待队列挂一次。而epoll只需要拷贝一次、挂载一次。

3、epoll的基本使用。
    3.1、int epoll_create(int size)
         创建一个epoll的句柄,自从Linux2.6.8以后size是忽略的。 当创建好epoll句柄之后,它就会占用一个fd值。所以在使用完epoll之后,调用close关闭,否则可能导致fd不够用。          
     
    3.2、int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
         epoll的事件注册函数,要先注册需要监听的事件。
         epfd: epoll_create()的返回值。
         op:表示动作,有三个宏
             EPOLL_CTL_ADD :注册新的fd到epfd当中。
             EPOLL_CTL_MOD :修改已经注册的fd的监听事件。
             EPOLL_CTL_DEL :从epfd中删除一个fd。
         fd :需要监听的I/O流的fd。

         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 events */
epoll_data_t data; /* User data variable */
};


                对于epoll_event结构体的 events变量有以下几个宏
                   EPOLLIN :表示对应的文件描述符可读。
                   EPOLLOUT :表示对应的文件描述符可写。
                   EPOLLPRI
:表示对应的文件描述符有紧急数据可读。
                   EPOLLERR :表示对应的文件描述符发生错误。
                   EPOLLHUP
:表示对应的文件描述符被挂断。
                   EPOLLLET
:将EPOLL设为边缘触发模式Edge Triggered,这是相对于水平触发Level Triggered来说的。
                   EPOLLONESHOT :只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个fd的话,需要再次把它
                              加入到EPOLL队列里面。

     3.3、int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)
          收集在epoll监控的时间中已经发生的事件。
          epfd:创建的epoll的fd。
          events: 分配好的epoll_event结构体数组,epoll将会把发生的时间赋值到events数组中。
          maxevents: 告诉内核这个events有多大,这个大小不能超过epoll_create()时传入的size。
          timeout: 超时设置单位ms.可设置如下三种
               0: 会立即返回。
               -1:将会阻塞。
               timeout : 阻塞timeout个时长,如果在这期间发生则返回,如果超过时间则返回。
          如果函数调用成功就返回对应I/O上已经准备好的文件描述符的数目。返回0表示已超时。
     3.4、epoll的两种工作方式。
           LT level triggered 水平触发模式,
              同时支持阻塞和非阻塞的socket。在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪            的fd进行I/O操作,如果你不做任何操作,讷河还是会继续通知你。(没处理这个流还是一直通知你)
           ET edge triggered 边缘触发模式
              只支持非阻塞的socket。效率比LT高。这种工作模式下,当从epoll_wait调用获取到事件后,如果没有把这次事件            对应的套接字处理完,那么在这个套接字中没有心的时间再次到来时,ET模式下是无法再次从epoll_wait调用中获取这
           个事件的。而LT只要有数据就总可以获取。
           文章开头处连接里面的大神的图,个人觉得很清晰明了:



         

      
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息