Netty入门到超神系列-零拷贝技术

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据同步 1个月
简介: 内存主要作用是在计算机运行时为操作系统和各种程序提供临时储存,操作系统的进程和进程之间是共享CPU和内存资源的。为了防止内存泄露需要一套完善且高效的内存管理机制。因此现代操作系提供了一种基于主内存抽象出来的概念:虚拟内存(Virtual Memory)。虚拟内存虚拟内存是计算机系统内存管理的一种技术,主要为每个进程提供私有的地址空间,让每个进程拥有一片连续完整的内存空间。而实际上,虚拟内存通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来物理内存物理内存指通过内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来

前言

本篇文章我们来探讨一下Linux中的几种“零拷贝”技术,我们在 java nio,kafka,RocketMQ等框架中多多少少都有听到这个概念,零拷贝是IO性能提升非常重要的技术,也是Netty高性能的原因之一。

物理内存和虚拟内存

内存主要作用是在计算机运行时为操作系统和各种程序提供临时储存,操作系统的进程和进程之间是共享CPU和内存资源的。为了防止内存泄露需要一套完善且高效的内存管理机制。因此现代操作系提供了一种基于主内存抽象出来的概念:虚拟内存(Virtual Memory)。

  • 虚拟内存
    虚拟内存是计算机系统内存管理的一种技术,主要为每个进程提供私有的地址空间,让每个进程拥有一片连续完整的内存空间。而实际上,虚拟内存通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来
  • 物理内存
    物理内存指通过内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来作为内存。也就是说每个虚拟内存都对应一个特定的地址空间(物理内存或者磁盘存储空间)

在用户进程和物理内存引入虚拟内存后,当程序向系统申请内存时,系统为程序分配虚拟内存,虚拟内存地址会映射到物理地址,为了获取到实际的数据,CPU 需要将虚拟地址转换成物理地址。

这里的页表可以理解成是虚拟内存映射到物理列出的链表。

内核空间和用户空间

操作系统的核心是内核,可以访问受保护的内存空间,也有访问底层硬件设备的权限,为了避免用户进程直接操作内核,操作系统将虚拟内存划分为内核空间(Kernel-space)和 用户空间(User-space)。

  • 内核空间
    内核空间总是驻留在内存中,它是为操作系统的内核保留的。应用程序是不允许直接在该区域进行读写或直接调用内核代码定义的函数的
  • 用户空间
    每个用户进程都有一个独立的用户空间,处于用户态的进程不能访问内核空间中的数据和调用内核函数 ,因此要进行系统调用的时候,就要将进程切换到内核态。

DMA传输原理

DMA (Direct Memory Access):DMA的意思是直接内存访问,它允许外围设备(硬件子系统)直接访问系统主内存。有了 DMA之后,系统主内存 与 硬盘或网卡之间的数据传输可以绕开 CPU 的全程调度,大大解放了CPU的劳动力,下面我们来理解一下DMA

传统IO流程

我们针对下面案例来分析一下IO的执行流程

RandomAccessFilerandomAccessFile=newRandomAccessFile(newFile("file.txt"),"rw");
byte[] arr=newbyte[(int)file.length()];
//读randomAccessFile.read(arr);
//把数据写到SokcetSocketsocket=newServerSocket(5555).accept();
//写socket.getOutputStream().write(arr);

上面的案例完成了一次读写操作,先是从磁盘读取 file.txt 文件,内容存储到 byte[]中,然后把 byte[]中的数据写到socket 。那么在没有DMA的情况下IO是如何工作的呢?

  1. 用户进程向 发起 read操作,用户进程由用户态切换为内核态,然后一直阻塞等待数据的返回。
  2. CPU 在接收到指令以后对磁盘发起 I/O 请求,将磁盘数据先放入磁盘控制器缓冲区。
  3. 接下来由CPU将磁盘缓冲区中的数据拷贝到内核缓冲区,然后再从内核缓冲区拷贝到用户缓冲区。
  4. 用户进程由内核态切换回用户态,解除阻塞状态,程序继续执行。

由于整个IO过程都需要CPU亲力亲为,在数据的拷贝是非常消耗CPU性能的,为了提升IO性能出现了DMA技术。

DMA IO流程

下面以读取文件数据到内存为例来演示 DMA 原理:

解释一下图中的步骤

  1. 应用进程发起read命令, 调用CPU 读取数据,此时CPU会将用户进行从用户态切到内核态,程序线程一直阻塞等待数据的返回。
  2. CPU向 DMA 磁盘控制器发起调度指令。
  3. DMA 磁盘控制器向磁盘请求IO,将磁盘数据先放入磁盘控制器缓冲区,CPU 不参与此过程。
  4. 后续DMA收到完成指令, 将数据从磁盘控制器缓冲区拷贝到内核缓冲区。
  5. DMA 磁盘控制器向 CPU 发出数据读完的信号,由CPU 负责将数据从内核缓冲区拷贝到用户缓冲区。
  6. 最后切换回用户态,返回数据,解除阻塞, 程序继续往后执行。

