一 什么是零拷贝(zero copy)? 零拷贝有什么优点?

1.1 什么是零拷贝?

零拷贝: 指的是在I/O过程中,用户空间和内核空间不需要进行CPU数据拷贝。零拷贝并不是指I/O过程中一次拷贝都没有发生。

传统的I/O读的时候,需要将内核缓冲区的数据拷贝到用户空间的缓冲区;传统I/O写的时候,需要将用户空间缓冲区数据拷贝到内核缓冲区。但是零拷贝就是避免了这种拷贝操作,提升了I/O性能。比如我们读取磁盘数据,然后写入网卡为例,按照传统I/O的工作方式如图所示:

从上图我们可以看到,总共发生了4次内核态集和用户态的切换、发生了4次数据拷贝。

1.2 零拷贝有什么优点

减少了CPU拷贝、降低了用户态和内核态切换次数,从而提升了I/O的性能。

二 零拷贝方案

零拷贝有多种实现方案,比如减少用户空间和内核空间的拷贝次数,也可以用户空间直接绕过内核空间直接进行I/O操作等,这里主要讲的是减少用户空间和内核空间的拷贝次数这种方案,也是目前用的比较多的方案。

2.1 mmap+write

我们知道,传统的I/O读写,将数据从磁盘写入网卡需要4次用户态和内核态的切换以及4次拷贝,性能不高。而文件内存映射因为直接可以在用户空间操作内核缓冲区或者说页高速缓存,只要指定了目的地,就可以在内核空间中直接拷贝数据,这样就可以避免在用户空间和内核空间多次进行数据拷贝。

2.1.1 mmap函数

void *mmap(void *addr, size_t length, int prot , int flags ,int fd, off_t offset);

addr: 开始映射的地址,属于进程的逻辑地址
length: 从开始映射地址,映射多长,一般是一个页大小,4KB
prot:期望的内存保护标志,不能与文件打开的标志冲突,比如文件只可读,这里的就不能可写
#1 PROT_EXEC 页内容可以被执行
#2 PROT_READ 页内容可以被读取
#3 PROT_WRITE 页可以被写入
#4 PROT_NONE 页不可访问
flags: 指定映射对象的类型,映射选项和映射页是否可以共享
MAX_FIXED: 如果参数start所指的地址无法成功建立映射时,则放弃映射
MAP_SHARED: 与其他映射这个文件的进程共享映射内存,可能存在并发修改
MAP_PRIVATE: 对映射区域的写入操作会产生一个映射文件的复制,类似于写时复制,对此区域作的任何修改都不会写回原来的文件内容。
fd: 文件描述符
offset: 文件映射的偏移量,已经映射了多少

2.1.2 mmap+write工作流程

第一:调用mmap函数将文件和进程虚拟地址空间映射
第二:将磁盘数据读取到页高速缓存
第三:调用write函数将页高速缓存数据直接写入套接字缓冲区
第四:将套接字缓冲区的数据写入网卡

2.1.3 mmap+write 数据传输流程

#1 用户进程调用mmap函数,向内核发起调用,CPU从用户态切换到内核态
#2 建立文件物理地址和虚拟内存映射区域的映射,或者说是内核缓冲区(页高速缓存)和虚拟内存映射区域的映射
#3 CPU向磁盘DMA控制器发送读取指定位置和大小的指令,DMA控制器将数据从磁盘拷贝到内核缓冲区
#4 mmap系统调用结束返回,CPU从内核态切换到用户态
#5 用户进程调用write函数,向内核发起调用,CPU从用户态切换到内核态
#6 CPU将页高速缓存中的数据拷贝到套接字缓冲区
#7 CPU向磁盘DMA控制器发送DMA写指令,DMA控制器从套接字缓冲区调用协议栈处理,最后把数据拷贝到网卡
#8 write系统调用结束返回,CPU从内核态切换到用户态

总结:
总共发生3次数据拷贝,4次上下文切换,所以mmap+write的效率比传统
read/write效率高一些。

2.2 sendfile

基于mmap方式实现的零拷贝已经比以前的传统I/O流程少了一次CPU拷贝,性能有所优化,但是还不是最优。Linux后面又引入了一个新的系统调用sendfile。
它实现了mmap+write的功能,简化了用户接口,即用户只需要一次调用,就可以实现mmap+write的功能。

2.2.1 sendfile函数

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

out_fd: 写入的文件描述符
in_fd: 写入文件描述符
offset: 从哪个位置开始读取
count: 读取多少数据

2.2.2 sendfile工作原理

第一:调用sendfile函数
第二:从磁盘读取数据,拷贝到内核缓冲区
第三:CPU将内核缓冲区数据拷贝到套接字缓冲区
第四:套接字缓冲区数据拷贝到网卡

2.2.3 sendfile数据传输流程

