• 转--select/poll/epoll到底是什么一回事


    面试题:说说select/poll/epoll的区别。 
    这是面试后台开发时的高频面试题,属于网络编程和IO那一块的知识。Android里面的Handler消息处理机制的底层实现就用到了epoll。 
    为此,我在Google上看了很多相关文章,才大概搞懂是怎么一回事。
    
    背景知识
    文件描述符fd
    文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
    
    文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。在Linux系统中,流在内核中可以表示成文件的形式。
    
    IO模型
    IO可以理解成对流的操作。
    
    一般对于一个read操作发生时,它会经历两个阶段。
    
    第一个阶段是等待数据准备。
    第二个阶段是真正读取的过程,将数据从内核缓冲区拷贝到用户进程缓冲区中,
    而五种常见的IO模型也是围绕这两个阶段来区分的。
    
    同步模型(synchronous IO) 
    阻塞IO(bloking IO)
    非阻塞IO(non-blocking IO)
    多路复用IO(multiplexing IO)
    信号驱动式IO(signal-driven IO)
    异步IO(asynchronous IO)
    其中,IO多路复用就是一种机制,实现一个进程可以监视多个描述符,一旦某个描述符就绪,就能够通知程序进行相应的读写操作。IO多路复用相比于多线程的优势在于系统的开销小,系统不必创建和维护进程或线程,免去了线程或进程的切换带来的开销。而操作系统支持IO多路复用的系统调用有select,poll和epoll。
    
    select
    先来看看select的函数声明:
    
    int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    1
    fd_set是表示文件描述符集合的数据结构。readfds,writefds和exceptfds分别对应三类文件描述符集。当select被调用时,内部逻辑如下:
    
    将3个fd集copy到内核,这里限制了fd最大数量为1024
    线程阻塞,直到超时或内核检测到有fd可读或可写,内核会通知监控者select,select返回可读或可写的fd总数
    那么用户进程如何找到可读可写的fd呢?select会将之前传递给内核的fd集从内核copy到用户进程。用户进程通过遍历的方式找到可读可写的fd。
    缺点:
    
    copy次数过多,而且每次调用select方法都要进行fd集的copy操作
    select监控fd数量有限
    用户进程通过遍历的方式找到可读写的fd,时间复杂度为o(n),IO效率随着fd数量增多而线性下降
    poll
    先来看看poll的函数声明:
    
    int poll (struct pollfd *fds, unsigned int nfds, int timeout);
    1
    pollfd是表示文件描述符集合的数据结构。
    
    struct pollfd {
        int fd; //文件描述符
        short events; //监视的请求事件
        short revents; //已发生的事件
    };
    1
    2
    3
    4
    5
    poll与select差不多,但poll的pollfd没有最大数量的限制,可是IO效率依旧没有提升orz。
    
    epoll
    select/poll都只有一个方法,而epoll的操作过程有3个方法,分别是epoll_create(), epoll_ctl(),epoll_wait()。
    
    epoll_create()
    int epoll_create(int size);//用于创建一个epoll的句柄,size是指监听的描述符个数。
    1
    该方法会在内核创建专属于epoll的高速cache区,并在该缓冲区建立红黑树和就绪链表,用户态传入的文件句柄将被放到红黑树中。
    
    epoll_ctl()
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    1
    该方法对epoll_create()所创建的内核cache区进行操作的,操作对象是需要监听的fd。
    
    比如,把要监听的fd注册到cache内,那么epoll_ctl()会将fd插入到红黑树中,并向内核注册了该fd的回调函数。内核在检测到某fd可读可写时则调用该回调函数,而回调函数的工作是将fd放到就绪链表。
    
    epoll_wait()
    int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);  
    1
    epoll_wait只需监控就绪链表,如果就绪链表有fd,则表示该fd可读可写,并返回给用户态(少量的copy);
    
    该函数返回需要处理的事件数目,如返回0表示已超时。
    
    小结
    执行epoll_create时,在创建了红黑树和就绪链表。执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树上,然后向内核注册回调函数,用于当中断事件到来时向准备就绪链表中插入数据。执行epoll_wait时返回就绪链表里的数据即可。
    
    因此,epoll比select和poll高效的原因是:
    
    减少了用户态和内核态之间文件句柄的copy
    降低了在文件句柄集中查找的时间复杂度。用红黑树维护fd集,可以将查找fd的时间复杂度降为o(logn)。
    参考
    https://www.zhihu.com/question/20122137
    http://www.jianshu.com/p/dfd940e7fca2#
    http://gityuan.com/2015/12/06/linux_epoll/
    
    
    如果有来生,一个人去远行,看不同的风景,感受生命的活力。。。
  • 相关阅读:
    FastReport合并多份报表为一份预览打印
    Delphi使用AcroPDF ActiveX显示PDF文件
    Delphi使用Zxing创建二维码
    TreeView和ListView数据库查询数据联动操作
    根据数据库查询结果动态创建控件(仿看板模式显示)
    GridView控件使用
    LayoutControl控件使用
    TreeListLookUpEdit控件使用
    Devexpress TreeList控件使用
    Asp.Net 5上传文件 (Core API方式)
  • 原文地址:https://www.cnblogs.com/Frank99/p/9790657.html
Copyright © 2020-2023  润新知