本文将介绍内核空间与用户空间的区别,I/O模型的分类和原理。
用户空间和内核空间
在学习I/O之前,有必要了解用户空间和内核空间的概念,因为所有的I/O操作都牵涉到用户空间到内核空间的切换。下面看下什么是用户空间和内核空间:
1、用户空间
用户空间是常规进程所在的区域,什么是常规进程,打开任务管理器看到的就是常规进程:
JVM也属于常规进程,驻守于用户空间,用户空间是非特权区域,比如在该区域执行的代码不能直接访问硬件设备。
2、内核空间
内核空间主要是指操作系统运行时所使用的用于程序调度、虚拟内存的使用或者连接硬件资源等的程序逻辑。内核代码有特别的权利,比如它能与设备控制器通讯,控制着整个用于区域进程的运行状态。
为什么要划分用户空间和内核空间?主要是为了保证操作系统的稳定性和安全性。用户程序不可以直接访问硬件资源,如果用户程序需要访问硬件资源,必须调用操作系统提供的接口,这个调用接口的过程也就是系统调用。每一次系统调用都会存在两个内存空间之间的相互切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间接收到远程主机的数据,然后再从内核空间复制到用户空间,供用户程序使用。
如何分配用户空间和内核空间的比例也是一个问题,是更多地分配给用户空间供用户程序使用,还是首先保住内核有足够的空间来运行,还是要平衡一下。在当前的Windows 32位操作系统中,默认用户空间:内核空间的比例是1:1,而在32位Linux系统中的默认比例是3:1(3GB用户空间、1GB内核空间)。
同步和异步、阻塞和非阻塞
IO的分类包括:同步IO、异步IO、阻塞IO和非阻塞IO。
1、同步和异步
同步和异步这个概念比较广,不仅仅是在I/O,其他的还有诸如同步调用/异步调用、同步请求/异步请求,都是一个意思。同步和异步,关注的是消息通信机制。
所谓同步,就是在发出一个"调用请求"时,在没有得到结果之前,该"调用请求"就不返回。换句话说,就是由"调用者"主动等待"调用"的结果。像我们平时写的,方法A调用Math.random()方法、方法B调用String.substring()方法都是同步调用,因为调用者主动在等待这些方法的返回。
所谓异步,则正好相反,"调用"发出之后,这个调用就直接返回了,所有没有返回结果。换句话说,当一个异步调用请求发出之后,调用者不会立刻得到结果。
2、阻塞和非阻塞
阻塞和非阻塞关注的是程序在等待调用结果时的状态。
阻塞调用指的是调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。
非阻塞调用指的是在不能立即得到结果之前,该调用不会阻塞当前线程。
Linux网络I/O模型
由于绝大多数的Java应用都部署在Linux系统上,因此这里谈一下Linux网络I/O模型。分为5类:
1、阻塞I/O模型
阻塞I/O模型就是最常用的I/O模型,缺省情况下所有的文件操作都是阻塞的,以Socket来讲解此模型:在用户空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区或者发生错误时才返回,在此期间会一直等待,进程在从调用recvfrom开始到它返回的整段时间内都是被阻塞的,因此被称为阻塞I/O。
2、非阻塞I/O模型
recvfrom从用户空间到内核空间的时候,如果该缓冲区没有数据的话,就直接返回一个EWOULDBOCK错误,一般都对非阻塞I/O模型进行轮询检查这个状态,看内核空间是不是有数据到来,有数据到来则从内核空间复制数据到用户空间。
3、I/O复用模型
Linux提供select/poll,进程通过将一个或者多个fd传递给select或poll系统调用,阻塞在select操作上,这样select/poll可以帮助我们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此它的使用受到了一些制约。Linux还提供了一个epoll系统调用,epoll使用基于事件驱动方式替代顺序扫描,因此性能更高。当有fd就绪时,立即会掉函数callback。
4、信号驱动I/O模型
首先开启Socket信号驱动I/O功能,并通过系统调用sigaction执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为进程生成一个SIGIO信号,通过信号会掉通知应用程序调用recvfrom来读取数据,并通知主循环函数来处理数据。
5、异步I/O
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知开发者。这种模型与信号驱动I/O模型的主要区别是:信号驱动I/O模型由内核通知应用程序何时可以开始一个I/O操作,异步I/O模型由内核通知应用程序I/O操作何时已经完成。
BIO与NIO
BIO:同步阻塞IO。对应有两种Java模型,一种是1:1模型,一个连 接 对应一个线程;另外一种是M:N模型。如下图所示:
M:N BIO
NIO:同步非阻塞IO,对应Java模型如下图所示:
NIO与BIO的主要区别:
在BIO中,等待客户端发送数据这个过程是阻塞的,这就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因
在NIO中,当一个Socket建立好之后,Thread并不会去阻塞接收这个Socket,而是将这个请求交给Selector,Selector会判断哪个Socket建立完成,然后通知对应线程,对应线程处理完数据再返回给客户端,这样就可以让一个线程处理更多的请求了
在NIO上,Selector做的事情就是以单条线程监视多Socket I/O的状态,空闲时阻塞当前线程,当有一个或者多个Socket有I/O事件时就从阻塞状态中醒来。
网友评论