首页 > 基础资料 博客日记
详解Java中的五种IO模型
2024-05-14 18:00:06基础资料围观206次
文章目录
前言
- 在学习IO模型前,需要先了解些基础概念,才能理解IO的执行流程及阻塞的原因
1、内核空间和用户空间
我们电脑上跑着的应用程序,其实是需要经过操作系统,才能做一些特殊操作,如磁盘文件读写、内存的读写
等等。因为这些都是比较危险的操作
,不可以由应用程序乱来,只能交给底层操作系统
来。
因此,操作系统为每个进程
都分配了内存空间,一部分是用户空间
,一部分是内核空间
。内核空间是操作系统内核访问的区域,是受保护
的内存空间,而用户空间是用户应用程序访问的内存区域。
- 内核空间:主要提供进程调度、内存分配、连接硬件资源等功能
- 用户空间:提供给各个程序进程的空间,它不具有访问内核空间资源的权限
- 如果应用程序需要使用到内核空间的资源,则需要通过系统调用来完成
- 进程从用户空间切换到内核空间,完成相关操作后,再从内核空间切换回用户空间
我们应用程序是跑在用户空间
的,它不存在实质的IO过程,真正的IO是在操作系统
执行的。即应用程序的IO操作分为两种动作:IO调用和IO执行。
IO调用是由进程(应用程序的运行态)发起,而IO执行是操作系统内核的工作。此时所说的IO是应用程序对操作系统IO功能的一次触发,即IO调用。
2、用户态和内核态
- 如果进程运行于
内核空间
,被称为进程的内核态 - 如果进程运行于
用户空间
,被称为进程的用户态
3、上下文切换
CPU寄存器,是CPU内置的容量小、但速度极快的内存
。而程序计数器,则是用来存储 CPU正在执行的指令位置
、或者即将执行的下一条指令位置。它们都是CPU在运行任何任务前,必须的依赖环境,因此叫做CPU上下文。
CPU上下文切换就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
一般我们说的上下文切换,就是指内核(操作系统的核心)在CPU上对进程或者线程进行切换。进程从用户态到内核态的转变,需要通过系统调用来完成。
系统调用的过程,会发生CPU上下文的切换。
4、虚拟内存
- 现代操作系统使用虚拟内存,即虚拟地址取代物理地址,使用虚拟内存可以有2个好处:
- 虚拟内存空间可以远远大于物理内存空间
- 多个虚拟内存可以指向同一个物理地址
正是多个虚拟内存可以指向同一个物理地址
,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样的话,就可以减少IO的数据拷贝次数啦。
5、DMA技术
DMA,英文全称是Direct Memory Access,即直接内存访问。DMA本质上是一块主板上独立的芯片,允许外设设备和内存存储器之间直接进行IO数据传输,其过程不需要CPU的参与
。
- 用户应用进程调用read函数,向操作系统发起IO调用,进入阻塞状态,等待数据返回
- CPU收到指令后,对DMA控制器发起指令调度
- DMA收到IO请求后,将请求发送给磁盘
- 磁盘将数据放入磁盘控制缓冲区,并通知DMA
- DMA将数据从
磁盘控制器缓冲区
拷贝到内核缓冲区
- DMA向CPU发出数据读完的信号,把工作交换给CPU,由CPU负责将数据从
内核缓冲区
拷贝到用户缓冲区
- 用户应用进程由内核态切换回用户态,解除阻塞状态
DMA的主要作用就是将数据从磁盘拷贝到内核缓冲区,这期间可以解放CUP去做其他事。
6、传统 IO 的执行流程
- 传统的IO流程,包括read和write的过程
- read:把数据从
磁盘
读取到内核缓冲区
,再拷贝到用户缓冲区
- write:先把数据写入到
socket缓冲区
,最后写入网卡设备
- read:把数据从
流程图如下:
读数据
- 用户应用进程调用read函数,向操作系统发起IO调用,上下文从用户态转为內核态(切换1)
- DMA控制器把数据从磁盘中,读取到内核缓冲区
- CPU把内核缓冲区数据,拷贝到用户应用缓冲区,上下文从内核态转为用户态(切换2),read函数返回
写数据
- 用户应用进程通过write函数,发起IO调用,上下文从用户态转为内校态(切换3)
- CPU将应用缓冲区中的数据,拷贝到socket缓冲区
- DMA控制器把数据从socket缓冲区,拷贝到网卡设备,上下文从内校态切换回用户态(切换4),write函数返回
从流程图可以看出,传统IO的读写流程,包括了4次上下文切换(4次用户态和内核态的切换
),4次数据拷贝(两次CPU拷贝以及两次的DMA拷贝
)。
一、阻塞IO模型
应用程序的进程发起IO调用,但是如果内核的数据还没准备好的话,那应用程序进程就一直在阻塞等待
,一直等到内核数据准备好了,从内核拷贝到用户空间,才返回成功提示,此次IO操作,称之为阻塞IO
。
- 阻塞IO比较经典的应用就是阻塞
socket
、Java BIO
- 阻塞IO的缺点就是:如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能,可以使用非阻塞IO优化
二、非阻塞IO模型
如果内核数据还没准备好,系统调用会立即返回
一个调用失败的信息,让它不需要等待,而是通过轮询
的方式再来请求。如果内核数据准备好,在数据从内核拷贝到用户空间期间是阻塞
的(因为现在是CUP在操作,之前准备数据是DMA)。
- 同步非阻塞 IO 的优点是每次发起的IO系统调用在内核等待数据过程中可以立即返回,用户线程不会阻塞,
实时性较好
- 同步非阻塞IO的缺点是不断地轮询内核,这将占用大量的CPU时间,
效率低下
三、IO多路复用模型
在这之前,我们先来复习下,什么是文件描述符fd(File Descriptor),它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程
返回一个文件描述符。
IO复用模型核心思路:系统给我们提供一类函数(如我们耳濡目染的select
、poll
、epoll
函数),它们可以同时监控多个fd的操作
,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用。
1、IO多路复用之select
应用进程通过调用select函数
,可以同时监控多个fd
,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom请求去读取数据。
非阻塞IO模型(NIO)中,需要N(N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。
select缺点
- 监听的IO
最大连接数有限
,在Linux系统上一般为1024
- select函数返回后,是通过
遍历
fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)
因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题
。但是呢,select和poll一样,还是需要通过遍历文件描述符
来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。因此经典的多路复用模型epoll诞生。
2、IO多路复用之epoll
为了解决select/poll存在的问题,多路复用模型epoll诞生,它采用事件驱动
来实现。
epoll先通过epoll_ctl()来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉遍历文件描述符的操作
,而是采用监听事件回调
的机制。这就是epoll的亮点。
3、总结select、poll、epoll的区别
select | poll | epoll | |
---|---|---|---|
底层数据结构 | 数组 | 链表 | 红黑树和双链表 |
获取就绪的fd | 遍历 | 遍历 | 事件回调 |
事件复杂度 | O(n) | O(n) | O(1) |
最大连接数 | 1024 | 无限制 | 无限制 |
fd数据拷贝 | 每次调用select,需要将fd数据从用户空间拷贝到内核空间 | 每次调用poll,需要将fd数据从用户空间拷贝到内核空间 | 使用内存映射(mmap),不需要从用户空间频繁拷贝fd数据到内核空间 |
epoll明显优化了IO的执行效率,但在进程调用epoll_wait()
时,仍然可能被阻塞
。能不能酱紫:不用我老是去问你数据是否准备就绪,等我发出请求后,你数据准备好了通知我就行了,这就诞生了信号驱动IO模型。
四、IO模型之信号驱动模型
信号驱动IO不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号(调用sigaction的时候建立一个SIGIO的信号),然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通过SIGIO信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用recvfrom,去读取数据。
信号驱动IO模型,在应用进程发出信号后,是立即返回的,不会阻塞进程。它已经有异步操作的感觉了。但是你细看上面的流程图,发现数据复制到应用缓冲的时候,应用进程还是阻塞的。回过头来看下,不管是BIO,还是NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的
。还有没有优化方案呢?AIO(真正的异步IO)
!
五、IO 模型之异步IO(AIO)
AIO也就是NIO2
。Java7中引入了NIO的改进版NIO2,它是异步IO模型
。
AIO 用来解决数据复制阶段的阻塞问题
- 同步意味着,在进行读写操作时,线程需要等待结果,还是相当于闲置
- 异步意味着,在进行读写操作时,线程不必等待结果,而是将来由操作系统来通过
回调方式
由另外的线程来获得结果
总结
阻塞、非阻塞、同步、异步IO划分
BIO、NIO、AIO
- 同步阻塞(blocking-IO)简称
BIO
- 同步非阻塞(non-blocking-IO)简称
NIO
- 异步非阻塞(asynchronous-non-blocking-IO)简称
AIO
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: