Java NIO(一)I/O简介

系列文章

第一篇 Java NIO(一)I/O简介
第一篇 Java NIO(二)BIO
第一篇 Java NIO(三)NIO之Buffer
第一篇 Java NIO(四)NIO之Channel
第一篇 Java NIO(五)NIO之Selector
第一篇 Java NIO(六)Netty


Java NIO(一)IO简介

本文章是对《Netty 4核心原理于手写RPC框架实战》该书研读的总结和记录,如有侵权,望及时联系


前言

高级编程语言都是对于计算机操作系统底层的一次再封装,使其能够为人们所理解和运用,但是封装必定会导致不可见性,这就会使得从上层(高级编程语言本身)来理解相关知识变得困难。因此本文从根本从发,从操作系统底层开始摸索I/O的相关知识,以下内容纯属个人的学习认知,如有错误,欢迎大家雅正。

一、I/O

1 什么是I/O

我们都知道,在UNIX的世界里,一切皆是文件,而文件是什么呢?文件就是一串二进制流,无论是Socket,还是FIFO,管道、终端、设备,对于计算机来说,一切都是文件,都是流。在信息交换的过程中,计算机都是对这些文件流进行数据的收发操作,简称为I/O操作(Input/Output)。从流中读取数据,系统调用read,往流中写数据,系统调用write。

可是计算机是如何分辨如此众多的文件流,又是如何知道需要操作哪一个文件流的呢?实际上这是由操作系统内核创建的文件描述符(fd,file description)来标识的。一个fd是一个非负整数。所以对文件流的操作就转换为对该fd进行操作。fd是对底层抽象文件流的一种具化形式

2 I/O的交互过程

我们知道,在CPU的所有指令中,有些指令是比较危险的,一旦错用,很可能导致系统崩溃,如清理内存,设置时钟等。因此出于系统安全、稳定等考虑,操作系统采用分级思想,将CPU特权分为多个等级,(Ring0~Ring3)并将将内存地址空间划分为用户空间和内核空间,用户空间用于运行用户自我的应用程序,内核空间由操作系统的内核来使用。(Linux只使用了Ring0和Ring3,Ring3供用户程序使用,此时进程为用户态;Ring0供内核代码使用,此时进程为内核态)对于一些级别比较重要的(危险的)指令,只能运行在内核态中,由内核来执行。

有了上述的知识储备之后,我们来看下I/O的交互过程,由于用户态和内核态的划分,I/O的交互过程可以分为两个阶段。首先是经过内核空间,由操作系统处理;紧接着是到用户空间,交由应用程序处理,交互流程如下图所示:
在这里插入图片描述
注:个人理解,其中的磁盘指的应该是外界设备,包括磁盘和网卡。
操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者是不能简单的通过指针来传递数据的。因为Linux使用的虚拟内存的机制,必须通过系统调用请求Kernel(内核)来协助完成I/O操作。因次具体的I/O交互过程可以这么来理解,以网络输入I/O为例说明:

  1. 等待网络数据到达网卡,然后将数据读取到内核缓冲区
  2. 从内核缓冲区复制数据,然后拷贝到用户空间

3 I/O的分类

I/O有内存I/O、网络I/O、磁盘I/O,通常我们所说的I/O指的是后两者,网络和磁盘I/O,下图是I/O通信过程的调度过程:
在这里插入图片描述

二、五种I/O通信模型

1.阻塞式I/O模型

阻塞I/O通信模型的通信过程如下所示:
阻塞I/O通信模型
当用户进程调用了recvfrom这个系统调用之后,内核就开始了I/O的第一阶段:数据准备。很多时候,数据在一开始还没有到达或者没有收到一个完整的网络包,这个时候内核就需要等待数据到来,表现在用户进程这边就是整个进程被阻塞,而当数据准备好后,内核就会将数据从内核拷贝到用户内存,然后返回结果,用户进程这时结束阻塞状态,进行数据处理
总结如下:

特 点在I/O执行的两个阶段(等待数据和拷贝数据)都被阻塞
典型应用阻塞Socket,Java BIO
优 点
  • 进程阻塞挂起不消耗CPU资源,及时响应每个操作
  • 实现难度低,易于开发
  • 适合并发量小的网络应用
缺 点
  • 不适合并发量打的应用,因为一个请求I/O会阻塞进程
  • 需要为每个请求分配一个处理进程(线程)以及及时响应,系统开销大

2.非阻塞式I/O模型

非阻塞I/O通信模型的通信过程如下图所示:
非阻塞I/O通信模型
当用户进程发出read操作时,如果内核中数据还没准备好,它并不会阻塞进程,而是立即返回一个error。从用户角度讲,它在发起read操作后可以立即拿到结果,用户判断结果是error是,它就知道数据没有准备好。于是它可以选择再次发送read操作,一旦内核中数据准备好了,那么在再次收到用户进程的系统调用后,内核会马上将数据拷贝到用户内存,供应用程序消费使用。
总结如下:

特 点用户进程需要不断地注定询问Kernel(内核)数据准备好了没有
典型应用Socket设置NON_BLOCK
优 点实现难度低,开发应用相对阻塞式I/O模型较难
缺 点
  • 进程轮训调用,消耗CPU资源
  • 适合开发量较小且不需要及时响应的网络应用开发

