【操作系统】IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析
【操作系统】IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
参考资料:
Java网络编程-IO模型篇
【Redis】网络模型:Redis的IO多路复用
【操作系统】全面解析IO 多路复用:select、poll、epoll
文章目录
- 【操作系统】IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
- 一、IO基本概念综述
- 1.1、IO的分类
- 1.2、IO工作原理
- 1.3、内核态与用户态
- 二、Linux的五种IO模型浅析
- 2.1、同步阻塞式IO-BIO
- 2.2、同步非阻塞式IO-NIO
- 2.3、多路复用模型
- 2.4、信号驱动模型
- 2.5、异步非阻塞式IO-AIO
- 2.6、五种IO模型小结
- 三、I/O 多路复用:select、poll、epoll
一、IO基本概念综述
对于IO
知识,想要真正的去理解它,需要结合多线程、网络、操作系统等多方面的知识,IO
最开始的定义就是指计算机的输入流和输出流,在这里主体为计算机本身,当然主体也可以是一个程序。
PS:从外部设备(如
U
盘、光盘等)中读取数据,这可以被称为输入,而在网络中读取一段数据,这也可以被称为输入。
最初的IO
流也只有阻塞式的输入输出,但由于时代的不断进步,技术的不停迭代,慢慢的IO
也会被分为很多种,接下来咱们聊聊IO
的分类。
1.1、IO的分类
IO
以不同的维度划分,可以被分为多种类型,比如可以从工作层面划分成磁盘IO
(本地IO
)和网络IO
:
- 磁盘
IO
:指计算机本地的输入输出,从本地读取一张图片、一段音频、一个视频载入内存,这都可以被称为是磁盘IO
。 - 网络
IO
:指计算机网络层的输入输出,比如请求/响应、下载/上传等,都能够被称为网络IO
。
也可以从工作模式上划分,例如常听的BIO、NIO、AIO
,还可以从工作性质上分为阻塞式IO
与非阻塞式IO
,亦或从多线程角度也可被分为同步IO
与异步IO
,这么看下来是不是感觉有些晕乎乎的?没关系,接下来我们对IO
体系依次全方位进行解析。
1.2、IO工作原理
无论是Java还是其他的语言,本质上IO
读写操作的原理是类似的,编程语言开发的程序,一般都是工作在用户态空间,但由于IO
读写对于计算机而言,属于高危操作,所以OS
不可能100%
将这些功能开放给用户态的程序使用,所以正常情况下的程序读写操作,本质上都是在调用OS
内核提供的函数:read()、 write()
。
也就是说,在程序中试图利用IO
机制读写数据时,仅仅只是调用了内核提供的接口函数而已,本质上真正的IO
操作还是由内核自己去完成的。
IO
工作的过程如下:
- ①首先在网络的网卡上或本地存储设备中准备数据,然后调用
read()
函数。 - ②调用
read()
函数后,由内核将网络/本地数据读取到内核缓冲区中。 - ③读取完成后向
CPU
发送一个中断信号,通知CPU
对数据进行后续处理。 - ④
CPU
将内核中的数据写入到对应的程序缓冲区或网络Socket
接收缓冲区中。 - ⑤数据全部写入到缓冲区后,应用程序开始对数据开始实际的处理。
在上述中提到了一个CPU
中断信号的概念,这其实属于一种I/O
的控制方式,IO
控制方式目前主要有三种:忙等待方式、中断驱动方式以及DMA
直接存储器方式,不过无论是何种方式,本质上的最终作用是相同的,都是读取数据的目的。
在上述IO
工作过程中,其实大体可分为两部分:准备阶段和复制阶段,准备阶段是指数据从网络网卡或本地存储器读取到内核的过程,而复制阶段则是将内核缓冲区中的数据拷贝至用户态的进程缓冲区。常听的BIO、NIO、AIO
之间的区别,就在于这两个过程中的操作是同步还是异步的,是阻塞还是非阻塞的。
1.3、内核态与用户态
用户态与内核态这两个词汇在前面多次提及到,那它两究竟是什么意思呢?先上图:
Linux
为了确保系统足够稳定与安全,因此在运行过程中会将内存划分为内核空间与用户空间,其中运行在用户空间的程序被称为“用户态”程序,同理,运行在“内核态”的程序则被称为“内核态”程序,而普通的程序一般都会运行在用户空间。
那么系统为什么要这样设计呢?因为如果内核与用户空间都为同一块儿,此时假设某个程序执行异常导致崩溃了,最终会导致整个系统也出现崩溃,而划分出两块区域的目的就在于:用户空间中的某个程序崩溃,那自会影响自身,而不会影响系统整体的运行。
同时为了防止普通程序去进行
IO
、内存动态调整、线程挂起等一些高危操作引发系统崩溃,因此这些高危操作的具体执行,也只能由内核自己来完成,但程序中有时难免需要用到这些功能,因此内核也会提供很多的函数/接口提供给外部调用。
当处于用户态的程序调用某个内核提供的函数时,此时由于用户态自身不具备这些函数的执行权限,因此会发生用户态到内核态的切换,也就是说:当程序调用某个内核提供的函数后,具体的操作会切换成内核自己去执行。
但用户态与内核态切换时,由于需要处理操作句柄、保存现场、执行系统调用、恢复现场等等过程,因此状态切换其实也是一个开销较大的动作,因此在设计程序时,要尽量减少会发生状态切换的事项,比如Java中,解决线程安全能用
ReetrantLock
的情况下则尽量不使用Synchronized
。
最后对于用户态和内核态的区别,用大白话来说就是:类似于做程序开发时,普通用户和管理员的区别,为了防止普通用户到处乱点,从而导致系统无法正常运转,因此有些权限只能开放给管理员身份执行,例如删库~
二、Linux的五种IO模型浅析
Linux
系统中共计提供了五种IO
模型,它们分别为BIO、NIO
、多路复用、信号驱动、AIO
,从性能上来说,它们属于依次递进的关系,但越靠后的IO
模型实现也越为复杂。
2.1、同步阻塞式IO-BIO
BIO(Blocking-IO)
即同步阻塞模型,这也是最初的IO
模型,也就是当调用内核的read()
函数后,内核在执行数据准备、复制阶段的IO
操作时,应用线程都是阻塞的,所以本次IO
操作则被称为同步阻塞式IO
,如下:
具体流程如下:
阶段一:
- 用户进程尝试读取数据(比如网卡数据)
- 此时数据尚未到达,内核需要等待数据
- 此时用户进程也处于阻塞状态
阶段二:
- 数据到达并拷贝到内核缓冲区,代表已就绪
- 将内核数据拷贝到用户缓冲区
- 拷贝过程中,用户进程依然阻塞等待
- 拷贝完成,用户进程解除阻塞,处理数据
当程序中需要进行IO
操作时,会先调用内核提供的read()
函数,但在之前分析过IO
的工作原理,IO
会经过“设备→内核缓冲区→程序缓冲区”这个过程,该过程必然是耗时的,在同步阻塞模型中,程序中的线程发起IO
调用后,会一直挂起等待,直至数据成功拷贝至程序缓冲区才会继续往下执行。
简单了解了
BIO
的含义后,那此刻思考一个问题:当本次IO
操作还在执行时,又出现多个IO
调用,比如多个网络数据到来,此刻该如何处理呢?
很简单,采用多线程实现,包括最初的IO
模型也的确是这样实现的,也就是当出现一个新的IO
调用时,服务器就会多一条线程去处理,因此会出现如下情况:
在BIO
这种模型中,为了支持并发请求,通常情况下会采用“请求:线程”1:1
的模型,那此时会带来很大的弊端:
- ①并发过高时会导致创建大量线程,而线程资源是有限的,超出后会导致系统崩溃。
- ②并发过高时,就算创建的线程数未达系统瓶颈,但由于线程数过多也会造成频繁的上下文切换。
在Tomcat
中,存在一个处理请求的线程池,该线程池声明了核心线程数以及最大线程数,当并发请求数超出配置的最大线程数时,会将客户端的请求加入请求队列中等待,防止并发过高造成创建大量线程,从而引发系统崩溃。
2.2、同步非阻塞式IO-NIO
NIO(Non-Blocking-IO)
同步非阻塞模型,从字面意思上来说就是:调用read()
函数的线程并不会阻塞,而是可以正常运行,如下:
当应用程序中发起IO
调用后,内核并不阻塞当前线程,而是立马返回一个“数据未就绪”的信息给应用程序,而应用程序这边则一直反复轮询去问内核:数据有没有准备好?直到最终数据准备好了之后,内核返回“数据已就绪”状态,紧接着再由进程去处理数据…
具体流程如下:
阶段一:
- 用户进程尝试读取数据(比如网卡数据)
- 此时数据尚未到达,内核需要等待数据
- 返回异常给用户进程
- 用户进程拿到error后,再次尝试读取
- 循环往复,直到数据就绪
阶段二:
- 将内核数据拷贝到用户缓冲区
- 拷贝过程中,用户进程依然阻塞等待
- 拷贝完成,用户进程解除阻塞,处理数据
- 可以看到,非阻塞IO模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致CPU空转,CPU使用率暴增。
其实相对来说,这个过程虽然没有阻塞发起
IO
调用的线程,但实际上也会让调用方不断去轮询发起“数据是否准备好”的信号,这也并非真正意义上的非阻塞。
通过如上的例子,应该能明显感受到这种所谓的NIO
相对来说较为鸡肋,因此目前大多数的NIO
技术并非采用这种多线程的模型,而是基于单线程的多路复用模型实现的,Java
中支持的NIO
模型亦是如此。
2.3、多路复用模型
在理解多路复用模型之前,我们先分析一下上述的NIO
模型到底存在什么问题呢?很简单,由于线程在不断的轮询查看数据是否准备就绪,造成CPU
开销较大。既然说是由于大量无效的轮询造成CPU
占用过高,那么等内核中的数据准备好了之后,再去询问数据是否就绪是不是就可以了?答案是Yes
。
那又该如何实现这个功能呢?此时大名鼎鼎的多路复用模型登场了,该模型是基于文件描述符File Descriptor
实现的,在Linux
中提供了select、poll、epoll
等一系列函数实现该模型,结构如下:
在多路复用模型中,内核仅有一条线程负责处理所有连接,所有网络请求/连接(Socket
)都会利用通道Channel
注册到选择器上,然后监听器负责监听所有的连接,过程如下:
阶段一:
- 用户进程调用select,指定要监听的FD集合
- 核监听FD对应的多个socket
- 任意一个或多个socket数据就绪则返回readable
- 此过程中用户进程阻塞
阶段二:
- 用户进程找到就绪的socket
- 依次调用recvfrom读取数据
- 内核将数据拷贝到用户空间
- 用户进程处理数据
当用户去读取数据的时候,不再去直接调用recvfrom了,而是调用select的函数,select函数会将需要监听的数据交给内核,由内核去检查这些数据是否就绪了,如果说这个数据就绪了,就会通知应用程序数据就绪,然后来读取数据,再从内核中把数据拷贝给用户态,完成数据处理,如果N多个FD一个都没处理完,此时就进行等待。
IO多路复用是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。不过监听FD的方式、通知的方式又有多种实现,常见的有:
- select
- poll
- epoll
详见参考:
【操作系统】全面解析IO 多路复用:select、poll、epoll
2.4、信号驱动模型
信号驱动IO
模型(Signal-Driven-IO
)是一种偏异步IO
的模型,在该模型中引入了信号驱动的概念,在用户进程中首先会创建一个SIGIO
信号处理程序,然后基于信号的模型进行处理。
具体流程如下:
阶段一:
- 用户进程调用sigaction,注册信号处理函数
- 内核返回成功,开始监听FD
- 用户进程不阻塞等待,可以执行其它业务
- 当内核数据就绪后,回调用户进程的SIGIO处理函数
阶段二:
- 收到SIGIO回调信号
- 调用recvfrom,读取
- 内核将数据拷贝到用户空间
- 用户进程处理数据
在该模型中,首先用户进程中会创建一个Sigio
信号处理程序,然后会系统调用sigaction
信号处理函数,紧接着内核会直接让用户进程中的线程返回,用户进程可在这期间干别的工作,当内核中的数据准备好之后,内核会生成一个Sigio
信号,通知对应的用户进程数据已准备就绪,然后由用户进程在触发一个recvfrom
的系统调用,从内核中将数据拷贝出来进行处理。
信号驱动模型相较于之前的模型而言,从一定意义上实现了异步,也就是数据的准备阶段是异步非阻塞执行的,但数据的复制阶段却依旧是同步阻塞执行的。
纵观上述的所有IO
模型:BIO、NIO
、多路复用、信号驱动,本质上从内核缓冲区拷贝数据到程序缓冲区的过程都是阻塞的,如果想要做到真正意义上的异步非阻塞IO
,那么就牵扯到了AIO
模型。
2.5、异步非阻塞式IO-AIO
AIO(Asynchronous-Non-Blocking-IO)
异步非阻塞模型,该模型是真正意义上的异步非阻塞式IO
,代表数据准备与复制阶段都是异步非阻塞的。
他会由内核将所有数据处理完成后,由内核将数据写入到用户态中,然后才算完成,所以性能极高,不会有任何阻塞,全部都由内核完成,可以看到,异步IO模型中,用户进程在两个阶段都是非阻塞状态。
在AIO
模型中,同样会基于信号驱动实现,在最开始会先调用aio_read、sigaction
函数,然后用户进程中会创建出一个信号处理程序,同时用户进程可立马返回执行其他操作,在数据写入到内核、且从内核拷贝到用户缓冲区后,内核会通知对应的用户进程对数据进行处理。
在
AIO
模型中,真正意义上的实现了异步非阻塞,从始至终用户进程只需要发起一次系统调用,后续的所有IO
操作由内核完成,最后在数据拷贝至程序缓冲区后,通知用户进程处理即可。
2.6、五种IO模型小结
最后用一幅图,来说明他们之间的区别
三、I/O 多路复用:select、poll、epoll
详见参考:
【操作系统】全面解析IO 多路复用:select、poll、epoll
【操作系统】IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析相关推荐
- IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)2017版
有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题.有时候我回微信的时候都是半夜了.但是我很乐意解答他们的问题.因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的 ...
- java io流区别_Java中IO流的分类和BIO,NIO,AIO的区别
到底什么是IO 我们常说的IO,指的是文件的输入和输出,但是在操作系统层面是如何定义IO的呢?到底什么样的过程可以叫做是一次IO呢? 拿一次磁盘文件读取为例,我们要读取的文件是存储在磁盘上的,我们的目 ...
- Java中IO流的分类和BIO,NIO,AIO的区别
到底什么是IO 我们常说的IO,指的是文件的输入和输出,但是在操作系统层面是如何定义IO的呢?到底什么样的过程可以叫做是一次IO呢? 拿一次磁盘文件读取为例,我们要读取的文件是存储在磁盘上的,我们的目 ...
- IO: BIO ? NIO ? AIO?
IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSock ...
- Java IO(BIO, NIO, AIO) 总结
文章转载自:JavaGuide 目录 BIO,NIO,AIO 总结 同步与异步 阻塞和非阻塞 1. BIO (Blocking I/O) 1.1 传统 BIO 1.2 伪异步 IO 1.3 代码示例 ...
- IO之 java中BIO NIO AIO原理、区别以及应用
在本篇文章中,我们主要介绍一下java中的BIO NIO AIO,重点是NIO 先说一下同步.异步.阻塞和非阻塞. 简单来讲,同步和异步是针对内核和应用程序之间的交互而言的:阻塞和非阻塞其实是针对进程 ...
- Java之IO,BIO,NIO,AIO知多少?
开心一笑 [一女人:"我真不放心丈夫,他准备到湖中心水最深的地方把猫扔掉."邻居:"那有什么不放心的?"女人:"猫已回家一钟头了!"] 提出 ...
- Java的IO:BIO | NIO | AIO
原文: http://my.oschina.net/bluesky0leon/blog/132361 BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...
- IO模式-BIO,NIO,AIO
目前常用的3种IO模式:分别是BIO.NIO和AIO. BIO BIO 全称Block-IO 是一种同步且阻塞的通信模式.是一个比较传统的通信方式,模式简单,使用方便.但并发处理能力低,通信耗时,依赖 ...
最新文章
- C#中的空值的判断(转)
- ZPhotoEngine超级算法库
- 【设计模式】 桥梁模式
- 查看docker容器日志
- html5网页制作图文混排,03第3章制作图文混排网页.docx
- C语言课后习题(12)
- 5岁自学python编程-25 岁才开始学习python晚吗?40 岁都不晚!
- 《学习》13权限管理
- 华为大数据解决方案(PPT)
- linux中sendto函数路径,Linux下send、sendto、sendmsg函数分析
- 写一些不属于自己的文字
- CentOS7配简单的桌面环境openbox
- 【转】奇文共欣赏,疑义相与析:原文转载《电脑维护技巧》(N条举措N条理由)并请大家交流研讨...
- 如何构建Birt报表应用程序?
- Codeforces869E The Untended Antiquity
- 数字水印进阶篇——基于DWT-SVD的数字水印(附matlab代码)
- 【C++】 STL库的stack容器
- 【不忘初心】 Windows11_22H2_22621.105_X64_可更新[纯净精简版][2.28G](2022.6.16)
- 值得你收藏的几个实用识别图片文字软件都在这
- Windows Store apps开发[8]处理Fullscreen, Snapped和Filled状态