深入底层,Linux五种IO模型全解析
Posted 毛奇志
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入底层,Linux五种IO模型全解析相关的知识,希望对你有一定的参考价值。
文章目录
一、前言
二、Java IO与操作系统IO
2.1 Java层面的三种IO 与 操作系统层面的五种IO
在Java中,主要有三种IO模型,分别是阻塞IO(BIO)、非阻塞IO(NIO)和 异步IO(AIO)。
Java中提供的IO有关的API,在文件处理的时候,本质上是依赖操作系统层面的IO操作实现的。在Linux 2.6以后,Java中NIO和AIO都是通过epoll来实现的;在Windows上,AIO是通过IOCP来实现的。
可以把Java中的BIO、NIO和AIO理解为是Java语言对操作系统的各种IO模型的封装。程序员在使用这些API的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
在Linux(UNIX)操作系统中,共有五种IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型。
金手指:
(1) 在Java中,主要有三种IO模型,分别是阻塞IO(BIO)、非阻塞IO(NIO)和 异步IO(AIO)。
(2) 在Linux(UNIX)操作系统中,共有五种IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型。
(3) Java中提供的IO有关的API,在文件处理的时候,其实依赖操作系统层面的IO操作实现的。
2.2 到底什么是IO
我们常说的IO,指的是文件的输入和输出,但是在操作系统层面是如何定义IO的呢?到底什么样的过程可以叫做是一次IO呢?
拿一次磁盘文件读取为例,我们要读取的文件是存储在磁盘上的,我们的目的是把它读取到内存中。可以把这个步骤简化成把数据从硬件(硬盘)中读取到用户空间中。
其实真正的文件读取还涉及到缓存等细节,这里就不展开讲述了。关于用户空间、内核空间以及硬件等的关系如果读者不理解的话,可以通过钓鱼的例子理解。
钓鱼的时候,刚开始鱼是在鱼塘里面的,我们的钓鱼动作的最终结束标志是鱼从鱼塘(磁盘)中被我们钓上来,放入鱼篓中。
钓鱼(IO操作):将鱼(文件)从鱼塘(磁盘)中通过鱼钩(内核空间),将鱼(文件)放到(拷贝)鱼篓(用户空间)里面。
这里面的鱼塘就可以映射成磁盘,中间过渡的鱼钩可以映射成内核空间,最终放鱼的鱼篓可以映射成用户空间。一次完整的钓鱼(IO)操作,是鱼(文件)从鱼塘(硬盘)中转移(拷贝)到鱼篓(用户空间)的过程。
问题:到底什么是IO?
回答:从Java语言的层面来说,读写磁盘文件和发送接收socket网络请求,就涉及到IO,一定要使用IO的方式才能完成读写磁盘文件和发送接收socket网络请求,否则无法传输Java对象。从操作系统的层面来说,IO就是:将文件从磁盘中通过内核空间,将文件拷贝到用户空间里面。涉及三个位置:磁盘、内核空间、用户空间,其操作对象是文件,操作名称是IO操作,操作方式是拷贝。
三、五种IO
3.1 第一种:阻塞IO模型
我们钓鱼的时候,有一种方式比较惬意,比较轻松,那就是我们坐在鱼竿面前,这个过程中我们什么也不做,双手一直把着鱼竿,就静静的等着鱼儿咬钩。一旦手上感受到鱼的力道,就把鱼钓起来放入鱼篓中。然后再钓下一条鱼。
映射到Linux操作系统中,这就是一种最简单的IO模型,即阻塞IO。 阻塞 I/O 是最简单的 I/O 模型,一般表现为进程或线程等待某个条件,如果条件不满足,则一直等下去。条件满足,则进行下一步操作。
对于上图的解释:
(1) 用户空间内的用户进程发出系统调用,内核空间收到调用(这一步永远无法省略)。
(2) 内核空间从“无数据报准备好”到“有数据报准备好”,这个时候内核空间只能等待数据(这一步永远无法处理),但是用户空间的用户进程也在这一个时间段内被停止下来了,这就很难受了,后面优化。
(3) 内核空间从“复制数据报”到“复制完成”,这个操作名称是“将数据从内核空间复制到用户空间”,这个操作过程内核空间在操作,无法完成其他操作容易理解,但是用户空间也无法完成其他操作,这就很难受了,后面优化。
(4) 内核空间返回成功指示,用户空间得到数据报,处理数据报,这很好理解(这一步永远无法省略)。
应用进程通过系统调用 recvfrom 接收数据,但由于内核还未准备好数据报,应用进程就会阻塞住,直到内核准备好数据报,recvfrom 完成数据报复制工作,应用进程才能结束阻塞状态。
这种钓鱼方式相对来说比较简单,对于钓鱼的人来说,不需要什么特制的鱼竿,拿一根够长的木棍就可以悠闲的开始钓鱼了(实现简单)。缺点就是比较耗费时间,比较适合那种对鱼的需求量小的情况(并发低,时效性要求低)。
3.2 第二种:非阻塞IO模型
我们钓鱼的时候,在等待鱼儿咬钩的过程中,我们可以做点别的事情,但是,我们要时不时的去看一下鱼竿,一旦发现有鱼儿上钩了,就把鱼钓上来。
映射到Linux操作系统中,这就是非阻塞的IO模型。应用进程与内核交互,目的未达到之前,不再一味的等着,而是直接返回。然后通过轮询的方式,不停的去问内核数据准备有没有准备好。如果某一次轮询发现数据已经准备好了,那就把数据拷贝到用户空间中。
对于上图的解释:
(1) 用户空间内的用户进程发出系统调用,内核空间收到调用(这一步永远无法省略)。
(2) 内核空间从“无数据报准备好”到“有数据报准备好”,这个时候内核空间只能等待数据(这一步永远无法处理),阻塞IO的时候,用户空间的用户进程也在这一个时间段内被停止下来了,非阻塞IO,用户空间的用户进程每个一段时间询问一次内核空间是否准备好,两次询问间隔可以完成自己的事情,提高利用率,还是难受,后面优化。
(3) 内核空间从“复制数据报”到“复制完成”,这个操作名称是“将数据从内核空间复制到用户空间”,这个操作过程内核空间在操作,无法完成其他操作容易理解,但是用户空间也无法完成其他操作,这就很难受了,后面优化。
(4) 内核空间返回成功指示,用户空间得到数据报,处理数据报,这很好理解(这一步永远无法省略)。
应用进程通过 recvfrom 调用不停的去和内核交互,直到内核准备好数据。如果没有准备好,内核会返回error,应用进程在得到error后,过一段时间再发送recvfrom请求。在两次发送请求的时间段,进程可以先做别的事情。
这种方式钓鱼,和阻塞IO比,所使用的工具没有什么变化,但是钓鱼的时候可以做些其他事情,增加时间的利用率。但是还是要时不时的关注鱼竿的动向,现在需要一种带提醒功能的鱼竿。
3.3 第三种:信号驱动IO模型
我们钓鱼的时候,为了避免自己一遍一遍的去查看鱼竿,我们可以给鱼竿安装一个报警器。当有鱼儿咬钩的时候立刻报警。然后我们再收到报警后,去把鱼钓起来。
映射到Linux操作系统中,这就是信号驱动IO。应用进程在读取文件时通知内核,如果某个 socket 的某个事件发生时,请向我发一个信号。在收到信号后,信号对应的处理函数会进行后续处理。
对于上图的解释:
(1) 用户空间内的用户进程新建一个sigio信号处理程序,向内核进程发出sigaction系统调用,告诉内核空间等他的数据报复制好了之后发送sigio信号(PS:这里的第一步不是发起recvfrom系统调用)。
(2) 内核空间从“无数据报准备好”到“有数据报准备好”,这个时候内核空间只能等待数据(这一步永远无法处理),阻塞IO的时候,用户空间的用户进程也在这一个时间段内被停止下来了;非阻塞IO,用户空间的用户进程每个一段时间询问一次内核空间是否准备好,两次询问间隔可以完成自己的事情,提高利用率;信号驱动IO模型,当内核空间准备好的时候,就发送一个sigio信号给用户空间,然后用户空间再发起系统调用,等待整个过程就优化好了,用户空间不用阻塞了。
(3) 内核空间从“复制数据报”到“复制完成”,这个操作名称是“将数据从内核空间复制到用户空间”,这个操作过程内核空间在操作,无法完成其他操作容易理解,但是用户空间也无法完成其他操作,这就很难受了,后面优化。
(4) 内核空间返回成功指示,用户空间得到数据报,处理数据报,这很好理解(这一步永远无法省略)。
应用进程预先向内核注册一个信号处理函数,然后用户进程返回,并且不阻塞,当内核数据准备就绪时会发送一个信号给进程,用户进程便在信号处理函数中开始把数据拷贝的用户空间中。
这种方式钓鱼,和前几种相比,所使用的工具有了一些变化,需要有一些定制(实现复杂)。但是钓鱼的人就可以在鱼儿咬钩之前彻底做别的事儿去了。等着报警器响就行了。
这就是带提醒的功能的鱼竿,要想办法在短时间内钓更多的鱼,就要同一时间多放几条鱼竿一起钓,就是线程池,就是另外一种IO.
3.4 第四种:IO复用模型,线程池
我们钓鱼的时候,为了保证可以最短的时间钓到最多的鱼,我们同一时间摆放多个鱼竿,同时钓鱼。然后哪个鱼竿有鱼儿咬钩了,我们就把哪个鱼竿上面的鱼钓起来。
映射到Linux操作系统中,这就是IO复用模型。多个进程的IO可以注册到同一个管道上,这个管道会统一和内核进行交互。当管道中的某一个请求需要的数据准备好之后,进程再把对应的数据拷贝到用户空间中。
对于上图的解释:
(1) 用户空间内的用户进程向内核进程发出select系统调用,注册到内核空间(PS:这里的第一步不是发起recvfrom系统调用也不是sigiaction系统调用了)。
(2) 内核空间从“无数据报准备好”到“有数据报准备好”,这个时候内核空间只能等待数据(这一步永远无法处理),阻塞IO的时候,用户空间的用户进程也在这一个时间段内被停止下来了;非阻塞IO,用户空间的用户进程每个一段时间询问一次内核空间是否准备好,两次询问间隔可以完成自己的事情,提高利用率;信号驱动IO模型,当内核空间准备好的时候,就发送一个sigio信号给用户空间,然后用户空间再发起系统调用(等待整个过程就优化好了,用户空间不用阻塞了)IO复用模型,进程受阻于select调用,等待多个套接字中的任意一个变为可读,当内核空间返回可读条件的时候,用户空间发起recvfrom系统调用。
(3) 内核空间从“复制数据报”到“复制完成”,这个操作名称是“将数据从内核空间复制到用户空间”,这个操作过程内核空间在操作,无法完成其他操作容易理解,但是用户空间也无法完成其他操作,这就很难受了,后面优化。
(4) 内核空间返回成功指示,用户空间得到数据报,处理数据报,这很好理解(这一步永远无法省略)。
IO多路转接是多了一个select函数,多个进程的IO可以注册到同一个select上,当用户进程调用该select,select会监听所有注册好的IO,如果所有被监听的IO需要的数据都没有准备好时,select调用进程会阻塞。当任意一个IO所需的数据准备好之后,select调用就会返回,然后进程在通过recvfrom来进行数据拷贝(只要一条鱼竿有鱼了就通知,上图不准确,应该是用户空间有多个进程,每个进程都是用select,图中用户空间只有一个进程,给人感觉不好理解)。
这里的IO复用模型,并没有向内核注册信号处理函数,所以,它并不是非阻塞的。进程在发出select后,要等到select监听的所有IO操作中至少有一个需要的数据准备好,才会有返回,并且也需要再次发送请求去进行文件的拷贝。
这种方式的钓鱼,通过增加鱼竿的方式,可以有效的提升效率。
问题1:提供一种异步IO?
回答1:以上四种IO都是同步的,且看第五种。
解释:我们说阻塞IO模型、非阻塞IO模型、IO复用模型和信号驱动IO模型都是同步的IO模型。原因是因为,无论以上那种模型,真正的数据拷贝过程,都是同步进行的。
问题2:为什么信号驱动IO不是异步的?
回答2:信号驱动,内核是在数据准备好之后通知进程,然后进程再通过recvfrom操作进行数据拷贝。整个过程中,我们可以认为数据准备阶段是异步的,但是,数据拷贝操作是同步的。所以,整个IO过程也不能认为是异步的。
3.5 第五种:异步IO模型
我们钓鱼的时候,采用一种高科技钓鱼竿,即全自动钓鱼竿。可以自动感应鱼上钩,自动收竿,更厉害的可以自动把鱼放进鱼篓里。然后,通知我们鱼已经钓到了,他就继续去钓下一条鱼去了。
映射到Linux操作系统中,这就是异步IO模型。应用进程把IO请求传给内核后,完全由内核去操作文件拷贝。内核完成相关操作后,会发信号告诉应用进程本次IO已经完成。
对于上图的解释:
(1) 用户空间内的用户进程向内核进程发出aio_read系统调用(PS:这里的第一步不是发起recvfrom系统调用)。
(2) 内核空间从“无数据报准备好”到“有数据报准备好”,这个时候内核空间只能等待数据(这一步永远无法处理),阻塞IO的时候,用户空间的用户进程也在这一个时间段内被停止下来了;非阻塞IO,用户空间的用户进程每个一段时间询问一次内核空间是否准备好,两次询问间隔可以完成自己的事情,提高利用率;信号驱动IO模型,当内核空间准备好的时候,就发送一个sigio信号给用户空间,然后用户空间再发起系统调用,等待整个过程就优化好了,用户空间不用阻塞了。
(3) 内核空间从“复制数据报”到“复制完成”,这个操作名称是“将数据从内核空间复制到用户空间”,这个操作过程内核空间在操作,无法完成其他操作容易理解,但是以上四个,用户空间也无法完成其他操作,这就很难受了,对于异步IO模型,从内核空间复制数据报到复制完成,用户空间不用阻塞。
(4) 内核空间向用户空间 递交在aio_read指令中指定的信号 ,用户空间完成数据处理和程序处理数据报,这很好理解(这一步永远无法省略)。
用户进程发起aio_read操作之后,给内核传递描述符、缓冲区指针、缓冲区大小等,告诉内核当整个操作完成时,如何通知进程,然后就立刻去做其他事情了。当内核收到aio_read后,会立刻返回,然后内核开始等待数据准备,数据准备好以后,直接把数据拷贝到用户控件,然后再通知进程本次IO已经完成。
这种方式的钓鱼,无疑是最省事儿的。啥都不需要管,只需要交给鱼竿就可以了。
3.6 五种IO模型对比
六个概念:磁盘、内核空间、用户空间
操作对象:文件/数据
操作名称:IO操作
操作方式:复制
金手指:五种IO模型一句话总结
(1) 同步阻塞IO:全程阻塞(等待数据期间,recvfrom系统调用阻塞,复制数据也阻塞);
(2) 同步非阻塞IO:等待数据轮询(等待数据期间,不断发出recvfrom系统调用),复制数据阻塞 ;
(3) 同步信号驱动IO:等待数据不阻塞(建立用来接收sigio信号的信号处理程序,发出sigaction系统调用得到返回,信号处理程序收到内核空间的sigio信号的时候,发出recvfrom系统调用),复制数据阻塞;
(4) 异步IO:等待数据和复制数据都不阻塞(发出aio_read系统调用得到返回,信号处理程序收到内核空间的递交的aio_read中的指定信号,表示复制完成,使用程序处理数据报);
(5) IO复用:全程阻塞(发出select系统调用,收到可读指令后,发出recvfrom系统调用) 。
四、面试金手指
金手指:从Linux五种IO到Java三种IO
(1) 在Linux(UNIX)操作系统中,共有五种IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型。、
(2) 在Java中,主要有三种IO模型,分别是阻塞IO(BIO)、非阻塞IO(NIO)和 异步IO(AIO)。
(3) Java中提供的IO有关的API,在文件处理的时候,其实依赖操作系统层面的IO操作实现的。
金手指:Java三个IO和Linux五种IO及其对应关系
Java BIO 对应 Linux 同步非阻塞IO
Java NIO 对应 Linux 信号驱动IO
Java AIO 对应 Linux 异步IO
金手指:五种IO模型一句话总结
(1) 同步阻塞IO:全程阻塞(等待数据期间,recvfrom系统调用,复制数据也阻塞);
(2) 同步非阻塞IO:等待数据轮询(等待数据期间,不断发出recvfrom系统调用),复制数据阻塞 ;
(3) 同步信号驱动IO:等待数据不阻塞(建立用来接收sigio信号的信号处理程序,发出sigaction系统调用得到返回,信号处理程序收到内核空间的sigio信号的时候,发出recvfrom系统调用),复制数据阻塞;
(4) 异步IO:等待数据和复制数据都不阻塞(发出aio_read系统调用得到返回,信号处理程序收到内核空间的递交的aio_read中的指定信号,表示复制完成,使用程序处理数据报);
(5) IO复用:全程阻塞(发出select系统调用,收到可读指令后,发出recvfrom系统调用) 。
五、尾声
深入底层,Linux五种IO模型全解析,完成了。
天天打码,天天进步!!!
以上是关于深入底层,Linux五种IO模型全解析的主要内容,如果未能解决你的问题,请参考以下文章