IO概念和五种IO模型

1、什么是IO?程序员

    咱们都知道unix世界里、一切皆文件、而文件是什么呢?文件就是一串二进制流而已、无论socket、仍是FIFO、管道、终端、对咱们来讲、一切都是文件、一切都是流、在信息交换的过程当中、咱们都是对这些流进行数据的收发操做、简称为I/O操做(input and output)、往流中读出数据、系统调用read、写入数据、系统调用write、不过话说回来了、计算机里有这么多的流、我怎么知道要操做哪一个流呢?作到这个的就是文件描述符、即一般所说的fd、一个fd就是一个整数、因此对这个整数的操做、就是对这个文件(流)的操做、咱们建立一个socket、经过系统调用会返回一个文件描述符、那么剩下对socket的操做就会转化为对这个描述符的操做、不能不说这又是一种分层和抽象的思想、web

136604819_1_20180624055738145

2、IO交互编程

    一般用户进程中的一个完整IO分为两阶段:缓存

    用户空间 <-----> 内核空间、服务器

    内核空间 <-----> 设备空间、网络

136604819_2_20180624055738192

    内核空间中存放的是内核代码和数据、而进程的用户空间中存放的是用户程序的代码和数据、不论是内核空间仍是用户空间、它们都处于虚拟空间中、Linux使用两级保护机制:0级供内核使用、3级供用户程序使用、多线程

操做系统和驱动程序运行在内核空间、应用程序运行在用户空间、二者不能简单地使用指针传递数据、由于Linux使用的虚拟内存机制、其必须经过系统调用请求kernel来协助完成IO动做、内核会为每一个IO设备维护一个缓冲区、用户空间的数据可能被换出、当内核空间使用用户空间指针时、对应的数据可能不在内存中并发

    对于一个输入操做来讲、进程IO系统调用后、内核会先看缓冲区中有没有相应的缓存数据、没有的话再到设备中读取、由于设备IO通常速度较慢、须要等待、内核缓冲区有数据则直接复制到进程空间、异步

    因此、对于一个网络输入操做一般包括两个不一样阶段:socket

   (1)等待网络数据到达网卡 –> 读取到内核缓冲区

   (2)从内核缓冲区复制数据 –> 用户空间

    IO有内存IO

    网络IO

    磁盘IO三种、一般咱们说的IO指的是后二者

3、POSIX

    对IO底层交互感兴趣的小伙伴能够好好了解一下POSIX(Portable Operating System Interface for Computing System)、我对深沉次原理也不怎么熟、之因此写此篇博文也是为了后面的Java IO学习、深刻浅出点到便可、此章节给有兴趣的朋友一个引子、

4、IO模型

   《UNIX网络编程》说得很清楚、5种IO模型分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型、异步IO模型、前4种为同步IO操做、只有异步IO模型是异步IO操做、请仔细阅读IO交互便于理解IO模型

136604819_3_20180624055738270

(一)阻塞IO模型

    当用户进程调用了recvfrom这个系统调用、内核就开始了IO的第一个阶段:准备数据、对于网络IO来讲、不少时候数据在一开始尚未到达(好比、尚未收到一个完整的UDP包)、这个时候内核就要等待足够的数据到来、而在用户进程这边、整个进程会被阻塞、当内核一直等到数据准备好了、它就会将数据从内核中拷贝到用户内存、而后返回结果、用户进程才解除阻塞的状态、从新运行起来、几乎全部的程序员第一次接触到的网络编程都是从listen()、send()、recv()等接口开始的、这些接口都是阻塞型的、

blocking IO的特色就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被阻塞了、

    典型应用:阻塞Socket、Java BIO

    进程阻塞挂起不消耗CPU资源、及时响应每一个操做

    实现难度低、开发应用较容易

    适用并发量小的网络应用开发

    不适用并发量大的应用、由于一个请求IO会阻塞进程、因此、得为每请求分配一个处理进程(线程)以及时响应、系统开销大

136604819_4_20180624055738349

(二)非阻塞IO模型

    当用户进程发出read操做时、若是内核中的数据尚未准备好、那么它并不会block用户进程、而是马上返回一个error、从用户进程角度讲、它发起一个read操做后、并不须要等待、而是立刻就获得了一个结果、用户进程判断结果是一个error时、它就知道数据尚未准备好、因而它能够再次发送read操做、一旦内核中的数据准备好了、而且又再次收到了用户进程的系统调用、那么它立刻就将数据拷贝到了用户内存、而后返回、非阻塞的接口相比于阻塞型接口的显著差别在于、在被调用以后当即返回、

在非阻塞式IO中、用户进程实际上是须要不断的主动询问kernel数据准备好了没有

    典型应用:Socket 设置 NONBLOCK

    进程轮询(重复)调用、消耗CPU的资源

    实现难度低、开发应用相对阻塞IO模式较难

    适用并发量较小、且不须要及时响应的网络应用开发

136604819_5_20180624055738442

(三)IO复用模型

    多个的进程的IO能够注册到一个复用器(select)上、当用户进程调用该select、select会监听全部注册进来的IO、若是select全部监听的IO在内核缓冲区都没有可读数据、select调用进程会被阻塞、而当任一IO在内核缓冲区中有可数据时、select调用就会返回、然后select调用进程能够本身或通知另外的进程(注册进程)来再次发起读取IO、读取内核中准备好的数据、多个进程注册IO后、只有一个select调用进程被阻塞

    IO复用相对阻塞和非阻塞更难简单说明、因此额外解释一段、其实IO复用模型和阻塞IO模型并无太大的不一样、事实上、还更差一些、由于这里须要使用两个系统调用(select和 recvfrom)、而阻塞IO模型只有一次系统调用(recvfrom)、可是、用select的优点在于它能够同时处理多个链接、因此若是处理的链接数不是很高的话、使用select/epoll的web server不必定比使用多线程加阻塞IO的web server性能更好、可能延迟还更大、select/epoll的优点并非对于单个链接能处理得更快、而是在于能处理更多的链接

在IO复用模型中、对于每个socket、通常都设置成为非阻塞、可是、如上图所示、整个用户的进程实际上是一直被阻塞的、只不过进程是被select这个函数阻塞、而不是被socket IO给阻塞

    典型应用:Java NIO、Nginx(epoll、poll、select)

    专注进程解决多个进程IO的阻塞问题、性能好、Reactor模式

    实现、开发应用难度较大

    适用高并发服务应用开发、一个进程/线程响应多个请求

136604819_6_20180624055738520

(四)信号驱动式IO模型

    信号驱动式IO就是指进程预先告知内核、向内核注册一个信号处理函数、而后用户进程返回不阻塞、当内核数据就绪时会发送一个信号给进程、用户进程便在信号处理函数中调用IO读取数据、从图中明白实际IO内核拷贝到用户进程的过程仍是阻塞的、信号驱动式IO并无实现真正的异步、由于通知到进程以后、依然是由进程来完成IO操做、

这和后面的异步IO模型很容易混淆、须要理解IO交互并结合五种IO模型的比较阅读

在信号驱动式IO模型中、依然不符合POSIX描述的异步IO、只能算是半异步、而且实际中并不经常使用、

    典型应用:(不知道)

    回调机制、实现、开发应用难度大

136604819_7_20180624055738645(五)异步IO模型

    用户进程发起aio_read(POSIX异步IO函数aio_或者lio_开头)操做以后、给内核传递描述符、缓冲区指针、缓冲区大小和read相同的三个参数以及文件偏移(与lseek相似)、告诉内核当整个操做完成时、如何通知咱们、马上就能够开始去作其它的事、而另外一方面、从内核的角度、当它受到一个aio_read以后、首先它会马上返回、因此不会对用户进程产生任何阻塞、而后、内核会等待数据准备完成、而后将数据拷贝到用户内存、当这一切都完成以后、内核会给用户进程发送一个信号、告诉它aio_read操做完成了

    异步IO的工做机制是:告知内核启动某个操做、并让内核在整个操做完成后通知咱们、这种模型与信号驱动的IO区别在于、信号驱动IO是由内核通知咱们什么时候能够启动一个IO操做、这个IO操做由用户自定义的信号函数来实现、而异步IO模型是由内核告知咱们IO操做什么时候完成、

这和前面的信号驱动式IO模型很容易混淆、须要理解IO交互并结合五种IO模型的比较阅读

在异步IO模型中、真正实现了POSIX描述的异步IO、是五种IO模型中惟一的异步模型

    典型应用:Java 7 AIO、高性能服务器应用

    不阻塞、数据一步到位、Proactor模式

    须要操做系统的底层支持、LINUX 2.5 版本内核首现、2.6 版本产品的内核标准特性

    回调机制、实现、开发应用难度大

    很是适合高性能高并发应用

(六)五种IO模型的比较

阻塞IO和非阻塞IO的区别在哪?

    前面的介绍中其实已经很明确的说明了这二者的区别、调用阻塞会一直阻塞住对应的进程直到操做完成、而非阻塞IO在内核还没准备数据的状况下会马上返回、阻塞和非阻塞关注的是进程在等待调用结果时的状态、阻塞是指调用结果返回以前、当前进程会被挂起、调用进程只有在获得结果才会返回、非阻塞调用指不能马上获得结果、该调用不会阻塞当前进程、

同步IO和异步IO区别在哪?

    二者的区别就在于同步作IO操做的时候会将进程阻塞、按照这个定义、以前所述的阻塞IO、非阻塞IO、IO复用、信号驱动都属于同步IO、有人可能会说、非阻塞IO并无被阻塞啊、这里有个很是狡猾的地方、定义中所指的IO操做是指真实的IO操做、就是例子中的recvfrom这个系统调用、非阻塞IO在执行recvfrom这个系统调用的时候、若是内核的数据没有准备好、这时候不会阻塞进程、可是、当内核中数据准备好的时候、recvfrom会将数据从内核拷贝到用户内存中、这个时候进程是被阻塞了、信号驱动也是一样的道理、在这段时间内、进程是被阻塞的、而异步IO则不同、当进程发起IO操做以后、就直接返回不再理睬了、直到内核发送一个信号、告诉进程说IO完成、在这整个过程当中、进程彻底没有被阻塞、

    同异步IO的根本区别在于、同步IO主动的调用recvfrom来将数据拷贝到用户内存、而异步则彻底不一样、它就像是用户进程将整个IO操做交给了他人(内核)完成、而后他人作完后发信号通知、在此期间、用户进程不须要去检查IO操做的状态、也不须要主动的去拷贝数据

信号驱动式IO和异步IO的区别?

    这里之因此单独拿出来是由于若是尚未清除IO概念很容易混淆、因此理解IO模型以前必定要理解IO概念、若是看完前面两个问题、相信也能理解信号驱动IO与异步IO的区别在于启用异步IO意味着通知内核启动某个IO操做、并让内核在整个操做(包括数据从内核复制到用户缓冲区)完成时通知咱们、也就是说、异步IO是由内核通知咱们IO操做什么时候完成、即实际的IO操做也是异步的、信号驱动IO是由内核通知咱们什么时候能够启动一个IO操做、这个IO操做由用户自定义的信号函数来实现

相关文章
相关标签/搜索