根据冯·诺依曼结构,计算机结构分为5大部分:运算器、控制器、存储器、输入设备、输出设备。
从计算机结构上来说,IO
就是计算机系统和外部设备(输入设备、输出设备,硬盘等也属于外部设备)之间的通信的过程。
从应用程序上看,为了保证操作系统的稳定性和安全性,一个进程的地址空间分为用户空间(User space)
和内核空间(Kernel space)
。
我们平时运行的应用程序都是运行在用户空间
的,只用内核空间
才能进行系统态级别的资源操作,如文件管理、内存管理,进程通信等。而用户空间
的程序是不能直接访问内核空间
的。所以要进行IO
操作,必须依赖内核空间
的能力。
IO(Input/Output 输入/输出)
即数据的读取(接收)和写入(发送)操作,通常用户进程一个完整的IO
分为两个阶段:用户空间<——>内核空间,内核空间<——>设备空间(磁盘、网络等)
;IO
有内存IO
、磁盘IO
、网络IO
三种,一般IO
说的是指后两种。
从应用程序的角度看,我们应用程序对操作系统的内核发起IO调用
(系统调用),内核执行具体的IO
操作。
进程IO
系统调用后,内核会首先看缓冲区中有没有相应的缓存数据,有数据则直接复制到进程空间(用户空间
),没有的话再到设备中取(磁盘),因为磁盘IO
一般数据较慢,需要等待。
所以当应用程序发起IO
调用后,会经历两个步骤:
(1)内核等待IO
设备准备好数据。
(2)内核将数据从内核空间
拷贝到用户空间
。
UNIX
系统下,I/O
模型一共分为五种:同步阻塞I/O,同步非阻塞I/O,I/O多路复用,信号驱动I/O,异步I/O
。
Java
中有3中常见的I/O
模型:
(1)BIO
:同步阻塞I/O
。
(2)NIO
:I/O多路复用
。
(3)AIO
:异步I/O
。
进程发起I/O调用
后,进程被阻塞,转到内核空间处理,整个I/O
处理完后返回进程,操作成功则获取到数据。
通俗来说,就是A拿着一根鱼竿钓鱼,在钓鱼的过程中,什么事都不干,一直等待上钩,有上钩,则钓起来,在开始下一次的钓鱼或者做其他事。
应用:阻塞scoket
、Java BIO
。
特点:进程阻塞挂起不消耗CPU
资源,及时响应每个操作,使用于并发量小的应用开发,不适用于高并发,因为请求I/O
会阻塞进程,需要为每一个请求分配一个处理进程,系统开销大。
进程发起IO
系统调用后,内核的缓冲区中如果有相应的数据,则直接返回数据给进程,如果没有,则到IO
设备中取数据,进程返回一个错误而不会被阻塞,每隔一定的时间,应用程序会不断进行IO系统调用
轮询内核数据是否已经准备好,没有,则返回一个错误,有则返回数据。注意,返回数据时,等待数据从内核空间
拷贝到用户空间
的这段时间,进程依然是阻塞的,直到拷贝完成。
通俗来说,A拿着一根鱼竿钓鱼,在等待上钩的过程中,A可以去干其他的事,比如玩会手机、看看书、上个厕所等,只不过隔一段时间就看下是否有上钩,一旦有上钩,就停下手中的事,把钓起来。把钓起来的这段过程中,A是干不了其他的事的。
特点:进程轮询(重复)调用,消耗CPU
资源。适用于并发量小,不需要即使相应的应用程序。
多个进程的I/O
注册到一个复用器(select
)上,在用一个进程调用这个复用器,select
会监听所有注册的I/O
。
如果select
监听的I/O
在内核中都没有可读数据,则这个select
调用进程会阻塞,当任一I/O调用
在内核缓存区中有可读数据时,select
进程就会返回,而后,select
调用进程可以自己或通知注册的I/O
进程再次发起读取I/O
,把内核中的数据读到用户空间
中。所有,当多个进程注册I/O
后,就只会有一个select
调用进程会被阻塞。
当然,某个进程把数据从内核空间
读取到用户空间
这段时间,对于这个进程而言,是阻塞的。
支持多路复用的系统调用:
(1)select
调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
(2)epoll
调用 :linux 2.6
内核,属于select
调用的增强版本,优化了IO
的执行效率。
通俗来说,A比较有钱,一次带十几根鱼竿去钓鱼,一次性有十几根鱼竿在等待上钩,A不停的查看每个鱼竿,那只鱼竿有上钩,就去那个鱼竿那把钓起来。
典型运用:select、poll、epoll
三种方法,nginx
可选这三种方案;Java NIO
。
特点:专一进程解决多个进程阻塞问题,效率高。适用于高并发。
进程发起I/O系统调用时,会在内核中注册一个信号处理函数,然后进程返回不阻塞,当内核中数据已经准备好就会发送一个信号给进程,进程在信号处理函数中调用I/O读取函数。
通俗来说,A拿着一根鱼竿去钓鱼,他比较聪明,在鱼竿上绑了一个铃铛,然后就去干其他的事了不再关注鱼竿这了,铃铛响了,说明有上钩了,就过来把给钓起来。
进程发起I/O系统调用,进程返回(不阻塞),但是也不会返回结果,当内核把整个I/O处理完后,会通知进程结果,进程直接获取到数据。
当应用程序调用aio_read
时,内核一方面去获取数据返回,另一方面把程序的控制权还给应用程序,应用程序可以去干其他的事,所以非阻塞。当内核中数据准备好后,由内核将数据拷贝到应用程序中,返回aio_read
中处理好的函数处理程序。即内核将整个I/O
过程处理完后(内核把数据拷贝到用户空间),再通知进程。而信号驱动I/O
是内核中数据准备好了,通知进程调用I/O
,读取数据。
通俗来说,A去钓鱼,但是他有其他的事要忙,就雇佣B帮他钓鱼,当B把钓起来后,就打电话通知A,A过来把拿走。
典型应用:Java7 AIO;
特点:非常适合高性能高并发应用,但是实现、开发难度较大。
综上而言,就阻塞程度:同步阻塞I/O>同步非阻塞I/O>I/O多路复用>信号驱动I/O>异步I/O