所以为什么要出现DMA呢?如果没有DMA,那么所有的拷贝操作都需要CPU的参与,拷贝数据非常消耗CPU资源,导致整体系统性能下降。所以DMA的出现解放了CPU,使得系统性能得到提升。


经过上面的流程,数据已经读取到用户缓冲区,接下来执行 write 向网络发送数据,先将数据从用户空间的页缓存拷贝到内核空间的网络缓冲区(socket buffer)中,然后再将写缓存中的数据拷贝到网卡设备完成数据发送,流程如下:

解释一下图中的步骤

  1. 用户进程通过 write() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
  2. CPU 将用户缓冲区(user buffer)中的数据拷贝到内核空间(kernel space)的网络缓冲区(socket buffer)。
  3. CPU 利用 DMA 控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。
  4. 上下文从内核态(kernel space)切换回用户态(user space),write 系统调用执行返回。

DMA 的问题

整个过程涉及 2 次 CPU 拷贝、2 次 DMA 拷贝总共 4 次拷贝,以及 4 次内核切换,如下图:

下面是内核切换完整流程:

  1. 用户进程执行read,从用户态切到内核态
  2. DMA控制器将数据从硬盘拷贝到内核缓冲区
  3. CPU将内核缓冲区的数据拷贝到用户空间的用户缓冲区
  4. 上下文从内核态切回用户态,read 调用执行返回。

DMA拷贝虽然一定程度解放了CPU,但是涉及到的内核切换次数和数据拷贝次数太多,依然不能让IO性能达到最优。

零拷贝技术

零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。

它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现 CPU 的零参与,彻底消除 CPU 在这方面的负载

也就是说所谓的零拷贝是消除CPU拷贝,但是DMA拷贝肯定是需要的。

MMAP模式

使用 MMAP 的目的是将内核中缓冲区(read buffer)的地址与用户空间的缓冲区(user buffer)进行映射,

从而实现内核缓冲区与应用程序内存的共享,这样在进行网络传输时,就可以减少内核空间到用户空间的拷贝,大致流程如下:

然而内核读缓冲区(read buffer)仍需将数据拷贝到内核写缓冲区(socket buffer), 整个拷贝过程会发生 4 次内核切换,1 次 CPU 拷贝和 2 次 DMA 拷贝。

  1. 用户进程调用 mmap 函数,用户进程从用户态切到内核态
  2. 将用户进程的内核缓冲区与用户缓存区进行内存地址映射
  3. DMA 控制器将数据从主存或硬盘拷贝到内核缓冲区
  4. 上下文从内核态切回用户态mmap 系统调用结束
  5. 用户进程调用 write 函数,上下文从用户态切换为内核态
  6. CPU 将内核缓冲区的数据拷贝到网络缓冲区(SocketBuffer)
  7. CPU 利用 DMA 控制器将数据从网络缓冲区拷贝到网卡进行数据传输。
  8. 上下文从内核态切换回用户态,write 调用结束

MMAP的问题是 4次内核切换,3次数据拷贝,拷贝次数和切换次数依然很多。

Sendfile模式

Sendfile在Linux2.1被引入 ,Sendfile 系统调用的引入,不仅减少了 CPU 拷贝的次数,还减少了上下文切换的次数通。过 Sendfile 数据可以直接在内核空间内部进行 I/O 传输,也就是说数据直接通过内核缓冲区(Kernel Buffer)拷贝到Socket缓冲区(Socket Buffer), 数据根部不经过用户空间,对于用户来说数据是不可见的。

基于 Sendfile 系统调用的零拷贝方式,整个拷贝过程会发生 2 次上下文切换,1 次 CPU 拷贝和 2 次 DMA 拷贝

  1. 用户进程执行 sendfile,上下文从用户态切换为内核态
  2. DMA 控制器将数据从主存或硬盘拷贝到内核缓冲区
  3. CPU 将内核缓冲区中的数据拷贝到的网络缓冲区
  4. CPU 利用 DMA 控制器将数据从网络缓冲区拷贝到网卡进行数据传输。
  5. 上下文从内核态切换回用户态Sendfile 结束

Sendfile模式只需要2次内核态的切换,数据拷贝次数还是3次,它的问题是用户程序不能对数据进行修改,而只是单纯地完成了一次数据传输过程。

Sendfile+DMA 优化

在Linux2.4 对Sendfile进行了优化 ,它将内核缓冲区中对应的数据描述信息(内存地址、地址偏移量)记录到相应的网络缓冲区中,由 DMA 根据内存地址、地址偏移量将数据批量地从读缓冲区(read buffer)拷贝到网卡设备中 。

也就是说它实现了将内核缓冲区中的数据直接拷贝到网卡设备,省去了内核缓冲区数据拷贝到网络缓冲区的过程,彻底消除了CPU考别。

整个拷贝过程会发生 2 次上下文切换、0 次 CPU 拷贝以及 2 次 DMA 拷贝。

  1. 用户进程调用 sendfile 上下文从用户态切换为内核态
  2. DMA 控制器将数据从主存或硬盘拷贝到内核缓冲区
  3. CPU 把内核缓冲区中的文件描述符和数据长度拷贝到网络缓冲区(socket buffer)。
  4. 基于已有的文件描述符和数据长度,DMA 控制器直接批量地将数据从内核缓冲区拷贝到网卡进行数据传输。
  5. 上下文从内核态切换回用户态Sendfile 执行结束

这种方式用户程序依然不能对数据进行修改的问题,它只适用于将数据从文件拷贝到 socket 套接字上的传输过程。

Splice

Linux 在 2.6.17 版本引入 Splice 系统调用 , 它通过在内核缓冲区和网络缓冲区之间建立通道(pipeline),来避免了两者之间的 CPU 拷贝操作。

整个拷贝过程会发生 2 次上下文切换,0 次 CPU 拷贝以及 2 次 DMA 拷贝。

  1. 用户进程调用 splice 函数,从用户态切换为内核态。
  2. DMA 控制器将数据从主存或硬盘拷贝到内核缓冲区。
  3. CPU 在内核缓冲区和网络缓冲区之间建立管道(pipeline)。
  4. DMA 控制器将数据从网络缓冲区拷贝到网卡进行数据传输。
  5. 上下文从内核态切换回用户态Splice 调用结束

Splice 拷贝的问题是用户程序同样不能对数据进行修改。

缓冲区共享

它的思想是为每个进程都维护着一个缓冲区,这个缓冲区池能被同时映射到用户空间和内核态,内核和用户共享这个缓冲区池,这样就避免了一系列的拷贝操作。就目前而言缓冲区共享并不是一个非常成熟的方案,这里也不进行探讨。

总结

零拷贝在数据进行IO时,对性能的影响是非常大的,零拷贝不是不拷贝,而是以消除CPU拷贝,减少拷贝,减少内核切换次数来提升IO性能为目的。

本文简单介绍了物理内存,虚拟内存,用户态,内核态等概念,并介绍了Linux系统中的零拷贝技术的集中方案,下面是各种零拷贝技术的对比

拷贝模式 函数 CPU拷贝次数 DMA拷贝次数 内核切换次数
传统IO read/write 2 2 4
mmap mmap/write 1 2 4
sendfile sendfile 1 2 2
sendfile优化 sendfile 0 2 2
splice splice 0 2 2
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
4月前
|
缓存 网络协议 算法
Netty的基础入门(上)
Netty的基础入门(上)
185 1
|
4月前
|
缓存 网络协议 算法
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
196 0
|
4月前
|
Java Unix Linux
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
当涉及到网络通信和高性能的Java应用程序时,Netty是一个强大的框架。它提供了许多功能和组件,其中之一是JNI传输。JNI传输是Netty的一个特性,它为特定平台提供了高效的网络传输。 在本文中,我们将深入探讨Netty提供的特定平台的JNI传输功能,分析其优势和适用场景。我们将介绍每个特定平台的JNI传输,并讨论其性能、可靠性和可扩展性。通过了解这些特定平台的JNI传输,您将能够更好地选择和配置适合您应用程序需求的网络传输方式,以实现最佳的性能和可靠性。
128 7
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
|
3月前
|
移动开发 前端开发 网络协议
技术笔记:Netty专题(六)
技术笔记:Netty专题(六)
31 0
|
3月前
|
消息中间件 存储 Java
美团面试:说说Netty的零拷贝技术?
零拷贝技术(Zero-Copy)是一个大家耳熟能详的技术名词了,它主要用于提升 IO(Input & Output)的传输性能。 那么问题来了,为什么零拷贝技术能提升 IO 性能? ## 1.零拷贝技术和性能 在传统的 IO 操作中,当我们需要读取并传输数据时,我们需要在用户态(用户空间)和内核态(内核空间)中进行数据拷贝,它的执行流程如下: ![](https://cdn.nlark.com/yuque/0/2024/png/92791/1706491312473-52f5904a-2742-4e99-9b78-995e9a8b9696.png?x-oss-process=image%2F
41 0
|
4月前
|
Dubbo Java 应用服务中间件
【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(附通信协议和代码)(上)
今天,我要向大家实现一个基于Netty实现的高性能远程通信框架!这个框架利用了 Netty 的强大功能,提供了快速、可靠的远程通信能力。 无论是构建大规模微服务架构还是实现分布式计算,这个分布式通信框架都是一个不可或缺的利器。
134 2
【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(附通信协议和代码)(上)
|
4月前
|
负载均衡 Java 调度
【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(Dispatcher和EventListener)(下)
经过阅读《【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(附通信协议和代码)(上)》,相信您已经对网络通信框架的网络通信层的实现原理和协议模型有了一定的认识和理解。
76 0
【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(Dispatcher和EventListener)(下)
|
4月前
|
设计模式 网络协议 算法
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)
168 1
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析(一)
|
4月前
|
消息中间件 缓存 Java
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty(二)
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
221 1
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty(二)
|
4月前
|
消息中间件 缓存 Java
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
143 0