Java NIO1:I/O模型概述

在开始NIO的学习之前,先对I/O的模型有一个理解,这对NIO的学习是绝对有好处的。我画一张图,简单表示一下数据从外部磁盘向运行中进程的内存区域移动的过程:

Java NIO1:I/O模型概述

这张图片明显忽略了很多细节,只涉及了基本操作,下面分析一下这张图。

用户空间和内核空间

一个计算机通常有一定大小的内存空间,如一台计算机有4GB的地址空间,但是程序并不能完全使用这些地址空间,因为这些地址空间是被划分为用户空间和内核空间的。程序只能使用用户空间的内存,这里所说的使用是指程序能够申请的内存空间,并不是真正访问的地址空间。下面看下什么是用户空间和内核空间:

1、用户空间

     用户空间是常规进程所在的区域,什么是常规进程,打开任务管理器看到的就是常规进程:

     

Java NIO1:I/O模型概述

     JVM就是常规进程,驻守于用户空间,用户空间是非特权区域,比如在该区域执行的代码不能直接访问硬件设备。

2、内核空间

     内核空间主要是指操作系统运行时所使用的用于程序调度、虚拟内存的使用或者连接硬件资源等的程序逻辑。内核代码有特别的权利,比如它能与设备控制器通讯,控制着整个用于区域进程的运行状态。和I/O相关的一点是:所有I/O都直接或间接通过内核空间

那么,为什么要划分用户空间和内核空间呢?这也是为了保证操作系统的稳定性和安全性。用户程序不可以直接访问硬件资源,如果用户程序需要访问硬件资源,必须调用操作系统提供的接口,这个调用接口的过程也就是系统调用。每一次系统调用都会存在两个内存空间之间的相互切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间接收到远程主机的数据,然后再从内核空间复制到用户空间,供用户程序使用。这种从内核空间到用户控件的数据复制很费时,虽然保住了程序运行的安全性和稳定性,但是牺牲了一部分的效率。

最后,如何分配用户空间和内核空间的比例也是一个问题,是更多地分配给用户空间供用户程序使用,还是首先保住内核有足够的空间来运行,还是要平衡一下。在当前的Windows 32位操作系统中,默认用户空间:内核空间的比例是1:1,而在32位Linux系统中的默认比例是3:1(3GB用户空间、1GB内核空间)。

进程执行I/O操作的步骤

缓冲区,以及缓冲区如何工作,是所有I/O的基础。所谓"输入/输出"讲的无非也就是把数据移入或移出缓冲区。

进程执行I/O操作,归结起来,就是向操作系统发出请求,让它要么把缓冲区里的数据排干净(写),要么用数据把缓冲区填满(读)。进程利用这一机制处理所有数据进出操作,操作系统内部处理这一任务的机制,其复杂程度可能超乎想像,但就概念而言,却非常直白易懂,从上面的图,可以总结一下进程执行I/O操作的几步:

1、进程使用底层函数read(),建立和执行适当的系统调用,要求其缓冲区被填满,此时控制权移交给内核

2、内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据

3、磁盘控制器和数据直接写入内核内存缓冲区,这一步通过DMA完成,无需主CPU协助。这里多提一句,关于DMA,可以百度一下,它是现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU的大量中断负载,大大提升了整个系统的效率

4、一盘磁盘控制器把缓冲区填满,内核随即把数据从内核空间的临时缓冲区拷贝到进程执行read()调用时指定的缓冲区

5、进程从用户空间的缓冲区中拿到数据

当然,如果内核空间里已经有数据了,那么该数据只需要简单地拷贝出来即可。至于为什么不能直接让磁盘控制器把数据送到用户空间的缓冲区呢?最简单的一个理由就是,硬件通常不能直接访问用户空间。

同步和异步、阻塞和非阻塞

有了上面对于I/O的理解,我们就可以理解一下同步和异步的区别了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/5e55c6907a5f56aa39c2971519af3b43.html