IO读写原理-程序员宅基地

技术标签: IO  

1. Java IO读写原理

无论是Socket的读写还是文件的读写,在Java层面的应用开发或者是linux系统底层开发,都属于输入input和输出output的处理,简称为IO读写。在原理上和处理流程上,都是一致的。区别在于参数的不同。

用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不同操作系统,名称不完全一样,但是功能是一样的。

先强调一个基础知识:read系统调用,并不是把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理设备。

read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。

2.用户进程缓冲区和内核缓冲区 

缓冲区的目的,是为了减少频繁的系统IO调用。大家都知道,系统调用需要保存之前的进程数据和状态等信息,而结束调用之后回来还需要恢复之前的信息,为了减少这种损耗时间、也损耗性能的系统调用,于是出现了缓冲区。

有了缓冲区,操作系统使用read函数把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区中。等待缓冲区达到一定数量的时候,再进行IO的调用,提升性能。至于什么时候读取和存储则由内核来决定,用户程序不需要关心。

在linux系统中,系统内核也有个缓冲区叫做内核缓冲区。每个进程有自己独立的缓冲区,叫做进程缓冲区。

所以,用户程序的IO读写程序,大多数情况下,并没有进行实际的IO操作,而是在读写自己的进程缓冲区。

这是一个计算机系统运行时的简化模型,我们把所有运行在操作系统上的进程成为用户进程,它们都运行在用户空间(可以看到用户空间有很多进程)。把操作系统运行的空间成为系统空间

为什么将进程分为用户进程和系统进程,首先你一定听说过内核态和用户态(kernel mode和user mode),在内核态可以访问系统资源,比如:

处理器cpu:cpu控制着一个程序的执行。

输入输出IO:linux有句话叫“一切都是流”,也就是所有输入输出设备的数据,包括硬盘,内存,终端都可以像流一样操作。

进程管理:类似对进程的创建,休眠,唤醒,释放之类的调度。比如linux下的fork和windows下的CreateProcess()函数。

内存:包括内存的申请,释放等管理操作。

设备:这个就是常常说的外设了,比如鼠标,键盘。

计时器:计算机能计时是因为晶体振荡器产生的电磁脉冲。那么所有的定时任务都是以它为基础的。

进程间通信IPC:进程之间是不能够互相访问内存的,所以进程与进程之间的交互需要通信,而通信也是一种资源。

网络通信:网络通信可以看做是进程见通信的特殊形式。

  而上面所说的这些系统资源,在用户进程中是无法被直接访问的,只能通过操作系统来访问,所以也把操作系统提供的这些功能成为:“系统调用”。

展示一个用户通过shell控制计算机所经过的数据流向:文件读写和终端控制,都是通过内核进行的。

2.1用户进程缓存区

前面提到,用户进程通过系统调用访问系统资源的时候,需要切换到内核态,而这对应一些特殊的堆栈和内存环境,必须在系统调用前建立好。而在系统调用结束后,cpu会从内核模式切回到用户模式,而堆栈又必须恢复成用户进程的上下文。而这种切换就会有大量的耗时。

你看一些程序在读取文件时,会先申请一块内存数组,称为buffer,然后每次调用read,读取设定字节长度的数据,写入buffer。(用较小的次数填满buffer)。之后的程序都是从buffer中获取数据,当buffer使用完后,在进行下一次调用,填充buffer。所以说:用户缓冲区的目的是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。除了在进程中设计缓冲区,内核也有自己的缓冲区。

2.2内核缓存区

当一个用户进程要从磁盘读取数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程缓冲区中。

但若是内核缓冲区中没有数据,内核会把对数据块的请求,加入到请求队列,然后把进程挂起,为其它进程提供服务。

等到数据已经读取到内核缓冲区时,把内核缓冲区中的数据读取到用户进程中,才会通知进程,当然不同的io模型,在调度和使用内核缓冲区的方式上有所不同,下一小结介绍。

