• select, poll, epoll


    上上上上篇博文中,转载了关于Linux IO的一些基本知识。但这只是原文的一半。在原文的另一半中,介绍了select、poll、epoll的一些知识。当然,最好是去看看APUE;不过看看速成的快餐博客了解个大概也不错。下面就是转载那原文的另一半。

    原文:https://segmentfault.com/a/1190000003063859

    略有删节。

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

    但select,poll,epoll本质上都是同步I/O,因为它们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
    (博主注:真正的异步IO在Linux下几乎没有,似乎只有Windows下的IOCP是异步IO. )

    select

    [cpp] view plain copy
     
    1. int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  

    select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,通过遍历fdset,来找到就绪的描述符。
    select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一 个缺点是:单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

    poll

    [cpp] view plain copy
     
    1. int poll (struct pollfd *fds, unsigned int nfds, int timeout);  


    不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。

    [cpp] view plain copy
     
    1. struct pollfd {  
    2.     int fd; /* file descriptor */  
    3.     short events; /* requested events to watch */  
    4.     short revents; /* returned events witnessed */  
    5. };  

    pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 
    和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
    从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

    epoll

    epoll是在2.6内核中提出的,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

    一 epoll操作过程
    epoll操作过程需要三个接口,分别如下:

    [cpp] view plain copy
     
    1. int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大  
    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);  


    1. 

    [cpp] view plain copy
     
    1. int epoll_create(int size);  

    创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。
    当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/<process id>/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

    2. 

    [cpp] view plain copy
     
    1. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  

    函数是对指定描述符fd执行op操作: 添加、删除和修改对fd的监听事件。

    3. 

    [cpp] view plain copy
     
    1. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);  

    等待epfd上的io事件,最多返回maxevents个事件。
    参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

    二 工作模式
    epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
    LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
    ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

    1. LT模式
    LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket. 在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。

    2. ET模式
    ET(edge-triggered)是高速工作方式,只支持no-block socket. 在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)
    ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

    博主注: Linux中的EAGAIN含义
    Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。
    从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。
    例如,以 O_NONBLOCK 的标志打开文件/socket/FIFO,如果连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。
    又例如,当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败,返回EAGAIN提示其再调用一次(也许下次就能成功)。



    四 epoll的优点主要是一下几个方面:
    1. 监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大。select的最大缺点就是进程打开的fd是有数量限制的。这对 于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。

    2. IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。
    如果没有大量的idle connection或者dead connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle connection,就会发现epoll的效率大大高于select/poll。

    博主注: 还有很多资料也比较有价值,就不一一转载了。列举如下:

    1. http://blog.sae.sina.com.cn/archives/5042
    2. http://www.zhihu.com/question/20114168
    3. http://blog.sina.com.cn/s/blog_8fa7dd41010153zx.html
    4. http://ju.outofmemory.cn/entry/88391
    5. http://blog.csdn.net/ysu108/article/details/7570571
    6. http://blog.chinaunix.net/uid-22663647-id-1771846.html
    7. http://www.artima.com/articles/io_design_patterns.html

    http://blog.csdn.net/nirendao/article/details/51167262

  • 相关阅读:
    java学习——内部类、匿名内部类
    Java中接口之间的继承
    Settings
    POM
    Maven指令
    Maven生命周期
    内部类
    Modules
    Simple Maven Project
    Maven概述
  • 原文地址:https://www.cnblogs.com/findumars/p/7296535.html
Copyright © 2020-2023  润新知