#1 用户进程调用sendfile函数,向内核发起调用,CPU从用户态切换内核态
#2 CPU向磁盘DMA控制器发送读取数据的指令,DMA控制器读取磁盘数据,拷贝到页高速缓存
#3 然后CPU将页高速缓存的数据拷贝到套接字缓冲区
#4 CPU向网卡DMA引擎发送读取指令,从套接字缓冲区调用协议栈处理,然后数据拷贝到网卡
#5 sendfile调用结束,CPU从内核态切换到用户态

总结:
总共发生3次数据拷贝,2次上下文切换,所以sendfile的效率比mmap+write方式
还高一些

2.3 sendfile + DMA scatter / gather copy

sendfile设计的时候并不是针对处理大文件的,如果需要处理大文件的话,需要调用另外一个接口sendfile64()。它的核心思想就是基于DMA scatter / gather 来实现的。

2.3.1 什么是block DMA?什么是SG-DMA?

2.3.1.1 什么是block DMA

block DMA就是要求传输数据块的源物理地址和目标物理地址都是连续的,每次只能传输一个数据块,传输完成后,中断机构触发中断。

2.3.1.2 什么是SG-DMA?

第一:SG-DMA是scatter / gather的缩写,scatter可以将一个源位置连续的数据块传输到目的地离散的存储;gather可以将源位置分散的数据块传输到目的地连续的存储。
第二:SG-DMA会预先维护一个物理上不连续的块描述符的链表,描述符中包含有数据的起始地址和长度。传输时只需要遍历链表,按序传输数据,全部完成后发起一次中断即可,效率比Block DMA要高。

2.3.2 sendfile函数

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

out_fd: 写入的文件描述符
in_fd: 写入文件描述符
offset: 从哪个位置开始读取
count: 读取多少数据

2.3.3 sendfile+DMA scatter/gather工作原理

第一:调用sendfile函数
第二:读取磁盘数据,通过scatter方式写入到页高速缓存,并且维护一个块描述符链表
第三:将描述符元数据信息拷贝到套接字缓冲区;并且只是网卡读取数据,网卡通过gather方式拷贝数据

2.3.4 sendfile + DMA scatter / gather copy 数据传输流程

#1 用户进程调用sendfile或者sendfile64()函数,向内核发起调用,CPU从用户态切换到内核态
#2 CPU向磁盘控制器发送读取数据指令,DMA控制器读取磁盘数据,以scatter方式将数据离散的存储在也缓冲区(也就是说可以不是连续的),然后维护一个块链表,方便在页高速缓存中查找
#3 CPU将描述符信息(数据大小和内存地址等元数据信息),以CPU方式拷贝到套接字缓冲区
#4 CPU通知网卡DMA控制器套接字缓冲区获取元数据信息,然后根据元数据信息,从页高速缓存以DMA gather聚合方式将离散的数据拷贝数据到网卡
#5 sendfile64系统调用结束,CPU从内核态切换到用户态

总结:
总共发生2次数据拷贝,1次元数据拷贝,2次上下文切换,所以sendfile64的效率比sendfile这种方式效果好些,尤其是处理大文件。真正来说1次元数据拷贝可以忽略不计。所以可以认为只有2次DMA数据拷贝和2次CPU的内核态和用户态的切换。
缺点:第一,需要硬件DMA支持scatter/gather功能;第二:只是支持输出的文件描述符不受限制,但是输入的文件描述符类型依然只能是文件。比如你可以文件到文件,也可以文件到网卡,但是不能网卡到文件。这就是FileChannel中transferFrom只能是读取文件,但是transferTo的目标可以是网卡也可以是文件的的根本原。

2.4 splice

我们知道,sendfile + DMA scatter / gather方式虽然可以只发生2次数据拷贝,2次CPU的用户态和内核态的切换,但是说到底,它是需要DMA硬件的支持,如果DMA不支持scatter / gather,那么还是得使用sendfile方式。至少FileChannel中transferTo是这么实现的。另外一个就是输入文件描述符类型的限制。所以后来出现了splice, 他可以完全通过软件实现。

2.4.1 splice函数

splice(int fd_in, loff_t *off_in, int fd_out,loff_t *off_out,size_t len, unsigned int flags );

fd_in: 输入文件描述符
off_in: 输入文件的offset偏移量,指示内核从哪里读取数据
fd_out: 输出文件描述符
off_out: 输出文件描述符,指示内核数据输出到什么位置
len: 指示了此次调用希望传输的字节数

2.4.2 splice工作原理

第一:调用pipe()函数创建管道
第二:调用splice()函数,读取数据,写入管道
第三:调用splice()函数,从管道读取数据,写入到目标

2.4.3 splice的数据传输流程

#1 用户进程调用pipe函数,向内核发起调用,CPU从用户态切换到内核态
#2 创建单向管道
#3 pipe函数调用结束返回,CPU从内核态切换到用户态
#4 用户进程调用splice函数,向内核发起调用,CPU从用户态切换到内核态
#5 CPU向磁盘DMA控制器发送读指令,DMA读取数据后,拷贝到页高速缓存;并且将页高速缓存数据写入管道中
#6 splice调用结束,CPU从内核态切换到用户态
#7 用户进程调用splice函数,向内核发起调用,CPU从用户态切换到内核态
#8 CPU从管道中消费数据,然后写入到套接字缓冲区,然后向网卡的DMA控制器发送写指令,DMA控制器将经过协议栈处理的套接字缓冲区的数据拷贝到网卡
#9 splice调用结束,CPU从内核态切换到用户态

三 Linux零拷贝方案比较

Linux 零拷贝方案相关推荐

  1. Linux - 零拷贝技术

    Linux - 零拷贝技术 前言 一. 相关概念 1.1 缓冲区 1.1.1 内核缓冲区 1.1.2 用户缓冲区 1.2 DMA技术 1.3 虚拟内存 二. 零拷贝 2.1 传统文件传输流程 2.2 ...

  2. Linux 零拷贝技术

    文章目录 使用标准I/O的痛点 零拷贝技术介绍 直接I/O操作 mmap内存映射 sendfile 在文件描述符之间传递数据 使用标准I/O的痛点 在Linux中 标准I/O操作都是基于数据拷贝的缓冲 ...

  3. Linux零拷贝技术

    1.为什么需要零拷贝 传统的 Linux 系统的标准 I/O 接口(read.write)是基于数据拷贝的,也就是数据都是 copy_to_user 或者 copy_from_user. 好处:通过中 ...

  4. 讲透彻什么是Linux零拷贝?

    本文探讨 Linux 中主要的几种零拷贝技术以及零拷贝技术的适用场景.为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入.在写一个服务端程序时(Web Server或者文件服务器),文件下载是一 ...

  5. 可算是有文章,把Linux零拷贝讲透彻了!

    击上方"朱小厮的博客",选择"设为星标" 后台回复"加群",加入组织 来源:22j.co/brVn 本文探讨 Linux 中主要的几种零拷贝 ...

  6. 搞懂Linux零拷贝,DMA

    目录 为什么要有 DMA 技术? 传统的文件传输有多糟糕? 如何优化文件传输的性能? 如何实现零拷贝? PageCache 有什么作用? 大文件传输用什么方式实现? 总结 磁盘可以说是计算机系统最慢的 ...

  7. linux零拷贝实现程序,浅析零拷贝技术

    前言零拷贝(英语:Zero-copy)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域.这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽. 零拷贝操作减少了在用户 ...

  8. linux 零拷贝goodlen,linux零拷贝技术

    简介零复制(英语:Zero-copy:也译零拷贝)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域.这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽. 零拷贝操作 ...

  9. Linux 零拷贝 sendfile函数中文说明及实际操作

    Sendfile函数说明  #include  ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); sendfi ...

最新文章

  1. EntityFramework Core解决并发详解
  2. linux ping 连续,在SUN Solaris下连续ping的使用
  3. Effective C++ 读后感
  4. AcWing 896. 最长上升子序列 II(二分优化LIS)
  5. Flash/Flex学习笔记(50):3D线条与填充
  6. 复旦邱锡鹏Lab提出:一个统一的面向基于Aspect的所有情感分析子任务的生成式方法...
  7. eclipse对mysql进行增删改查_用eclipse编写的 能对MySQL数据库中表实现增删改查功能...
  8. 拓端tecdat|使用markdown,knitr和pandoc在R语言中编写可重现的报告
  9. 阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第4节 等待唤醒机制_8_等待唤醒机制代码实现_包子类包子铺类...
  10. Java数据结构:双向链表Double Linked List基础笔记
  11. p6spy mysql8_P6Spy监控SQL语句及性能测试
  12. 最大公约数,最小公倍数的求法
  13. reference to ‘ max‘ is ambiguous
  14. 系统性谈谈软件可靠性——第4讲:软件可靠性测试
  15. QML 全屏 输入法无法显示
  16. 魔兽名字显示服务器,魔兽世界怀旧服服务器名称
  17. 用System中System.setOut()方法修改输出方式
  18. 请详细分析商品期货最近走势( 2016-11-29)
  19. 图像处理学习2,边缘检测1(sobel算子,kirsch算子)
  20. matlab生成随机数,matlab随机数生成方法

热门文章

  1. python中排序的函数_Python中sorted()排序函数
  2. 如何连接文档服务器,如何连接服务器地址
  3. MATLAB 句柄函数记录——多变量
  4. OpenCV计算机视觉实战(Python版)_005图像的梯度处理
  5. Python机器学习:逻辑回归002逻辑回归的损失函数
  6. mybatis mysql uuid_spring boot整合mybatis利用Mysql实现主键UUID的方法
  7. c c和java最大的区别是什么,“一般”和“一般”之间的区别是什么,类型在C ++和Java?...
  8. java 内存指针_C指针和内存
  9. onCreat 函数 回调函数
  10. 中如何刷新当前路由_企业装修和家庭改造中,路由器及接收器如何安放