你可以认为,read是把数据从内核缓冲区复制到进程缓冲区。write是把进程缓冲区复制到内核缓冲区。当然,write并不一定导致内核的写动作,比如os可能会把内核缓冲区的数据积累到一定量后,再一次写入。这也就是为什么断电有时会导致数据丢失。所以说内核缓冲区,是为了在OS级别,提高磁盘IO效率,优化磁盘写操作。

2.3java IO读写的底层流程实例

首先看看一个典型Java 服务端处理网络请求的典型过程:

(1)客户端请求

Linux通过网卡,读取客户断的请求数据,将数据读取到内核缓冲区。

(2)获取请求数据

服务器从内核缓冲区读取数据到Java进程缓冲区。

(1)服务器端业务处理

Java服务端在自己的用户空间中,处理客户端的请求。

(2)服务器端返回数据

Java服务端已构建好的响应,从用户缓冲区写入系统缓冲区。

(3)发送给客户端

Linux内核通过网络 I/O ,将内核缓冲区中的数据,写入网卡,网卡通过底层的通讯协议,会将数据发送给目标客户端。

3. 四种主要的IO模型

 

服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

(1)同步阻塞IO(Blocking IO)

首先,解释一下这里的阻塞与非阻塞:

阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间,执行用户的操作。阻塞指的是用户空间程序的执行状态,用户空间程序需等到IO操作彻底完成。传统的IO模型都是同步阻塞IO。在java中,默认创建的socket都是阻塞的。

其次,解释一下同步与异步:

同步IO,是一种用户空间与内核空间的调用发起方式。同步IO是指用户空间线程是主动发起IO请求的一方,内核空间是被动接受方。异步IO则反过来,是指内核kernel是主动发起IO请求的一方,用户线程是被动接受方。

(4)同步非阻塞IO(Non-blocking IO)

非阻塞IO,指的是用户程序不需要等待内核IO操作完成后,内核立即返回给用户一个状态值,用户空间无需等到内核的IO操作彻底完成,可以立即返回用户空间,执行用户的操作,处于非阻塞的状态。

简单的说:阻塞是指用户空间(调用线程)一直在等待,而且别的事情什么都不做;非阻塞是指用户空间(调用线程)拿到状态就返回,IO操作可以干就干,不可以干,就去干的事情。

非阻塞IO要求socket被设置为NONBLOCK。

强调一下,这里所说的NIO(同步非阻塞IO)模型,并非Java的NIO(New IO)库。

(3)IO多路复用(IO Multiplexing)

即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

(5)异步IO(Asynchronous IO)

异步IO,指的是用户空间与内核空间的调用方式反过来。用户空间线程是变成被动接受的,内核空间是主动调用者。

这一点,有点类似于Java中比较典型的模式是回调模式,用户空间线程向内核空间注册各种IO事件的回调函数,由内核去主动调用。

3.1 同步阻塞IO(Blocking IO)

在linux中的Java进程中,默认情况下所有的socket都是blocking IO。在阻塞式 I/O 模型中,应用程序在从IO系统调用开始,一直到到系统调用返回,这段时间是阻塞的。返回成功后,应用进程开始处理用户空间的缓存数据。

 

举个例子,发起一个blocking socket的read读操作系统调用,流程大概是这样:

  1. 当用户线程调用了read系统调用,内核(kernel)就开始了IO的第一个阶段:准备数据。很多时候,数据在一开始还没有到达(比如,还没有收到一个完整的Socket数据包),这个时候kernel就要等待足够的数据到来。
  2. 当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。
  3. 从开始IO读的read系统调用开始,用户线程就进入阻塞状态。一直到kernel返回结果后,用户线程才解除block的状态,重新运行起来。

所以,blocking IO的特点就是在内核进行IO执行的两个阶段,用户线程都被block了。

BIO的优点:

程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。

BIO的缺点:

一般情况下,会为每个连接配套一条独立的线程,或者说一条线程维护一个连接成功的IO流的读写。在并发量小的情况下,这个没有什么问题。但是,当在高并发的场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上,BIO模型在高并发场景下是不可用的。

