Linux 操作系统原理 — 零拷贝技术
目录
文章目录
- 目录
- Linux I/O 缓存背景
- 为什么需要零拷贝?
- 零拷贝技术(Zero-Copy)
- 方法一:用户态直接 I/O
- 方法二:mmap + write
- 方法三:Sendfile
- 方法四:Sendfile + DMA gather copy
- 方法五:Splice
- 方法六:写时复制
- 方法七:缓冲区共享
- 零拷贝对比
- 参考文章
Linux I/O 缓存背景
当请求文件服务器的下载功能时,服务端程序所做的事情是:将服务器磁盘中的文件不做修改地从已连接的 Socket 发送到客户端,通常使用下面的代码完成:
while ((n = read(diskfd, buf, BUF_SIZE)) > 0)write(sockfd, buf , n);
该程序的基本操作就是循环的从磁盘读入文件内容到缓冲区,再将缓冲区的内容发送到 Socket。Linux I/O 操作默认是缓冲 I/O,主要使用了 read()
和 write()
这两个系统调用。当应用程序访问某块数据时,操作系统首先会检查最近是否访问过此系统,文件内容是否缓存在了内核缓冲区。如果是,操作系统则直接根据 read()
传入的 buf 地址,将内核缓冲区中的内容拷贝到 buf 地址所指向的用户空间缓冲区中;如果不是,操作系统则首先将磁盘上的数据拷贝的内核缓冲区,这一步主要依靠 DMA(直接存储器存取)来传输,继而再把内核缓冲区上的内容拷贝到用户空间缓冲区。接下来,write()
再把用户缓冲区的内容拷贝到网络堆栈相关的内核缓冲区中,最后 Socket 再把内核缓冲区的内容发送到网卡上。
可见,上述简单的 I/O 操作,实际上发生了多次的数据拷贝。与此同时,在用户态、内核态运行模式的切换也会产生多次 CPU 上下文切换,无疑也加重了 CPU 的性能损耗。
问题的关键在于,在文件下载的过程中,我们并不需要对文件的内容做任何修改,那么这种缓存式的 I/O 操作就显得非常的无谓了。零拷贝技术主要就是为了解决这种低效性。
为什么需要零拷贝?
传统的 Linux 系统的标准 I/O 接口(read、write)是基于数据拷贝的,也就是数据都是 copy_to_user 或者 copy_from_user,这样做的好处是,通过中间缓存的机制,减少磁盘 I/O 的操作,但是坏处也很明显,大量数据的拷贝,用户态和内核态的频繁切换,会消耗大量的 CPU 资源,严重影响数据传输的性能,有数据表明,在 Linux 内核协议栈中,这个拷贝的耗时甚至占到了数据包整个处理流程的 57.1%。
零拷贝技术(Zero-Copy)
零拷贝就是这个问题的一个解决方案,通过尽量避免拷贝操作来缓解 CPU 的压力。零拷贝并没有真正做到“0”拷贝,它更多是一种思想,很多的零拷贝技术都是基于这个思想去做的优化。
零拷贝是一种数据传输的优化思想,主要任务是避免进行不必要的数据拷贝动作。
在 Linux 中零拷贝技术主要有 3 个实现思路:
- 用户态直接 I/O:应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输。这种方式依旧存在用户空间和内核空间的上下文切换,硬件上的数据直接拷贝至了用户空间,不经过内核空间。因此,直接 I/O 不存在内核空间缓冲区和用户空间缓冲区之间的数据拷贝。
- 减少数据拷贝次数:在数据传输过程中,避免数据在用户空间缓冲区和系统内核空间缓冲区之间的 CPU 拷贝,以及数据在系统内核空间内的 CPU 拷贝,这也是当前主流零拷贝技术的实现思路。
- 写时复制技术:写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么将其拷贝到自己的进程地址空间中,如果只是数据读取操作则不需要进行拷贝操作。
方法一:用户态直接 I/O
这种方法可以使应用程序或者运行在用户态下的库函数直接访问硬件设备,数据直接跨过内核进行传输,内核在整个数据传输过程除了会进行必要的虚拟存储配置工作之外,不参与其他任何工作,这种方式能够直接绕过内核,极大提高了性能。
缺点:
- 这种方法只能适用于那些不需要内核缓冲区处理的应用程序,这些应用程序通常在进程地址空间有自己的数据缓存机制,称为自缓存应用程序,如数据库管理系统就是一个代表。
- 这种方法直接操作磁盘 I/O,由于 CPU 和磁盘 I/O 之间的执行时间差距,会造成资源的浪费,解决这个问题需要和异步 I/O 结合使用。
方法二:mmap + write
这种方法,使用 mmap 来代替 read,可以减少一次拷贝操作,如下:
buf = mmap(diskfd, len);write(sockfd, buf, len);
应用程序调用 mmap ,磁盘文件中的数据通过 DMA 拷贝到内核缓冲区,接着操作系统会将这个缓冲区与应用程序共享,这样就不用往用户空间拷贝。应用程序调用write ,操作系统直接将数据从内核缓冲区拷贝到 socket 缓冲区,最后再通过 DMA 拷贝到网卡发出去。
用户程序读写数据的流程如下:
- 用户进程通过 mmap() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
- 将用户进程的内核空间的读缓冲区(read buffer)与用户空间的缓存区(user buffer)进行内存地址映射。
- CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
- 上下文从内核态(kernel space)切换回用户态(user space),mmap 系统调用执行返回。
- 用户进程通过 write() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
- CPU 将读缓冲区(read buffer)中的数据拷贝到网络缓冲区(socket buffer)。
- CPU 利用 DMA 控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。
- 上下文从内核态(kernel space)切换回用户态(user space),write 系统调用执行返回。
缺陷:mmap 隐藏着一个陷阱,当 mmap 一个文件时,如果这个文件被另一个进程所截获,那么 write 系统调用会因为访问非法地址被 SIGBUS 信号终止,SIGBUS 默认会杀死进程并产生一个 coredump,如果服务器被这样终止了,那损失就可能不小了。
解决这个问题通常使用文件的租借锁:首先为文件申请一个租借锁,当其他进程想要截断这个文件时,内核会发送一个实时的 RT_SIGNAL_LEASE 信号,告诉当前进程有进程在试图破坏文件,这样 write 在被 SIGBUS 杀死之前,会被中断,返回已经写入的字节数,并设置 errno 为 success。通常的做法是在 mmap 之前加锁,操作完之后解锁。
方法三:Sendfile
从 Linux 2.1 版内核开始,引入了 sendfile,也能减少一次拷贝。
#include<sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
sendfile 是只发生在内核态的数据传输接口,没有用户态的参与,自然避免了用户态数据拷贝。它指定在 in_fd 和 out_fd 之间传输数据,其中,它规定 in_fd 指向的文件必须是可以 mmap 的,out_fd 必须指向一个套接字,也就是规定数据只能从文件传输到套接字,反之则不行。sendfile 不存在像 mmap 时文件被截获的情况,它自带异常处理机制。
用户程序读写数据的流程如下:
- 用户进程通过 sendfile() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
- CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
- CPU 将读缓冲区(read buffer)中的数据拷贝到的网络缓冲区(socket buffer)。
- CPU 利用 DMA 控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。
- 上下文从内核态(kernel space)切换回用户态(user space),Sendfile 系统调用执行返回。
缺陷:能适用于那些不需要用户态处理的应用程序。
方法四:Sendfile + DMA gather copy
常规 sendfile 还有一次内核态的拷贝操作,能不能也把这次拷贝给去掉呢?答案就是这种 DMA 辅助的 sendfile。
这种方法借助硬件的帮助,在数据从内核缓冲区到 socket 缓冲区这一步操作上,并不是拷贝数据,而是拷贝缓冲区描述符,待完成后,DMA 引擎直接将数据从内核缓冲区拷贝到协议引擎中去,避免了最后一次拷贝。
用户程序读写数据的流程如下:
- 用户进程通过 sendfile() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
- CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
- CPU 把读缓冲区(read buffer)的文件描述符(file descriptor)和数据长度拷贝到网络缓冲区(socket buffer)。
- 基于已拷贝的文件描述符(file descriptor)和数据长度,CPU 利用 DMA 控制器的 gather/scatter 操作直接批量地将数据从内核的读缓冲区(read buffer)拷贝到网卡进行数据传输。
- 上下文从内核态(kernel space)切换回用户态(user space),Sendfile 系统调用执行返回。
缺陷:
6. 除了3、4 中的缺陷,还需要硬件以及驱动程序支持。
7. 只适用于将数据从文件拷贝到套接字上。
方法五:Splice
splice 去掉 sendfile 的使用范围限制,可以用于任意两个文件描述符中传输数据。
#define _GNU_SOURCE /* See feature_test_macros(7) */#include <fcntl.h>ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
但是 splice 也有局限,它使用了 Linux 的管道缓冲机制,所以,它的两个文件描述符参数中至少有一个必须是管道设备。
splice 提供了一种流控制的机制,通过预先定义的水印(watermark)来阻塞写请求,有实验表明,利用这种方法将数据从一个磁盘传输到另外一个磁盘会增加 30%-70% 的吞吐量,CPU 负载也会减少一半。
用户程序读写数据的流程如下:
- 用户进程通过 splice() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。
- CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。
- CPU 在内核空间的读缓冲区(read buffer)和网络缓冲区(socket buffer)之间建立管道(pipeline)。
- CPU 利用 DMA 控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。
- 上下文从内核态(kernel space)切换回用户态(user space),Splice 系统调用执行返回。
缺陷:
- 同样只适用于不需要用户态处理的程序。
- 传输描述符至少有一个是管道设备。
方法六:写时复制
在某些情况下,内核缓冲区可能被多个进程所共享,如果某个进程想要这个共享区进行 write 操作,由于 write 不提供任何的锁操作,那么就会对共享区中的数据造成破坏,写时复制就是 Linux 引入来保护数据的。
写时复制,就是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中,这样做并不影响其他进程对这块数据的操作,每个进程要修改的时候才会进行拷贝,所以叫写时拷贝。这种方法在某种程度上能够降低系统开销,如果某个进程永远不会对所访问的数据进行更改,那么也就永远不需要拷贝。
缺陷:需要 MMU 的支持,MMU 需要知道进程地址空间中哪些页面是只读的,当需要往这些页面写数据时,发出一个异常给操作系统内核,内核会分配新的存储空间来供写入的需求。
方法七:缓冲区共享
这种方法完全改写 I/O 操作,因为传统 I/O 接口都是基于数据拷贝的,要避免拷贝,就去掉原先的那套接口,重新改写,所以这种方法是比较全面的零拷贝技术,目前比较成熟的一个方案是最先在 Solaris 上实现的 fbuf (Fast Buffer,快速缓冲区)。
Fbuf 的思想是每个进程都维护着一个缓冲区池,这个缓冲区池能被同时映射到程序地址空间和内核地址空间,内核和用户共享这个缓冲区池,这样就避免了拷贝。
缺陷:
- 管理共享缓冲区池需要应用程序、网络软件、以及设备驱动程序之间的紧密合作。
- 改写 API ,尚处于试验阶段。
零拷贝对比
无论是传统 I/O 拷贝方式还是引入零拷贝的方式,2 次 DMA Copy 是都少不了的,因为两次 DMA 都是依赖硬件完成的。
下面从 CPU 拷贝次数、DMA 拷贝次数以及系统调用几个方面总结一下上述几种 I/O 拷贝方式的差别:
参考文章
https://mp.weixin.qq.com/s/3H26Yqa8pp5g3BYEGgaAXw
https://mp.weixin.qq.com/s/GtNrVAvqsnzYSoGiqoI-0A
https://mp.weixin.qq.com/s/mZujKx1bKl1T6gEI1s400Q
Linux 操作系统原理 — 零拷贝技术相关推荐
- Linux 中的零拷贝技术,第 2 部分
技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...
- Linux 中的零拷贝技术
引言 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输.这样做最大的好处是可以减 ...
- Linux网络处理“零拷贝”技术mmap()内核进程间通信设计8086分页管理——摆在一起来谈谈...
Jack:最近听说了网络处理的"零拷贝"技术,觉得非常神奇,在网上查阅了很多资料.不过,并不是太明白--知其然,而不知其所以然.你能通俗地解释一下吗? 我:这是一个相对比较复杂的话 ...
- linux 中的零拷贝技术,第 2 部分,Linux的零拷贝技术
8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 不是零拷贝的情况下是如何进行的,有什么不好的地方? 一次读取磁盘文件发送到网络的数据的拷贝过程: 用户态:用户进程开辟的 ...
- 面试题:如何理解 Linux 的零拷贝技术?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 本文讲解 Linux 的零拷贝技术,云计算是一门很庞大的技术学科, ...
- linux 占用缓存前10_Linux 中的零拷贝技术
什么是拷贝 要知道零拷贝,需要先知道什么是拷贝.其实这个"拷贝"来自于 Linux 中的标准 I/O 接口的基本数据拷贝的操作,而这一操作会导致数据在操作系统内核地址空间的缓冲区和 ...
- 系统在此应用程序中检测到基于堆栈的缓冲区_Linux 中的零拷贝技术
转载:Linux 中的零拷贝技术,第 1 部分 引言 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定 ...
- Linux - 零拷贝技术
Linux - 零拷贝技术 前言 一. 相关概念 1.1 缓冲区 1.1.1 内核缓冲区 1.1.2 用户缓冲区 1.2 DMA技术 1.3 虚拟内存 二. 零拷贝 2.1 传统文件传输流程 2.2 ...
- Linux Zero-copy零拷贝技术:源码示例
<Linux Zero-copy零拷贝技术:源码示例> <Linux Zero-copy零拷贝技术全面揭秘> <什么是mmap?零拷贝?DMA?> <Linu ...
最新文章
- Django 缓存6.2
- wireshrk中的名词说明
- JZOJ 5404. 【NOIP2017提高A组模拟10.10】Graph
- rs485编程java_串行编程RS485
- sqlserver 如何把一列分为一行显示_SqlServer数据库如何快速修改表的一列值
- UE4 iOS游戏开发
- java保存图书每日的交易记录
- 微信小程序|开发实战篇之四
- TOMCAT安装指南
- 【Python数据分析】假设检验的基本思想、原理和步骤
- Android蜗牛睡眠技术文档,蜗牛睡眠app 问题提问集中贴 常见问题汇总
- python多线程网易云歌单
- 45岁了社保交了25年不交了行吗?
- 链接元宇宙,开启新纪元
- python中的row函数均方_均方位移python(Mean square displacement python)
- Proteus电路图绘制与CubeMX生成框架下填充Keil 5代码的联调仿真
- AGC001E BBQ Hard 组合计数
- 用 visual foxpro 也可以编写“迅雷下载”
- 我们这一代人的困惑 - 转载
- SQL2000 安装详解~及安装问题解决方法
热门文章
- BeagleBone硬件概览Ethernet端口板载LEDc重置按钮等介绍
- Kali Linux安全渗透教程(内部资料)
- sysbench mysql 结果_sysbench使用和结果说明
- 信息处理进入了计算机领域,信 息 处 理 进 入 了 计 算 机 领 域 ,实 质 是 进 入 了()的 领 域 。...
- 来人呐,有人又要抢钱啦!
- 小伙一本正经用石头打造CPU,号称99秒“解决”芯片危机
- 搜狗发布全球首个手语AI合成主播,用技术造福听障人群
- 谷歌联手马斯克,低延迟星链云计算下半年可用
- 对话嬴彻科技CEO马喆人:L3才是自动驾驶货运的本质拐点
- Linux基础命令---dumpe2fs