细谈Select,Poll,Epoll

  • 阻塞 io 模型 blocking IO
  • 非阻塞 io 模型 nonblocking IO
  • io多路复用模型 IO multiplexing
  • 细谈 io 多路复用技术 select 和poll
  • 细谈事件驱动--epoll
  • 总结

操做系统在处理io的时候,主要有两个阶段:linux

  • 等待数据传到io设备
  • io设备将数据复制到user space

咱们通常将上述过程简化理解为:服务器

  • 等到数据传到kernel内核space
  • kernel内核区域将数据复制到user space(理解为进程或者线程的缓冲区)

而根据这两个阶段而不一样的操做方法,就会产生多种io模型,本文只讨论select,poll,epoll,因此只引出三种io模型。网络

阻塞 io 模型 blocking IO

最经常使用的也就是阻塞io模型。默认状况下,全部文件操做都是阻塞的。咱们以套接字接口为例来说解此模型,在进程空间调用recvfrom,其系统调用知道数据包到达而且被复制到进程缓冲中或者发生错误时才会返回,在此期间会一直阻塞,因此进程在调用recvfrom开始到它返回的整段时间都是阻塞的,所以称之为阻塞io模型。多线程

注意:在阻塞狀態下,程序是不會浪費CPU的,cpu只是不执行io操做了,还会去作别的。socket

image.png

应用层有数据过来,会调用recvfrom方法,可是这个时候应用层的数据还没复制到kernel中,将应用层数据复制到kerne这个阶段是须要时间的,因此recvfrom方法会阻塞,当内核中的数据准备好以后,recvfrom方法还不会返回,而是会发起一个系统调用将kernel中的数据复制到进程的缓冲区中,也就是user space,当这个工做完成以后,recvfrom才会返回并解除程序的阻塞。tcp

因此咱们总结能够发现,主要就是上面两个阶段函数

  • 应用层数据到kernel
  • kernel复制到user space

阻塞io模型就是将这个两个过程合并在一块儿,一块儿阻塞。 而非阻塞模型则是将第一个过程的阻塞变成非阻塞,第二个阶段是系统调用,是必须阻塞的,因此非阻塞模型也是同步的,由于它们在kernel里的数据准备好以后,进行系统调用,将数据拷贝到进程缓冲区中。性能

非阻塞 io 模型 nonblocking IO

就是对于第一个阶段,也就是应用层数据到kernel的过程当中,recvfrom会轮询检查,若是kernel数据没有准备还,就返回一个EWOULDBLOCK错误。不断的轮询检查,直到发现kernel中的数据准备好了,就返回,而后进行系统调用,将数据从kernel拷贝到进程缓冲区中。有點類似busy-waiting的方法。spa

image.png

io多路复用模型 IO multiplexing

  • 目的:由于阻塞模型在没有收到数据的时候就会阻塞卡住,若是一次须要接受多个socket fd的时候,就会致使必须处理完前面的fd,才能处理后面的fd,即便可能后面的fd比前面的fd还要先准备好,因此这样就会形成客户端的严重延迟。为了处理多个请求,咱们天然先想到用多线程来处理多个socket fd,可是这样又会启动大量的线程,形成资源的浪费,因此这个时候就出现了io多路复用技术。就是用一个进程来处理多个fd的请求。操作系统

  • 应用:适用于针对大量的io请求的状况,对于服务器必须在同时处理来自客户端的大量的io操做的时候,就很是适合

image.png

细谈 io 多路复用技术 select 和poll

select

select的工做流程: 单个进程就能够同时处理多个网络链接的io请求(同时阻塞多个io操做)。基本原理就是程序呼叫select,而后整个程序就阻塞了,这时候,kernel就会轮询检查全部select负责的fd,当找到一个client中的数据准备好了,select就会返回,这个时候程序就会系统调用,将数据从kernel复制到进程缓冲区。

image.png

下图为select同时从多个客户端接受数据的过程

虽然服务器进程会被select阻塞,可是select会利用内核不断轮询监听其余客户端的io操做是否完成。

image.png

Poll介绍

poll的原理与select很是类似,差异以下:

  • 描述fd集合的方式不一样,poll使用 pollfd 结构而不是select结构fd_set结构,因此poll是链式的,没有最大链接数的限制
  • poll有一个特色是水平触发,也就是通知程序fd就绪后,此次没有被处理,那么下次poll的时候会再次通知同个fd已经就绪。

select缺点

  • 根据fd_size的定义,它的大小为32个整数大小(32位机器为32*32,全部共有1024bits能够记录fd),每一个fd一个bit,因此最大只能同时处理1024个fd

  • 每次要判断【有哪些event发生】这件事的成本很高,由于select(polling也是)采起主动轮询机制

1.每一次呼叫 select( ) 都须要先从 user space把 FD_SET复制到 kernel(约线性时间成本) 为何 select 不能像epoll同样,只作一次复制就好呢? 每一次呼叫 select()前,FD_SET均可能更动,而 epoll 提供了共享记忆存储结构,因此不须要有 kernel 與 user之间的数据沟通

2.而后kernel还要轮询每一个fd,约线性时间

  • 假设现实中,有1百万个客户端同时与一个服务器保持着tcp链接,而每个时刻,一般只有几百上千个tcp链接是活跃的,这时候咱们仍然使用select/poll机制,kernel必须在搜寻完100万个fd以后,才能找到其中状态是active的,这样资源消耗大并且效率低下。

对于select和poll的上述缺点,就引进了一种新的技术,epoll技术

细谈事件驱动--epoll

epoll 提供了三个函数:

  • int epoll_create(int size); 创建一個 epoll 对象,并传回它的id

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 事件注册函数,将须要监听的事件和须要监听的fd交给epoll对象

  • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 等待注册的事件被触发或者timeout发生

epoll解决的问题:

  • epoll没有fd数量限制 epoll没有这个限制,咱们知道每一个epoll监听一个fd,因此最大数量与能打开的fd数量有关,一个g的内存的机器上,能打开10万个左右

  • epoll不须要每次都从user space 将fd set复制到内核kernel epoll在用epoll_ctl函数进行事件注册的时候,已经将fd复制到内核中,因此不须要每次都从新复制一次

  • select 和 poll 都是主動輪詢機制,须要拜訪每一個 FD; epoll是被动触发方式,给fd注册了相应事件的时候,咱们为每个fd指定了一个回调函数,当数据准备好以后,就会把就绪的fd加入一个就绪的队列中,epoll_wait的工做方式实际上就是在这个就绪队列中查看有没有就绪的fd,若是有,就唤醒就绪队列上的等待者,而后调用回调函数。

  • 虽然epoll。poll。epoll都须要查看是否有fd就绪,可是epoll之因此是被动触发,就在于它只要去查找就绪队列中有没有fd,就绪的fd是主动加到队列中,epoll不须要一个个轮询确认。 换一句话讲,就是select和poll只能通知有fd已经就绪了,但不能知道到底是哪一个fd就绪,因此select和poll就要去主动轮询一遍找到就绪的fd。而epoll则是不但能够知道有fd能够就绪,并且还具体能够知道就绪fd的编号,因此直接找到就能够,不用轮询。

总结

  • select, poll是为了解決同时大量IO的情況(尤为网络服务器),可是随着链接数越多,性能越差
  • epoll是select和poll的改进方案,在 linux 上能够取代 select 和 poll,能够处理大量链接的性能问题
相关文章
相关标签/搜索