3.2.同步非阻塞NIO(Non Blocking IO)

 

在linux系统下,可以通过设置socket使其变为non-blocking。NIO 模型中应用程序在一旦开始IO系统调用,会出现以下两种情况:

  1. 在内核缓冲区没有数据的情况下,系统调用会立即返回,返回一个调用失败的信息。
  2. 在内核缓冲区有数据的情况下,是阻塞的,直到数据从内核缓冲复制到用户进程缓冲。复制完成后,系统调用返回成功,应用进程开始处理用户空间的缓存数据。

举个例子。发起一个non-blocking socket的read读操作系统调用,流程是这个样子:

  1. 在内核数据没有准备好的阶段,用户线程发起IO请求时,立即返回。用户线程需要不断地发起IO系统调用。
  2. 内核数据到达后,用户线程发起系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。
  3. 用户线程才解除block的状态,重新运行起来。经过多次的尝试,用户线程终于真正读取到数据,继续执行。

NIO的特点:

应用程序的线程需要不断的进行 I/O 系统调用,轮询数据是否已经准备好,如果没有准备好,继续轮询,直到完成系统调用为止。

NIO的优点:每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。

NIO的缺点:需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。

总之,NIO模型在高并发场景下,也是不可用的。一般 Web 服务器不使用这种 IO 模型。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。java的实际开发中,也不会涉及这种IO模型。

再次说明,Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )。

3.3 IO多路复用模型(I/O multiplexing)

如何避免同步非阻塞NIO模型中轮询等待的问题呢?这就是IO多路复用模型。

IO多路复用模型,就是通过一种新的系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核kernel能够通知程序进行相应的IO系统调用。

目前支持IO多路复用的系统调用,有 select,epoll等等。select系统调用,是目前几乎在所有的操作系统上都有支持,具有良好跨平台特性。epoll是在linux 2.6内核中提出的,是select系统调用的linux增强版本。

IO多路复用模型的基本原理就是select/epoll系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。因此,好处也就显而易见了——通过一次select/epoll系统调用,就查询到到可以读写的一个甚至是成百上千的网络连接。

举个例子。发起一个多路复用IO的的read读操作系统调用,流程是这个样子:

在这种模式中,首先不是进行read系统调动,而是进行select/epoll系统调用。当然,这里有一个前提,需要将目标网络连接,提前注册到select/epoll的可查询socket列表中。然后,才可以开启整个的IO多路复用模型的读流程。

  1. 进行select/epoll系统调用,查询可以读的连接。kernel会查询所有select的可查询socket列表,当任何一个socket中的数据准备好了,select就会返回。当用户进程调用了select,那么整个线程会被block(阻塞掉)。
  2. 用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。
  3. 用户线程才解除block的状态,用户线程终于真正读取到数据,继续执行。

多路复用IO的特点:

IO多路复用模型,建立在操作系统kernel内核能够提供的多路分离系统调用select/epoll基础之上的。多路复用IO需要用到两个系统调用(system call), 一个select/epoll查询调用,一个是IO的读取调用。

和NIO模型相似,多路复用IO需要轮询。负责select/epoll查询调用的线程,需要不断的进行select/epoll轮询,查找出可以进行IO操作的连接。

另外,多路复用IO模型与前面的NIO模型,是有关系的。对于每一个可以查询的socket,一般都设置成为non-blocking模型。只是这一点,对于用户程序是透明的(不感知)。

多路复用IO的优点:

用select/epoll的优势在于,它可以同时处理成千上万个连接(connection)。与一条线程维护一个连接相比,I/O多路复用技术的最大优势是:系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。

Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。

多路复用IO的缺点:

本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都需要在读写事件就绪后,自己负责进行读写,也就是说这个读写过程是阻塞的。

如何充分的解除线程的阻塞呢?那就是异步IO模型。

3.4异步IO模型(asynchronous IO)

如何进一步提升效率,解除最后一点阻塞呢?这就是异步IO模型,全称asynchronous I/O,简称为AIO。

