I/O模型

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

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

用户空间和内核空间

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

1、用户空间

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

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的解读,我们来看一下同步和异步、阻塞和非阻塞两组概念的区别,主要二者在关注点上有所不同。

1、同步和异步

同步和异步这个概念比较广,不仅仅是在I/O,其他的还有诸如同步调用/异步调用、同步请求/异步请求,都是一个意思。同步和异步,关注的是消息通信机制。

所谓同步,就是在发出一个"调用请求"时,在没有得到结果之前,该"调用请求"就不返回,但是一旦调用返回就得到返回值了。换句话说,就是由"调用者"主动等待"调用"的结果。像我们平时写的,方法A调用Math.random()方法、方法B调用String.substring()方法都是同步调用,因为调用者主动在等待这些方法的返回。

所谓异步,则正好相反,"调用"发出之后,这个调用就直接返回了,所有没有返回结果。换句话说,当一个异步调用请求发出之后,调用者不会立刻得到结果,因此异步调用适用于那些对数据一致性要求不是很高的场景,比如模块A更新了缓存中的某个值,模块B将某个内容分享到新浪微博,这些模块的关注点更多是"做了这件事"而不是"做了这件事是否马上成功",用分布式的话说,就是牺牲了系统的强一致性而提高了整个系统的可用性及分区容错性。如果这种场景下,我们希望获取异步调用的结果,"被调用者"可以通过状态、通知来通知调用者,或通过回调函数处理这个调用,对应Java中的有Future/FutureTask、wait/notify。

2、阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果时的状态。

阻塞调用指的是调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。

非阻塞调用指的是在不能立即得到结果之前,该调用不会阻塞当前线程。

Linux网络I/O模型

由于绝大多数的Java应用都部署在Linux系统上,因此这里谈一下Linux网络I/O模型。

Linux的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个file descriptor(fd,文件描述符)。而对一个Socket的读写也会有相应的描述符,称为Socketfd(Socket描述符),描述符就是一个数字,它指向内核中的一个结构体(结构体,C/C++数据类型,类似Java中的类,存储各种不同类型的数据,这里存储的是文件路径、数据区等一些属性)。

根据UNIX网络编程对I/O模型的分类,UNIX提供了5种I/O模型,分别为:

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就绪时,立即会掉函数rollback。

4、信号驱动I/O模型

首先开启Socket信号驱动I/O功能,并通过系统调用sigaction执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为进程生成一个SIGIO信号,通过信号会掉通知应用程序调用recvfrom来读取数据,并通知主循环函数来处理数据。

5、异步I/O

告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知开发者。这种模型与信号驱动I/O模型的主要区别是:信号驱动I/O模型由内核通知开发者何时可以开始一个I/O操作,异步I/O模型由内核通知开发者I/O操作何时已经完成。

