Linux的IO模型 —— 多路复用(select、poll、epoll)

目录

        • 1、前言
        • 2、内核空间、用户空间、同步、异步、阻塞、非阻塞
        • 3、同步阻塞 IO
        • 4、同步非阻塞IO
        • 5、多路复用
          • 5.1 select
          • 5.2 poll
          • 5.3 epoll

1、前言

  应用进程和内核之间的数据交互方式一直在演进,下面我们对各种形态的交互方式进行介绍。在这之前,我们先明确几个概念:内核空间用户空间同步异步阻塞非阻塞

2、内核空间、用户空间、同步、异步、阻塞、非阻塞

2.1 内核空间
  操作系统单独拥有的内存空间为内核空间,这块内存空间独立于其他的应用内存空间,除了操作系统,其他应用程序不允许访问这块空间。但操作系统可以同时操作内核空间和用户空间。
2.2 用户空间
  单独给用户应用进程分配的内存空间,操作系统和应用程序都可以访问这块内存空间。
2.3 同步
  调用线程发出同步请求后,在没有得到结果前,该调用就不会返回。所有同步调用都必须是串行的,前面的同步调用处理完了后才能处理下一个同步调用。
2.4 异步
  调用线程发出异步请求后,在没有得到结果前,该调用就返回了。真正的结果数据会在业务处理完成后通过发送信号或者回调的形式通知调用者。
2.5 阻塞
  调用线程发出请求后,在没有得到结果前,该线程就会被挂起,此时CPU也不会给此线程分配时间,此线程处于非可执行状态。直到返回结果返回后,此线程才会被唤醒,继续运行。划重点:线程进入阻塞状态不占用CPU资源。
2.6 非阻塞
  调用线程发出请求后,在没有得到结果前,该调用就返回了,整个过程调用线程不会被挂起。

3、同步阻塞 IO

  同步阻塞IO模式是Linux中最常用的IO模型,所有Socket通信默认使用同步阻塞IO模型。同步阻塞IO模型中,应用线程在调用了内核的IO接口后,会一直被阻塞,直到内核将数据准备好,并且复制到应用线程的用户空间内存中。
Linux的IO模型 —— 多路复用(select、poll、epoll)_第1张图片
优点:
并发量较少的网络通信场景较高效
应用程序开发简单

缺点:
不适合并发量较大的网络通信场景

4、同步非阻塞IO

  同步非阻塞IO是同步阻塞IO的一种变种IO模式,它和同步阻塞区别在于,应用线程在向内核发送IO请求后,内核的IO数据在没有准备好的时候会立刻给应用线程返回一个错误代码(EAGAIN 或 EWOULDBLOCK),在内核的IO数据准备好了之后,应用线程再发起IO操作请求时候,内核会在将IO数据从内核空间复制到用户空间后给应用线程返回正常应答。常见的Non-Blocking模式的Socket网络通信就是同步非阻塞模式。
Linux的IO模型 —— 多路复用(select、poll、epoll)_第2张图片
优点:
在内核IO数据准备阶段不会阻塞应用线程,适合对线程阻塞敏感的网络应用

缺点:
轮询查询内核IO数据状态,耗费大量CPU,效率低
需要不断轮序,增加开发难度

5、多路复用

Linux的IO模型 —— 多路复用(select、poll、epoll)_第3张图片
  多路复用是目前大型互联网应用中最常见的一种IO模型,简单说就是应用进程中有一个IO状态管理器,多个网络IO注册到这个管理器上,管理器使用一个线程调用内核API来监听所有注册的网络IO的状态变化情况,一旦某个连接的网络IO状态发生变化,能够通知应用程序进行相应的读写操作。多路网络IO复用这个状态管理器,所以叫多路复用模式。多路复用本质上是同步阻塞,但与传统的同步阻塞多线程模型相比,IO 多路复用的最大优势是在处理IO高并发场景时只使用一个线程就完成了大量的网络IO状态的管理工作,系统资源开销小。
多路复用的基本工作流程:
1、应用程序将网络IO注册到状态管理器;
2、状态管理器通过调用内核API来确认所管理的网络IO的状态;
3、状态管理器探知到网络IO的状态发生变化后,通知应用程序进行实质的同步阻塞读写操作。
  目前Linux主要有三种状态管理器:select,poll,epoll。epoll是Linux目前大规模网络并发程序开发的首选模型,在绝大多数情况下性能远超select和poll。目前流行的高性能Web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。

