Linux IO模型知识梳理

(本文大部分内容非原创,是自己整理复习的知识点。在最下面都会给上所有知识点的来源参考或出处,需要深入了解可以通过链接跳转)

Linux IO模型初步介绍

之前整理了一篇关于Java IO模型的知识点,便于学习,复习。点击跳转

但是如果想要继续深入一点去了解IO的知识点的话,去了解Unix网络编程定义的五大IO模型是不可避免的。这五大IO模型分别是阻塞IO模型,非阻塞IO模型,IO复用模型,信号驱动IO模型,异步IO模型。下面分别对这五种IO模型进行介绍,并重点介绍高频面试知识点IO复用模型。

阻塞IO模型

进程在进行IO操作或系统调用时会挂起,会一直阻塞到内核缓冲区数据准备好并复制到用户缓冲区之后。我们把recvfrom函数视为系统调用,具体的流程就可以参考下图:(图片大部分来自书上,有些过于模糊,重新描了一下):

具体流程如下:

  1. 用户进程需要进行IO操作时,会进行一次系统调用,进入到内核态,此时用户进程被挂起。处于阻塞状态。此时进程不会再占用cpu资源。
  2. 内核进行数据的准备,把需要的数据填充到内核缓冲区。
  3. 内核缓冲区数据填充完毕,把数据从内核缓冲区复制到用户缓冲区。
  4. 数据复制完毕,返回,从内核态从新切换到用户态,进程进入就绪状态等待cpu执行。

总结:

  1. 用户进程从进行系统调用进入内核态后,会一直挂起,阻塞到数据从内核缓冲区复制到用户缓冲区完毕。期间不会消耗cpu资源。

  2. 适用并发量小的网络应用开发。

非阻塞IO模型

用户进程在进行IO操作或系统调用时不会挂起,只是来看一下数据报准备好了没,如果没好可以继续做自己的事情,而不是一直阻塞在那等待。

具体流程如下:

  1. 用户进程需要进行IO操作时,会进行一次系统调用,进入到内核态。如果数据没有准备好,立即返回EWOULDBLOCK。此时不会造成进程阻塞,还可以继续处理其他事情。
  2. 接下来进程会轮询查看内核数据是否准备好,如果没有准备好,就继续立即返回EWOULDBLOCK,不阻塞进程。
  3. 轮询到数据准备好了后,进行数据复制,从内核缓冲区复制到用户缓冲区。在此期间进程会挂起,处于阻塞状态,直到数据复制完成。
  4. 数据复制完毕后返回,进程转为就绪态,等待cpu调度。

总结:

  • 非阻塞IO因为要用轮询代替了阻塞,使得进程在内核数据准备期间不会阻塞,可以执行其他事情,但是因为要轮询,所以会对CPU资源造成较大的消耗。

  • 在进行内核态往用户态数据复制过程中,进程还是会处于阻塞状态的。

  • 虽然非阻塞IO可以使得进程在IO内核数据准备期间不阻塞,可以执行其他事情,但是,由于轮询需要消耗较大的cpu资源,所以会使得服务端处理和响应请求会有较大的延时。

  • 适用并发量较小、且不需要及时响应的网络应用开发。(因为有可能你数据报准备好了,但是还没去轮询访问,不能第一时间访问到数据)

IO复用模型(select/poll)

IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

具体流程:

(1)用户首先将需要进行IO操作的socket添加到select中,然后阻塞地等待select系统调用返回

(2)当数据到达时,socket被激活,select函数返回。

(3)用户线程正式发起read请求,读取数据并继续执行。(可以参考下图)

这里与非阻塞IO的最大区别在于select可以同时处理多个socket的IO。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

与阻塞IO的区别:

  • 基于select/poll的IO复用与阻塞IO类似,只不过阻塞IO是一直阻塞在recvfrom系统调用等待数据准备好并复制到缓冲区,而IO复用会先阻塞在select或者poll系统调用,监视某一个连接数据准备好的再返回,然后调用recvfrom系统调用进行数据复制,只是此时的recvfrom不再需要等待数据准备,可以直接复制。时间较短,主要阻塞在select/poll系统调用上。
  • 基于select/poll的IO复用实际上比阻塞IO多了一个步骤,多进行了一次系统调用,只不过IO复用的优势不是在于处于一个连接更快,而是可以处理更多的连接。
  • 在处理连接数不高的情况下,基于select/poll的IO多路复用的服务器不一定会比多线程+阻塞IO的服务器性能高,可能延时会更大。

总结:

  • 基于select或者poll模型的IO多路复用基本是一样的只是基于select的模型默认能同时接收的连接数是1024个,因为一个进程默认最多打开1024个fd文件描述符。而基于poll模型的IO多路复用就没有限制,因为是基于链表来存储的。
  • 基于select/poll的IO多路复用也不能设置太大的连接数,因为监视复用器的连接数据是否准备好是采用循环进行无差别的监视,时间复杂度为O(n),如果连接数太大的话,循环监视一次的时间会相对长,反而会降低监视的效率。

IO复用模型(epoll)

在了解基于epoll的IO模型的时候,先回顾一下select/poll的IO多路复用缺点:

  • select的模型默认能同时接收的连接数是1024个,因为一个进程默认最多打开1024个fd文件描述符。而基于poll模型的IO多路复用就没有限制。
  • 对复路器上的连接的监视轮询是线性时间复杂度O(n),也就是说随着连接数的增加,对复路器的监视效率会降低。

而我们的epoll也就是解决了这两个问题,做出一些改进:

  • 一个进程打开的fd连接文件描述符没有限制。会限制于内存大小,1GB内存大概可以打开10w个。
  • 利用每个文件描述符fd上的callback函数来实现异步回调,省略了对复路器上的连接监视轮询的开销。时间复杂度O(1),就不会随着连接数的增多而降低。

具体流程

  1. 连接到服务进程上的多个套接字连接会注册到复路器上。
  2. 进程调用epoll系统调用。进入内核态,应用进程挂起。
  3. 一旦复用器上的某个连接数据准备好了,就会通过该连接套接字描述符fd上的回调函数通知应用进程并,然后进程会取消阻塞状态并再发起系统调用recvfrom把内核缓冲区的数据复制到用户缓冲区,此时进程又会被挂起,此时的系统调用recvfrom时内核缓冲区数据必定是准备好的。
  4. 数据复制完成返回。

epoll,poll,select的区别

epoll深层解析

推荐阅读1

推荐阅读2

信号驱动IO模型

调用流程

  1. 在Socket连接上安装一个信号处理函数,然后进程调用sigaction系统调用,但是立即返回,进程不用阻塞。继续执行。
  2. 当某个Socket的数据准备好了之后,进程会收到一个SIGIO信号,可以在信号处理函数中调用recvfrom进行数据的复制。复制过程中进程阻塞。
  3. 复制完成返回。

异步IO模型

调用流程

  1. 应用进程接收到IO连接,发起一次aio_read系统调用,然后立即返回,进程不阻塞,可以继续执行。
  2. 等待数据准备好。
  3. 数据准备好了以后,内核直接把数据从内核缓冲区复制到用户缓冲区,而无需用户进程发起系统调用再进行复制。
  4. 数据复制完以后返回指定信号给用户进程,此时数据已经在用户的缓冲区了,所以用户进程可以直接在用户缓冲区拿数据处理。

参夸资料

《Unix网络编程》

详解Unix5种IO模型

高性能IO模型分析——知乎《破执》

posted @ 2020-09-09 21:33  CryFace  阅读(240)  评论(0编辑  收藏  举报