零拷贝

Posted 烟草的香味

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了零拷贝相关的知识,希望对你有一定的参考价值。

是什么

什么是零拷贝呢? 这个词想必听过不止一次了吧, 但一直没有认真的研究一下这到底是个什么玩意.

在很久之前, 一次IO 操作的流程大致是这样的:

假设, 这里的 IO 设备是磁盘, 那么磁盘的一次read操作流程如下:

  1. CPU向磁盘发起 IO 请求
  2. 磁盘将数据放入磁盘控制器缓冲区(上图步骤1), 并发起 IO 中断通知 CPU
  3. CPU 将数据拷贝到 Pagecache 中 (上图步骤2)
  4. 再将数据从 Pagecache 拷贝到应用缓冲区中 (上图步骤3)
  5. read函数返回, 应用读取到文件内容 (上图步骤4)

在上面的数据获取的过程中, 发生了3次数据的拷贝, 其中2次是 CPU 全程参与的. 而这个过程, CPU 忙于拷贝数据, 无暇做其他工作.

为了减轻 CPU 的压力, DMA应运而生.

DMA做的事情, 简单说来就是上图步骤2. 数据从 IO 设备缓冲区到内核缓冲区的拷贝工作, 不需要CPU 参与, 也就腾出一定的时间来做其他事情了. 其操作步骤大致如下:

  1. 应用程序调用read方法发起 IO 请求
  2. 系统向DMA发起 IO 请求, 然后继续处理其他工作
  3. DMA向磁盘发起 IO 请求
  4. 磁盘收到请求后, 将数据拷贝到磁盘缓冲区, 通过中断通知DMA
  5. DMA将数据拷贝到内核缓冲区(Pagecache), 然后通知 CPU 读取
  6. CPU 再将数据拷贝到应用缓冲区并返回read

于是, 一次数据读取的流程变成了这样:

现在, 每一次的数据读取, 都会发生2次数据拷贝(IO设备内部的就不算在其中了). 而零拷贝就是为了解决这个问题.

解决方案

mmap

想一下, 为什么数据需要从内核缓冲区拷贝到应用缓冲区? 他们用的明明是同一个物理内存呀. 还不是因为虚拟内存的存在, 所以他们在内存空间的不同地址. 如果能够让他俩共用用一段物理内存, 不就不需要拷贝数据了.

mmap的本意, 是将磁盘文件的内容直接映射到一段内存空间中进行读取, 而这恰好也减少了数据的拷贝.

Go中使用mmap如下:

package main

import (
	"fmt"
	"golang.org/x/exp/mmap"
)

func main() 
	at, _ := mmap.Open("./tmp.txt")
	buff := make([]byte, 1024)
	_, _ = at.ReadAt(buff, 0)
	_ = at.Close()
	fmt.Println(string(buff))

但是, 遗憾的是, 官方包没有提供write方法. 好在有一些优秀的开源项目可供参考.

mmap很好的解决了数据拷贝带来的消耗, 虽然还有一次DMS负责的数据拷贝, 但DMS不会影响 CPU 的执行.

单独读取一个设备, 亦或者单独向一个设备中写入内容, 这样确认很好, 但是, 如果我们要进行文件传输, 将一个文件的内容发送到网卡, 那么这个流程在使用mmap时就是这样的:

sendfile

我们知道, 应用程序调用系统是需要进行上下文切换的, 是否有一个函数直接告诉 CPU 把2个 IO 设备的数据进行拷贝? 这样就可以减少一次系统调用嘛.

没错, 使用的, 通过sendfile的方式, 整个流程大致如下:

Go中可直接通过函数syscall.Sendfile实现.

你以为这就完了么? 不, 这还不是零拷贝, 这此种仍然存在一次 CPU 主导的内存拷贝.

零拷贝

借用之前mmap的思路, 既然应用程序和内核可以公用同一个缓冲区, 网卡和磁盘为什么不可以呢? 于是, 复制流程就成了下面这样:

网卡和 IO 设备使用同一个内存地址空间. 在这个过程中所有的数据拷贝均有DMA参与, CPU没有参与, 极大的提升了传输效率.

注意, 此功能需要 linux2.4 以上, 且网卡支持(通过命令ethtool -k eth0 | grep scatter-gather查看)才行.

而这, 就是常说的零拷贝技术了. 零拷贝不是真的没有发生数据拷贝, 而是CPU没有负责数据拷贝.

如何使用零拷贝呢? 还是调用sendfile, 如果在支持零拷贝的系统上, 就会自动使用零拷贝技术啦.

总结

已知的, 在kafka中使用了零拷贝的技术.

如何, 简单看下来, 零拷贝也没有那么什么嘛. 此项暂时搁置, 再见


原文地址: https://hujingnb.com/archives/896

理解了零拷贝原理,总结一下