AIO的基本流程是:用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。

kernel的数据准备是将数据从网络物理设备(网卡)读取到内核缓冲区;kernel的数据复制是将数据从内核缓冲区拷贝到用户程序空间的缓冲区。

  1. 当用户线程调用了read系统调用,立刻就可以开始去做其它的事,用户线程不阻塞。
  2. 内核(kernel)就开始了IO的第一个阶段:准备数据。当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存)。
  3. kernel会给用户线程发送一个信号(signal),或者回调用户线程注册的回调接口,告诉用户线程read操作完成了。
  4. 用户线程读取用户缓冲区的数据,完成后续的业务操作。

异步IO模型的特点:

在内核kernel的等待数据和复制数据的两个阶段,用户线程都不是block(阻塞)的。用户线程需要接受kernel的IO操作完成的事件,或者说注册IO操作完成的回调函数,到操作系统的内核。所以说,异步IO有的时候,也叫做信号驱动 IO 。

异步IO模型缺点:

需要完成事件的注册与传递,这里边需要底层操作系统提供大量的支持,去做大量的工作。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36685916/article/details/91171725

智能推荐

阿里云服务器镜像Linux操作系统选哪个比较好?_阿里云linux选择什么系统-程序员宅基地

文章浏览阅读2.9k次。阿里云服务器镜像Linux系统怎么选择?阿里云Linux服务器可选镜像CentOS、Ubuntu、Alibaba Cloud Linux、Anolis OS等Linux镜像,阿里云ASK来详细说下阿里云服务器ECS系统Linux镜像选择方法:阿里云服务器Linux系统镜像选择阿里云服务器ECS系统支持很多Linux镜像,如Alibaba Cloud Linux、Anolis OS、CentOS、SUSE Linux、Ubuntu、Debian、Fedora、 Fedora CoreOS、CoreOS_阿里云linux选择什么系统

【解决oracle乱码问题】_vb 调用orcal视图乱码-程序员宅基地

文章浏览阅读6.9k次。参考:https://www.cnblogs.com/terryMe/p/6725120.html解决乱码问题解决乱码问题需要关注的三点:(1)Oracle数据库内部的字符集(2)Oracle客户端应用所在环境的字符集(3)Oracle数据库所在服务器的系统中NLS_LANG变量里保存的字符集(最好与前两点保持一致)解决方法:步骤一:查询Oracle数据库所在服务器的系统中NLS_LANG变量里保存的字符集方式一:select * from v$nls_parameters;查询结果如下_vb 调用orcal视图乱码

AG-Admin环境搭建部署及启动前端_前端agentadmin-程序员宅基地

文章浏览阅读4k次。源码地址https://gitee.com/geek_qi/ace-security/tree/v2.2/AG-Admin环境搭建:搭建步骤参考: http://blog.csdn.net/u011781521/article/details/79056974启动顺序按顺序运行main类:CenterBootstrap(ace-center)、ConfigServer..._前端agentadmin

vulhub靶场搭建及漏洞复现教程-程序员宅基地

文章浏览阅读1.8w次,点赞17次,收藏147次。准备一个纯净的ubantu系统1、先更新一下安装列表sudo apt-get update2、安装docker.iosudo apt install docker.io查看是否安装成功docker -v3、查看是否安装pippip -V检测到未安装,提示是否安装,按y下载再次输入pip -V查看是否安装成功4、安装docker-composepip install docker-compose查看是否安装成功docker_vulhub

全新好用的窗口置顶工具WindowTop-程序员宅基地

文章浏览阅读3.5k次。全局快捷键功能可以配置窗口置顶,透明,穿透,画中画,截图等功能,而鼠标快捷键操作可以配置在对应窗口连续点击执行置顶窗口,窗口移动,最大化,最小化等操作。打开WindowTop软件,所有已打开的窗口都会在左上角出现一个置顶栏,点击置顶栏的置顶复选框即可置顶窗口或取消窗口。输入法提示功能可以在置顶栏实时显示输入法的状态,又或者以独立的小窗口显示。屏幕截图,窗口截图,支持自由编辑画图,并能将截图以贴图的方式贴在屏幕上。可改变置顶栏的外观,还可以自由拖动置顶栏到想要的位置。_windowtop

