Java NIO学习系列五:I/O模型

  前面总结了很多IO、NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型。什么是IO模型?对于不同人、在不同场景下给出的答案是不同的,所以先限定一下本文的上下文:Linux环境下的network IO。

  本文会从如下几个方面展开:

  一些基础概念

  I/O模型

  总结

1. 一些基础概念

  IO模型这个概念属于比较基础的底层概念,在此之前容我再先简单介绍一些涉及到的更底层的概念,帮助对I/O模型的理解:

1.1 用户空间与内核空间

  现在操作系统都是采用虚拟存储器,对于32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

1.2 文件描述符

  对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

1.3 缓存 I/O

  缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都属于缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存(page cache)中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

  因为数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,所以这些数据拷贝操作所带来的CPU以及内存开销是非常大的,这也是缓存I/O所带来的缺点。

2. I/O模型

  上面有提到,对于一次IO访问(比如read),数据会先被复制到操作系统内核缓冲区中,然后再从操作系统内核的缓冲区复制到应用程序的地址空间。也就是说,当一个IO操作发生时,会经历两个阶段:

  1. 数据准备阶段(Waiting for the data to be ready);
  2. 将数据从内核复制到进程中(Copying the data from the kernel to the process);

  这是因为存在上面两个阶段,linux系统产生了下面5种I/O模型:

  • 阻塞I/O(blocking IO)
  • 非阻塞I/O(nonblocking IO)
  • I/O多路复用(IO multiplexing)
  • 信号驱动I/O(signal driven IO)
  • 异步I/O(asynchronous IO)

  下面我们来一一介绍(本文暂介绍除信号驱动I/O外其余4种IO模型)。

2.1 阻塞I/O(blocking IO)

  在linux中,默认情况下socket都是阻塞式的,一个典型的读操作流程大概是这样的:

  当用户进程调用recvfrom这个系统调用时,kernel就开始了IO的第一个阶段:数准备阶段(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的TCP包。这个时候kernel就要等待直到所有数据到达),这个过程相当于将数据被复制到操作系统内核的缓冲区,是需要一个过程,需要等待的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中复制到用户内存,然后kernel返回结果,用户进程才会解除block的状态,重新运行起来。

  Blocking IO最大的特点就是在IO执行的两个阶段都被会阻塞。

2.2 非阻塞I/O(nonblocking IO)

  可以将设置socket设置为non-blocking模式,对于此时的读操作,流程大概是这个样子的:

  当用户进程发起recvfrom这个系统调用时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个recvfrom调用,马上就得到了一个结果,不管有没有数据。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发起recvfrom操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它接下来就将数据复制到用户内存,然后返回。

  Nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有,这个过程不阻塞,但是在IO的第二个阶段还是需要等待内核将数据复制到用户进程的,也就是这部分还是会阻塞的。

2.3 I/O多路复用(IO multiplexing)

  IO multiplexing就是我们说的IO多路复用,有些地方也称这种IO方式为event driven IO。主要是通过select/epoll来实现单个process同时处理多个网络连接的IO,它的基本原理是依赖select,poll,epoll这些function来不断的轮询负责的所有socket,当某个socket有数据到达了,就通知用户进程。

  当用户进程调用了select时整个进程会被block,同时kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel复制到用户进程。

  所以,I/O多路复用的特点是通过一种机制使得一个进程能同时等待多个文件描述符(至于为什么是文件描述符,因为对于kernel而言,所有打开文件都由文件描述符引用,而一次IO连接也相当于打开文件),而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

  IO多路复用是建立在Nonblocking IO之上的,即通过单个线程来“监视”多个IO连接,谁准备就绪了就处理谁,处理的过程和Nonblocking的过程是一样的。

2.4 异步I/O(asynchronous IO)

  Asynchronous IO不同于上面三种模型,先看一下它的流程:

  用户进程发起read操作之后,立刻就可以开始去做其它的事情了。另一方面,从kernel的角度,当它收到一个asynchronous read调用之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,并且数据准备好之后再将数据复制到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

  其实,从这里可以看出异步I/O最大的特点就是,将前面提到的两步IO操作全部异步化了,整个过程完全不阻塞,而Nonblocking IO在复制数据到用户进程这一步其实还是阻塞的,只是数据准备阶段不阻塞而已。

3. 总结

  本文总结了linux网络IO中常见的五种IO模型,虽说不是Java IO,但是Java IO、NIO中也是遵循同样的IO模型,关于这一点,后面会专门写一篇文章来阐述。

  五种IO模型分别为:阻塞I/O(blocking IO)、非阻塞I/O(nonblocking IO)、I/O多路复用(IO multiplexing)、信号驱动I/O(signal driven IO)、异步I/O(asynchronous IO)。在这五种模型的基础上,有两个概念不得不提,阻塞和非阻塞、同步和异步,这两个概念容易搞混,:

3.1 阻塞和非阻塞

  阻塞很好理解,就是进程或者线程停在那里等待某个状态,什么都不干。但是什么情况称为阻塞,什么情况称为非阻塞呢?在IO模型中的定义是取决于前面提到的第一阶段:数据准备阶段,也就是说,在这个阶段会导致进程或线程阻塞的IO就成为阻塞式IO,反之就是非阻塞式IO。所以Nonblocking IO在第一阶段是可以做别的事情的,但是在第二阶段任然是阻塞的,这点需要注意。

3.2 同步和异步

  在说明同步IO模型和异步IO模型的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
  • An asynchronous I/O operation does not cause the requesting process to be blocked;

  两者的区别就在于执行"IO Operation"的时候是否会导致进程或线程阻塞,这个"IO Operation"是指前面提到的第二阶段:将数据从kernel复制到用户进程中。按照这个定义,前面说的Blocking IO、Non-blocking IO、IO multiplexing就都属于synchronous IO,只有异步IO才属于Asynchronous IO,因为只有它在整个IO过程中都不会导致阻塞。

  最后再附上一张五种IO模型比较图,以帮助理解:

参考文献

Linux IO模式及 select、poll、epoll详解

原文地址:https://www.cnblogs.com/volcano-liu/p/11001746.html

时间: 2024-10-03 23:29:12

Java NIO学习系列五:I/O模型的相关文章

java nio学习(五)

通道之间的数据传输 在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel. transferFrom() FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中).下面是一个简单的例子: RandomAccessFile fromFile

从底层入手,图解 Java NIO BIO MIO AIO 四大IO模型与原理

目录 写在前面 1.1. Java IO读写原理 1.1.1. 内核缓冲与进程缓冲区 1.1.2. java IO读写的底层流程 1.2. 四种主要的IO模型 1.3. 同步阻塞IO(Blocking IO) 1.4. 同步非阻塞NIO(None Blocking IO) 1.5. IO多路复用模型(I/O multiplexing) 1.6. 异步IO模型(asynchronous IO) 小结一下: 写在最后 疯狂创客圈 百万级流量 高并发实战 疯狂创客圈 Java 分布式聊天室[ 亿级流量

Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这部分内容需要以下Jar包支持 mysql-connector:MySQL数据库连接驱动,架起服务端与数据库沟通的桥梁: MyBatis:一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架: log4j:Apache的开源项目,一个功能强大的日志组件,提供方便的日志记录: 修改后的pom.xm

java nio学习笔记(一)

位置保留,待用 java nio学习笔记(一),布布扣,bubuko.com

Java命令学习系列(7):Javap(转)

原文出处: Hollis(@Hollis_Chuang) javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码. 一般情况下,很少有人使用javap对class文件进行反编译,因为有很多成熟的反编译工具可以使用,比如jad.但是,javap还可以查看java编译器为我们生成的字节码.通过它,可以对照源代码和字节码,从而了解很多编译器内部的工作. 实例 javap命令分解一个class文件,它根据options来决定到底输出什么.如果没有使用options,那么

深入Java集合学习系列:HashMap的实现原理

参考文献 引用文献:深入Java集合学习系列:HashMap的实现原理,大部分参考这篇博客,只对其中进行稍微修改 自己曾经写过的:Hashmap实现原理 1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的).此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 2. HashMap的数据结构: 在ja

Linux运维系统工程师与java基础学习系列-8

Java天生骄傲系列-8 函数的应用(重点掌握) 如何定义函数 例1: package test.myeclipse;                 publicclass test1 { publicstaticvoid main(String[]args) { int Sum = getSum(); System.out.println("Sum="+Sum); } publicstaticint getSum() { return 3+4; } } 运行结果:Sum=7 例2:

Linux运维系统工程师与java基础学习系列-4

Java天生骄傲系列-4 程序流程控制 判断 选择 循环 判断结构: If语句三种格式: 1.  if(条件表达式) { 执行语句: } 2.  if(条件表达式) { 执行语句: } else { 执行语句: } 3.  if(条件表达式) { 执行语句: } else if (条件表达式) { 执行语句: } --. else { 执行语句: } if(条件表达式) { 执行语句: } 牛刀小试: package test.myeclipse; publicclass test1 { pub

深入Java集合学习系列:LinkedHashMap的实现原理

1. LinkedHashMap概述: LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变.   LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表.此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序.   注意,此实现不是同步的.如果多个线程同时访问链接的哈希映射,而其中至少一个线