3.多路复用I/O模型

多路复用I/O通信模型的通信过程如下图所示:
在这里插入图片描述
多个进程的I/O可以注册到一个复用器(Selector)上,当用户进程调用该Selector时,Selector会监听注册进来的所有I/O,如果Selector监听的所有I/O在内核缓冲区都没有数据可读,select调度进程会被阻塞,而当任一I/O在内核缓冲区有可读数据时,select调用就会返回,而后select调用进程可以自己或者通知另外的进程(注册进程)再次发起读取I/O,读取内核中准备好的数据。这样多个进程注册I/O后,只有select调用进程会被阻塞

事实上,多路复用模型和阻塞模型并没有太大的不同,严格来说,甚至更差一些。一方面是因为它需要两次系统调用,另外一方面,虽然只有调用select的进程会被阻塞住以外,其他注册的I/O进程,我个人认为多路复用相对阻塞I/O模型来说,仅仅是取消了I/O第一阶段,没有数据准备好时候的阻塞,并把后续拷贝数据包的阻塞延后了而已,而且从某种意义上来说,多路复用在select调用的时候,如果注册在Selector上的进程强依赖该I/O数据,那么这段时间,该进程未阻塞和阻塞并没有太大的区别。因此说,多路复用它解决的最本质问题并不是单个连接能处理的更快,而是可以处理更多的连接。所以在某种情况下,多路复用的模型并不一定比阻塞式的要好。还是需要开发者具体情况具体处理。
总结如下:

特 点对于每一个Socket,一般都设置成非阻塞,但是整个用户进程其实一直是阻塞的,只不过进程不是被Socket I/O阻塞,而是被select调用阻塞的
典型应用Java NIO Nginx(epoll、poll、select)
优 点
  • 专一进程解决多个进程I/O阻塞的问题,性能好,Reactor模式
  • 适合高并发服务应用开发,一个进程/线程响应多个请求
缺 点实现和开发难度比较大

4.信号驱动I/O模型

多路复用I/O通信模型的通信过程如下图所示:
在这里插入图片描述
信号驱动模型是指进程预先告知内核,向内核注册一个信号处理函数,然后用户进程返回不阻塞,当内核数据准备就绪的时候,会发送一个信号给进程,用户进程便在信号处理函数中调用I/O读取数据,实际上,整个I/O过程中,数据从内核拷贝到用户空间阶段还是阻塞的,信号驱动I/O并没有做到真正的异步,因为在数据准备就绪后,还是得由进程来完成剩下的I/O操作
总结如下:

特 点并不属于异步,属于伪异步,实际中不常使用
典型应用应用场景较少
优 点/
缺 点实现和开发难度大

5.异步I/O模型

多路复用I/O通信模型的通信过程如下图所示:
在这里插入图片描述
从用户进程监督来说,在发起aio_read操作后,给内核传递来和read相同的描述符、缓冲区指针(为了接受数据)、缓冲区大小三个参数,以及文件偏移,告诉内核当整个I/O操作完成后,如何通知我们立刻开始去做其他的事情;而从内核角度来说,内核在收到一个aio_read的操作指令后,内核会立即返回,以至于不会阻塞进程,然后内核等待数据准备好,然后内核将数据拷贝的用户内存,完成后给用户进程发送一个信号,告诉用户进程aio_read操作完成(依据之前用户进程传递的参数)

这里需要注意到的是,异步模型和信号驱动模型的不同在于,异步模型中整个I/O操作是需要用户进程做什么的(无论是第一阶段数据等待还是第二阶段数据拷贝),内核帮用户进程做完整个I/O之后,通知用户进程进行数据的消费处理。而信号驱动模型是内核通知到用户进程,数据已经准备好,用户进程可以去内核缓冲区去拷贝数据过来进行消费处理了

总结如下:

特 点真正实现了异步I/O,是五种模型中唯一一个异步模型
典型应用Java7 AIO 高性能服务器应用
优 点
  • 不阻塞,数据一步到位,采用Proactor模式
  • 非常适合高性能高并发应用
缺 点
  • 需要操作系统底层支持,Linux 2.5内核首现,Linux 2.6产品的内核标准特性
  • 实现和开发难度大

三、同步、异步、阻塞、非阻塞

最后,我们来看一下I/O学习中最容易混淆的概念,同步异步、阻塞非阻塞

1 同步VS异步

同步和异步指的是CPU时间片上的利用。主要看请求发起方对消息结果的获取是主动发起的还是被动通知的,

  • 同步:请求方主动发起
  • 异步:请求方被动通知

2 阻塞非阻塞

阻塞非阻塞在计算机里面通常是针对I/O操作来说的,这两个概念的区别在于,在调用了一个函数后,在等待这个函数返回结果之前,当前的进程(线程)是挂起状态还是运行状态。

  • 阻塞:处于挂起状态
  • 非阻塞:处于运行状态

总结

以上就是本人对于IO操作系统底层学习的一个总结,分了三个部分总结,I/O的预热知识、I/O的五种通信模型、
I/O的最易混淆的一些概念。希望对大家能有所帮助,如有问题,欢迎大家雅正或联系我,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值