5.1 select

  select是最古老的多路复用模型,Linux在2.6版本之前仅提供select模式,一度是主流的网络IO模式。select采取定期轮询的方式将自己管理的所有网络IO对应的文件句柄发送给内核,进行状态查询,下面是内核系统对应用程序提供的API:

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

  fd_set是一个Long的数组的数据结构,用于存放的是文件句柄(file descriptor)。这个API有三个关键参数,即readset/writeset/exceptset,前两个参数是注册到select,所有需要监听的网络IO文件句柄数组,第三个参数是一个空数组,由内核轮询所有网络IO文件句柄后,将状态有变化的文件句柄值写入到exceptset数组中,也就是说readset/writeset是输入数据,exceptset是输出数据。最后,内核将变化的句柄数数量返回给调用者。
Linux的IO模型 —— 多路复用(select、poll、epoll)_第4张图片
从接口的细节,我们可以看到归纳下select的工作流程:
1、应用线程将需要监视的网络IO文件句柄注册到select状态监视器;
2、select状态监视器工作线程定期调用内核API,将自己所有管理的文件句柄通过(readset/writeset)两个参数传给内核;
3、内核轮询所有传进来的文件句柄的网络IO状态,将有变化的文件句柄值写入exceptset数组中,并且将变化的句柄数数量返回给调用者;
4、select工作线程通知应用程序进行实质的同步阻塞读写操作。
select机制的特性分析:
1、每次调用select,都需要把readset/writeset集合从用户空间态拷贝到内核空间,如果readset/writeset集合很大时,那这个开销很大;
2、每次调用select都需要在内核遍历传递进来的所有文件句柄,每次调用都进行线性遍历,时间复杂度为O(n),文件句柄集合很大时,那这个开销也很大;
3、内核对被监控的文件句柄集合大小做了限制,X86为1024,X64为2048。

5.2 poll

  poll模型和select模型非常类似,状态监视器同样管理一批网络IO状态,内核同样对传输过来的所有网络IO文件句柄进行线性轮询来确认状态,唯一区别是应用线程传输给内核的文件句柄数组不限制大小,解决了select中说道的第三个问题,其他两个问题依然存在。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

typedef struct pollfd {
        int fd;                         // 需要被检测或选择的文件描述符
        short events;                   // 对文件描述符fd上感兴趣的事件
        short revents;                  // 文件描述符fd上当前实际发生的事件
} pollfd_t;
5.3 epoll

  epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll文件句柄没有个数限制,将应用程序关心的网络IO文件句柄的事件存放到内核的一个事件表中,在用户空间和内核空间的copy只需一次。epoll内核和网络设备建立了订阅回调机制,一旦注册到内核事件表中的网络连接状态发生了变化,内核会收到网络设备的通知,订阅回调机制替换了select/poll的轮询查询机制,将**时间复杂度从原来的O(n)降低为O(1),**大幅提升IO效率,特别是在大量并发连接中只有少量活跃的场景。
Linux提供的三个epoll的API:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

  epoll在内核内存里建了一个红黑树用于存储epoll_ctl传来的连接,epoll内核还会建立一个rdllist双向链表,用于存储网络状态发生变化的文件句柄,当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可,有数据就返回,没有数据就让epoll_wait睡眠,等到timeout时间到后即使链表没数据也返回。因为epoll不像select/poll那样采取轮询每个连接来确认状态的方法,而是监听一个双向链表,在连接数很多的情况下,epoll_wait非常高效。
Linux的IO模型 —— 多路复用(select、poll、epoll)_第5张图片
  所有添加到epoll中的网络连接都会与设备(如网卡)驱动程序建立回调关系,也就是说相应连接状态的发生时网络设备会调用回调方法通知内核,这个回调方法在内核中叫做ep_poll_callback,它会把网络状态发生变化的变更事件放到上面的rdllist双向链表中。
  epoll的epoll_wait调用,有EPOLLLT和EPOLLET两种触发返回模式,LT是默认的模式,更加安全,ET是“高速”模式,更加高效:
水平触发(LT):默认工作模式,即当epoll_wait检测到某网络连接状态发生变化并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件;
边缘触发(ET): 当epoll_wait检测到某网络连接状态发生变化并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。
epoll机制的
特性分析:

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听超过10万个连接);
2、通过epoll_ctl方法将网络连接注册到内核,不用每次查询连接状态时将所有网络文件句柄传输传输给内核,大幅提高效率;
3、内核和网络设备建立事件订阅机制,监听连接网络状态不使用轮询的方式,不会随着文件句柄数目的增加效率下降,只有活跃可用的文件句柄才会触发回调函数;Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关;
4、利用MMAP内存映射技术加速用户空间与内核空间的消息传递,减少复制开销。

你可能感兴趣的:(Linux,linux,后端)