为什么要零拷贝?

        传统的IO拷贝在计算机中拷贝次数太多,速度太慢,零拷贝可以减少拷贝次数,增加系统性能。另外,零拷贝并不是指没有进行文件的拷贝,只是减少了拷贝的次数。

1、基本概念

1.1、 DMA控制器

​ 首先需要知道计算机硬件的读写速度,大概如下:

  • CPU高速缓存属于速度最快的,这里可以认为是飞机的速度。
  • 网卡的速度,可以认为是汽车速度。
  • 硬盘可以认为是走路的速度。

        那假如把硬盘的文件通过网卡发送出去,普通IO,CPU高速缓存要向网卡写数据,即使CPU的速度再快,也会被其他两个设备影响。因此CPU需要一个小弟DMA控制器。

        直接内存访问(DMA)是一种完全由硬件执行I/O交换的工作方式。在这种方式中,DMA控制器从CPU完全接管对总线的控制,数据交换不经过CPU,而直接在内存和I/O设备之间进行 。DMA方式一般用于高速传送成组数据。DMA控制器将向内存发出地址和控制信号,修改地址,对传送的字的个数计数,并且以中断方式向CPU报告传送操作的结束。

        DMA方式的主要优点是速度快。由于CPU根本不参加传送操作,因此就省去了CPU取指令、取数、送数等操作。在数据传送过程中,没有保存现场、恢复现场之类的工作。内存地址修改、传送字个数的计数等等,也不是由软件实现,而是用硬件线路直接实现的。所以DMA方式能满足高速I/O设备的要求,也有利于CPU效率的发挥。

1.2、 CPU用户态和内核态

        世界上的人本来就是不平等的,这就好比,你老婆能管你的钱,但是你只能闷头挣钱一样。程序也是如此,有的程序权限很高,可以访问计算机的任何资源,但是有的程序权限就低,只能访问部分资源。这两个类型的程序,就可以映射为CPU的用户态和内核态。方便记忆可以这么理解,内核态是计算机的核心,可以访问计算机的任何资源,如网卡、硬盘。但是为了安全,CPU不能让用户程序肆无忌惮的访问计算机的任何资源,这样如果用户程序不稳定可能会造成系统崩溃,因此才有的用户态。

  • 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
  • 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

        因此,在CPU想要读取硬盘文件的时候,需要从用户态切换为内核态,才有权限。读取完成之后,为了程序安全,需要从内核态切换为用户态。

2、零拷贝

2.1、普通拷贝

在普通的拷贝时,大概流程如下

  1. cpu切换到内核态,先到内核态查询内核缓冲区,如果内核缓冲区有,则可以直接拷贝到用户空间中。
  2. 如果内核缓冲区没有,则CPU会让DMA加载到内核空间中。这里就会有一次DMA拷贝。
  3. 拷贝到内核缓冲区之后,CPU将会从内核缓冲区拷贝走。这是一次CPU拷贝。拷贝完成切换到用户态。
  4. 写的时候,再次切换到内核态。切换完成之后,写到socket缓冲区。写完之后,切换到用户态。
  5. DMA通过异步的方式将socket缓冲区的数据通过网卡发送到对端。

总结这种普通的IO。总共有4次CPU切换(上图蓝色)分别是读2次、写2次。4次文件拷贝,分别是:

  1. 文件从硬盘到内核空间
  2. 内核空间到CPU
  3. CPU到socket缓冲区
  4. socket缓冲区到网卡。

2.2、mmap零拷贝

        mmap是零拷贝的一种方式通过虚拟内存的方式实现。也就是说用户空间和内核空间使用同一个物理地址。这样,文件就不在需要经过用户空间。可以从内核缓冲区直接复制到socket缓冲区。减少了一次文件拷贝。

2.3、sendfile零拷贝

        sendfile函数在两个文件描述符之间传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,被称为零拷贝

  1. 系统调用 sendfile() 通过 DMA 把硬盘数据拷贝到内核缓冲区,然后数据被内核直接拷贝到另外一个与 socket 相关的 socket缓冲区。这里没有 用户态和核心态 之间的切换,在内核中直接完成了从一个 缓冲区 到另一个缓冲区的拷贝。
  2. DMA 把数据从内核缓冲区 直接拷贝给协议栈,没有切换,也不需要数据从用户态和核心态,因为数据就在 内核里。

总结

零拷贝并不是没有拷贝,是指减少拷贝的次数。有两种方式mmap和sendfile。

  1. mmap 适合小数据量读写,sendFile 适合大文件传输。(这个并没有查到详细理论依据,如果您有线索,欢迎留言)
  2. mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
  3. sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。

如果有误,欢迎指正。

以上是关于零拷贝的主要内容,如果未能解决你的问题,请参考以下文章

Netty零拷贝

零拷贝kafka和netty零拷贝在实现机制上的区别

理解了零拷贝原理,总结一下

零拷贝技术

理解了零拷贝原理,总结一下

零拷贝原理的文章网上满天飞,但你知道如何使用零拷贝吗?