felx的使用_felx: 0 0 0-程序员宅基地

文章浏览阅读246次。flex的使用Flex 布局是什么?Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局。.box{ display: flex;}行内元素也可以使用 Flex 布局。.box{ display: inline-flex;}Webkit 内核的浏览器,必须加上-webkit前缀。.box{ display: -webkit-flex; /* Safari */ display: f_felx: 0 0 0

随便推点

【c++】寻找四位数的 “数字黑洞”_# i. 数字黑洞 时间限制: c/c++ 1s 空间限制: c/c++ 32mb 题目描述 任意一-程序员宅基地

文章浏览阅读4k次,点赞22次,收藏7次。寻找四位数的 “数字黑洞”数字黑洞“数学黑洞”:任意一个4位自然数,将组成该数的各位数字重新排列,形成一个最大数和一个最小数,之后两数相减,其差仍为一个自然数。重复进行上述运算,会发现一个神秘的数。描述输入:一个4位的自然数。输出:从该4位自然数开始,每次按题意变换后的自然数,直到数学黑洞为止。案例输入样例 :7700输出样例 :7700762352653996626441766174c++代码如下#include <iostream>using _# i. 数字黑洞 时间限制: c/c++ 1s 空间限制: c/c++ 32mb 题目描述 任意一个四位

【转】Ubuntu 12.04 安装JDK 8和Eclipse-程序员宅基地

文章浏览阅读52次。原文网址:http://blog.csdn.net/yechaodechuntian/article/details/24853813Ubuntu 12.04 下安装 JDK8方法一:(缺点是安装时附加openjdk等大量程序并无法去除,优点是安装简单)$ sudo apt-get install eclipse方法二:(优点是安装内容清爽,缺点是配置麻烦)1、安装JDK,参...

写给想当程序员的朋友-程序员宅基地

文章浏览阅读241次。谨以此文献给所有想当程序员的朋友      (一) 文章由来及个人经历      我是一名计算机专业的本科毕业生,毕业已经1年多了。毕业后从事的是软件编程工作,经常有其他专业的朋友想从事软件编程工作,向我请教如何,因为我自觉涉行不深,不敢信口开河,无奈朋友信任,我不得不郑重考虑一下这个问题了,来帮助朋友选择和回报朋友的信任。      这也就是此文的由来。      还是先谈谈我个..._想当程序员

七牛云存储之Base64图片上传_七牛云上传base64图片-程序员宅基地

文章浏览阅读5.5k次。概述 实际开发中,经常会遇到需要将图片转换为base64编码后的字符串进行处理,所以七牛云存储也提供了该接口的,支持base64图片上传。思路:获取七牛提供的Auth对象,为获取token指定需要上传的空间(bucket)和 文件存储在空间中的名词(key) 获取一个本地图片,并将其转换为base64字符串通过http请求七牛提供的接口 put64查看结果代码示例:package c_七牛云上传base64图片

android 安装界面关闭程序,关于Android app首次安装完成后在安装界面直接“打开”应用再按home键返回桌面,重新进入app重复实例化launcher activity的问题的解决...-程序员宅基地

文章浏览阅读299次。一、问题描述如标题所述,最近被重复实例化launcher activity这个问题搞得很惨,这个问题有哪些表现呢?如下:在package installers 安装界面安装完一个应用后,直接打开app,然后进入了 Activity_1, 此时再通过此activity用startActivity(intent)的方法打开 Activity_2.然后按home键返回桌面,在桌面点击app图标进入,你觉..._安卓安装应用正在安装后关闭

HTML---今天学了有序列表<ol>和无序列表<ul>,自我总结一下_无序列表和有序列表的标签分别是-程序员宅基地

文章浏览阅读245次。分享有序列表和无序列表的使用和常用属性_无序列表和有序列表的标签分别是