Java NIO1:I/O模型概述的更多相关文章

  1. I/O模型之四:Java 浅析I/O模型(BIO、NIO、AIO、Reactor、Proactor)

    目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...

  2. CSS 盒子模型概述

    一.简介   CSS 盒子模型(元素框)由元素内容(content).内边距(padding).边框(border).外边距(margin)组成.     盒子模型,最里面的部分是实际内容:直接包围内 ...

  3. 关于JAVA中的static方法、并发问题以及JAVA运行时内存模型

    一.前言 最近在工作上用到了一个静态方法,跟同事交流的时候,被一个问题给问倒了,只怪基础不扎实... 问题大致是这样的,“在多线程环境下,静态方法中的局部变量会不会被其它线程给污染掉?”: 我当时的想 ...

  4. CSS框模型(框模型概述、内边距、边框、外边距、外边距合并)

    CSS 框模型概述 CSS 框模型 (Box Model) 规定了元素框处理元素内容.内边距.边框 和 外边距 的方式. 元素框的最内部分是实际的内容,直接包围内容的是内边距.内边距呈现了元素的背景. ...

  5. 权限系统与RBAC模型概述

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/SJQ. http://www.cnblogs.com/shijiaqi1066/p/3793894.html ...

  6. Java对象的内存模型(一)

    前言 新人一枚,刚刚入门编程不久,各方面都在学习当中,博文有什么错误的地方,希望我们可以多多交流! 最近,在开发App后台过程中,需要将项目部署到云服务器上.而云服务器的内存大小却只有1G.要如何做到 ...

  7. Java虚拟机:内存模型详解

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 我们都知道,当虚拟机执行Java代码的时候,首先要把字节码文件加载到内存,那么这些类的信息都存放在内存中的哪个区域呢?当我们创建一个对象实 ...

  8. Java并发(一、概述)

    离上次写博客又隔了很久,心中有愧.在我不断使用Java的过程中,几乎都是拿来就用,就Java并发这块我还没有系统的梳理过,趁着国庆有空余时间,把它梳理一遍.以下部分内容参考相关书籍,以作学习之用,特此 ...

  9. Java多线程与并发模型之锁

    这是一篇总结Java多线程开发的长文.文章是从Java创建之初就存在的synchronized关键字引入,对Java多线程和并发模型进行了探讨.希望通过此篇内容的解读能帮助Java开发者更好的理清Ja ...

  10. 权限系统与RBAC模型概述[绝对经典]

    0. 前言 一年前,我负责的一个项目中需要权限管理.当时凭着自己的逻辑设计出了一套权限管理模型,基本原理与RBAC非常相似,只是过于简陋.当时google了一些权限管理的资料,从中了解到早就有了RBA ...

随机推荐

  1. Codeigniter基础

    一.去除index.php 新建.htaccess: RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUES ...

  2. oracle undo回滚段详解

    1.Undo是干嘛用的?   在介绍undo之前先说一下另外一个东西 transaction ,翻译成交易或事务.我们在进行一个事务的过程中需要申请许多资源,一个复杂的事务也需要很多步来完成.那么一个 ...

  3. CSS 两列布局 之 左侧适应,右侧固定 3种方式

    第一种:左侧用margin-right,右侧float:right CSS代码: html, body,ul,li #wrapper { width: 100%; height: 100%; padd ...

  4. CozyRSS开发记录15-获取和显示RSS内容

    CozyRSS开发记录15-获取和显示RSS内容 1.内容列表 我们先给RSSContentFrame增加一个ViewModel,里面和RSS源列表一样,提供一个ObservableCollectio ...

  5. (UWP开发)更为合理的一种ListView下拉刷新(PullToRefresh)实现方法

    最近在做的一个项目需要用到下拉刷新,但是参考了现在网络上比较普遍的方法,觉得都不太好,因为要在外部套上一个SrollViewer,容易出现滚动错误.于是刚开始的时候就把思路定到了ListView内部的 ...

  6. spring 定时任务配置

    1.(易)如何在spring中配置定时任务? spring的定时任务配置分为三个步骤: 1.定义任务 2.任务执行策略配置 3.启动任务 (程序中一般我们都是到过写的,直观些) 1.定义任务 < ...

  7. 【NEUQACM OJ】1017: 平面切割(特别版)

    1017: 平面切割(特别版) 题目描述 我们要求的是n条闪电型折线分割平面的最大数目.比如,一条闪电型折线可以将平面分成两部分,两条最多可以将平面分成12部分,三条最多可将平面分成31部分,四条最多 ...

  8. 第一章 DeepLab的创作动机

    这一段时间一直在做深度学习方面的研究,目前市场上的深度学习工具主要分为两大块.一块是基于Python语言的theano:另一块是可以在多个语言上使用并能够在GPU和CPU之间随意切换的Caffe.但是 ...

  9. 【内核篇】Windows内核重要变量

    ====================================================== LIST_ENTRY PsLoadedModuleList; [定  义] wrk\wrk ...

  10. MySQL慢查询日志总结

    慢查询日志概念 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志 ...