一.IO读写的基本原理
1.1 内核态和用户态
为了避免用户进程直接操作内核,保证内核安全,操作系统将内存分为了两部分:内核空间和用户空间内核模块运行在内核空间,对应内核态,用户程序运行在用户空间,对应用户态
1.2 系统调用
用户态进程不能直接访问内核空间的数据,也不能直接调用内核函数,因此需要将进程切换到内核才可以进行这个操作这个过程叫系统调用
1.3 内核缓冲区与进程缓冲区
用户程序进行IO的读写依赖于底层的IO读写,主要是read和wirte两大原语我们理解的应用程序IO过程:物理设备(磁盘等)和内存的读写,实际上这并不是真正的用户程序的IO
每一次物理设备和内存的资源交换都要经过一次中断这种操作是由操作系统系统内核(Kernel)完成的,与我们无关系统中断是一次非常消耗系统资源的过程,大致过程为:发生中断时,需要保存进程的一系列信息(比如PCB),结束中断要恢复一系列信息频繁的中断会极大的影响操作系统的性能,所以出现了内核缓冲区和进程缓冲区的概念内存当中有一个内核缓冲区,每个进程对应一个进程缓冲区
用户程序的读,使用read函数,将内核缓冲区的数据复制到进程缓冲区用户程序的写,使用wirte函数,将进程缓冲区的数据复制到内存缓冲区
所以,真正的用户程序的IO并没有内存和物理设备之间资源的交换,而是进程缓冲区和内存缓冲区的复制
二.四大IO模型
2.1 阻塞与非阻塞
阻塞IO:执行read和wirte函数必须到内核态,只有内核IO完成后才能返回到用户态执行用户程序的操作指令非阻塞IO:用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间去执行用户程序的后续指令,并且内核IO会返回一个当前IO的状态值在java中默认创建的socket都属于阻塞IO
2.2 同步与异步
同步IO:用户空间(进程或者线程)是主动发起IO请求的一方,系统内核被动接受异步IO:系统内核主动发起IO请求,用户空间被动接受
2.3 同步阻塞式IO (BIO)
默认情况下,在java应用程序进程中所有对 socket 连接进行的IO 当需要IO操作时,Java应用程序发起系统调用: 一.等待内核缓冲区存在需要的数据 二.等待数据从内核缓冲区复制到进程缓冲区 在这两步操作完成之前,Java进程(线程)都处于阻塞状态,直到两步操作完成,返回成功后,才会继续执行 BIO的缺点:一般情况下,会为每个连接(socket)配备一个独立的线程,一个线程维护一个连接的IO操作。在高并发情况下,BIO需要大量的线程来维护网络连接,内存、线程切换的开销会非常巨大,新跟那个很低
2.4 同步非阻塞IO(NIO)
注:这里的NIO和Java中的NIO是两个概念,Java中的NIO指的是多路复用IO模型 在linux系统下,socket连接默认是阻塞的,可以将socket设置为非阻塞模式,这样就变成了同步非阻塞IO 当需要IO操作时,Java应用程序发起系统调用: 一.当内核缓冲区中没有所需要的数据时,系统调用会立即返回一个调用失败的信息,并立即返回,此时应用程序在执行,用户进程(线程)需要不断地发起IO系统调用 二.一旦内核缓冲区中存在数据时,用户进程进入阻塞状态,直到数据从内核缓冲区成功复制到进程缓冲区,用户程序回复执行 NIO的优缺点: 优点:在内核缓冲区中不存在数据时,应用进程是不会进入阻塞状态 缺点:应用进程(线程)需要不断地发起系统调用,轮询内核,这将占用大量的CPU,对于高并发情形,不适用
2.5 IO多路复用(JAVA NIO)
在IO多路复用模型中,需要用到另外的两种系统调用:select/epoll 几乎所有的操作系统都支持select系统调用,它具有良好的跨平台性。而epoll是在Linux 2.6 内核中提出来的,是select系统调用的Linux增强版本 在IO多路复用模型中,通过select/epoll两大系统调用,单个应用程序的线程可以不断地轮询成百上千的socket连接的就绪状态,当某个或者某些socket网络连接有IO就绪状态时就返回这些就绪状态 IO多路复用的read大致流程为: (1)选择器注册:将需要read操作的socket提前注册到 select/epoll 选择器中,在java中对应的选择器的类为 Selector 类 (2)就绪状态的轮询:通过选择器的查询方法,查询所有的socket连接,通过查询的系统调用,内核会返回一个就绪的 socket 列表,列表中的 socket 表明内存缓冲区已经存在数据 (3)用户线程获得就绪状态的列表,发起 read 系统和调用,用户线程阻塞,根据对应的 socket 进行数据的复制 (4)复制完成后,用户线程继续执行 IO多路复用的优缺点: 优点:IO多路复用和同步非阻塞IO类似,都需要不断地轮询 socket 的状态,但是IO多路复用可以通过一个线程处理成百上千的 socket 连接,从而大大的减少了系统开销 缺点:本质上 select/epoll 系统调用是是阻塞式的,属于同步IO,在返回 socket 就绪后,数据的复制,进程(线程)还是会进入到阻塞状态 JAVA 的 NIO 组件在 linux 系统上使用的是 epoll 系统调用来实现的,也就是说,JAVA 语言的 NIO 组件所使用的就是 IO多路复用模型
2.6 异步IO
异步 IO 无论是在等待数据到内核缓冲区,还是数据复制的过程中,应用程序都处于非阻塞状态