Unix的五种I/O模型
1、什么是I/O?
IO(Input/Output)就是内存和外部设备(磁盘,网络…)之间拷贝数据的过程。
2、一次I/O的过程
用户的一次I/O分为两个过程:
第一阶段:将数据从外部设备读入到内核缓冲区。
第二阶段:将内核 缓冲区的数据读取到用户进程的缓冲区。
3、为什么I/O操作不是直接由用户进程读取数据到用户进程缓冲区,而是要由内核将数据读取到内核缓冲区再拷贝到用户进程缓冲区?
应用程序没办法直接和硬件打交道 ,必须通过操作系统的内核去和硬件或者网络这些外部设备进行交互,然后用户进程通过操作系统提供的指令集去和内核进行一个交互。
4、五种网络I/O模型
《UNIX网络编程:卷一》第六章—I/O复用。书中讲解了5种类UNIX下可用的I/O模型:
在JDK1.4之前,Java的IO模型只支持阻塞式IO(Blocking IO),简称为BIO
在JDK1.4时,支持了I/O多路复用模型,相对于之前的IO模型,这是一个新的模型,所以称之为NIO(New IO),有新就有旧,所以有时也把BIO称之为OIO(old IO),其实都是一个意思。到现在为止,JDK1.8都已经出来了,JDK1.4时引入的nio包,也没有什么新鲜的了,所以更多的人愿意把NIO理解为None-Blocking IO,即非阻塞IO。
在JDK1.7时,对NIO包进行了升级,支持了异步I/O(Asynchronous IO),简称为AIO,因为是对nio包的升级,所有有时又称之为NIO2.0。
1、阻塞式I/O:blocking IO
用户进程所在线程在调用系统函数recvfrom拷贝数据的时候,用户进程所在线程会根据**socketd(套接字描述符)**找到内核中关于该套接字连接的缓冲区域,如果该缓冲区无数据报准备好,用户进程所在线程就会休眠,等待该缓冲区有数据报准备好,再从内核缓冲区将数据报拷贝到用户进程的缓冲区域。然后用户进程所在线程才开始处理相应的数据报。在这个IO过程中,内核在准备数据报和拷贝数据报到用户进程缓冲区这段时间,用户进程所在线程是什么事情都做不了的,一直处于阻塞状态。换句话说,用户进程当前线程在调用 recvfrom函数之后,其会一直处于阻塞状态,直到拷贝成功或者返回错误。
2、非阻塞式I/O: nonblocking IO
用户进程所在线程在调用系统函数recvfrom拷贝数据的时候,用户进程所在线程会根据socketd(套接字描述符)找到内核中关于该套接字连接的缓冲区域,如果该缓冲区无数据报准备好,内核快速返回用户进程所在线程EWOULDBLOCK错误,用户进程所在线程会一直处于轮询状态,一直调用recvfrom函数,直到有数据报准备好,再将数据报拷贝到用户缓冲区。
思考:在多线程中,是否可以用一个单独的线程轮询调用recvfrom函数,轮询所有的套接字连接,当相应的套接字有数据报拷贝成功后,再开启新的线程去处理该套接字数据业务?
3、I/O复用(select,poll,epoll…):IO multiplexing
用户进程所在线程调用select函数,该线程会一直被阻塞,直到有一个或者多个套接字连接状态变为可读,然后用户进程所在线程调用recvfrom函数,拷贝相应套接字的数据到用户进程缓冲区中。
多路复用IO和非阻塞IO的区别在于:非阻塞IO通过用户线程去轮询调用recvfrom函数,直到有数据报准备好,然后进行拷贝,线程不会处于休眠状态。多路复用IO通过select函数将监听数据报是否准备好的任务交给了内核去完成,select函数会一直阻塞调用线程,直到一个或多个数据报准备就绪。非阻塞IO一个线程一次只能处理一个套接字连接,而多路复用一个线程一次可以处理多个套接字连接。
思考:是否可以用一个单独的线程轮询select函数获取准备就绪的套接字,然后开启新的线程去调用recvfrom函数,实现数据的拷贝?
4、信号驱动式I/O(SIGIO):signal driven IO
用户进程所在线程调用sigio函数后,相当于告诉内核,有准备好的数据报就通知我,此时用户进程所在线程处于未阻塞的状态,可以执行其他的任务,等到内核中数据报准备就绪之后,内核向该用户线程发送一个准备就绪的信号,用户进程所在线程收到信号后调用recvfrom函数,拷贝相应的数据到用户缓冲区。信号量和IO复用的最大区别是IO复用会阻塞select函数调用者线程,信号量不会阻塞线程。
5、异步I/O(POSIX的aio_系列函数):asynchronous IO
用户进程所在线程调用aio_read函数,无论内核中数据报是否准备好,都不阻塞线程,直接返回了,所有的任务都交给了内核去完成,当数据报准备好之后,内核将数据报拷贝到用户进程缓冲区,然后内核发送成功的信号给用户进程指定线程,线程根据信号通知处理相应的数据报。异步IO和信号驱动模型的区别在于:信号驱动模型是给用户进程所在线程一个数据报准备就绪的信号,用户进程所在线程收到就绪信号再调用recvfrom函数去主动拷贝数据到用户进程缓冲区。而异步IO是通过aio_read命令,将拷贝数据报的任务交给了内核,在拷贝完成之后再发送成功的信号给用户进程所在线程。
总结
阻塞IO模型,非阻塞IO模型,IO复用模型,信号驱动模型都是同步IO模型,因为在调用recvfrom函数在拷贝数据时,都会阻塞线程,只有异步IO模型,是由内核直接拷贝数据到用户进程缓冲区,不存在阻塞的问题。《Netty权威指南》《Unix网络编程》中对IO模型分析时,都是描述的对用户进程的阻塞。笔者认为,java程序运行的最小单位是线程,所以是对进程中线程的阻塞。个人理解,可能有失偏颇,欢迎大家斧正。