Linux零拷贝原理
Linux零拷贝原理
前言
磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝、直接 I/O、异步 I/O 等等,这些优化的目的就是为了提高系统的吞吐量。
DMA技术
在没有DMA技术之前,IO过程是这样的:
可以看到,整个数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。当我们用千兆网卡或者硬盘传输大量数据的时候,都用 CPU 来搬运的话,肯定忙不过来。
DMA即直接内存访问(*Direct Memory Access*) 技术:在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。
也就是说等待磁盘读取数据并将其拷贝到内核空间这部分工作交给了DMA控制器,解放CPU,从而让CPU做更重要的事情。
文件传输过程及优化
如果我们需要将文件读取出来,然后通过网络协议发送给客户端
read(file,buf,len);
write(socket,buf,len);
传统 I/O 的工作方式是,数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。
期间一共发生4次用户态和内核态的上下文切换,因为一次系统调用需要来回两次切换。
还发生了4次数据拷贝操作,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝的。过多的数据拷贝无疑会消耗 CPU 资源,大大降低了系统性能。
这种简单又传统的文件传输方式,存在冗余的上文切换和数据拷贝,在高并发系统里是非常糟糕的,多了很多不必要的开销,会严重影响系统性能。
所以,要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数。
如何减少内核态和用户态之间的切换的次数呢?
读取磁盘数据的时候,之所以要发生上下文切换,这是因为用户空间没有权限操作磁盘或网卡,内核的权限最高,这些操作设备的过程都需要交由操作系统内核来完成,所以一般要通过内核去完成某些任务的时候,就需要使用操作系统提供的系统调用函数。
而一次系统调用必然会发生 2 次上下文切换:首先从用户态切换到内核态,当内核执行完任务后,再切换回用户态交由进程代码执行。
所以,要想减少上下文切换到次数,就要减少系统调用的次数。
如何减少内存拷贝次数呢?
「从内核的读缓冲区拷贝到用户的缓冲区里,再从用户的缓冲区里拷贝到 socket 的缓冲区里」,这个过程是没有必要的。
因为文件传输的应用场景中,在用户空间我们并不会对数据「再加工」,所以数据实际上可以不用搬运到用户空间,因此用户的缓冲区是没有必要存在的。
零拷贝技术
零拷贝(Zero-Copy)是一种 I/O
操作优化技术,可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间。
因此零拷贝技术只传输文件的原始内容,不允许进程对文件内容作进一步的加工的,比如压缩数据和加密。
目前只有在使用NIO和Epoll传输时才可以使用该特性。
零拷贝技术实现的方式通常有 2 种:
mmap + write
buf = mmap(file, len);
write(sockfd, buf, len);现在操作系统都使用虚拟内存,好处是:
1、多个虚拟内存可以指向同一块物理地址
2、虚拟内存的空间可以远大于物理内存的空间
正是有了虚拟内存的出现,我们可以把内核空间和用户空间的虚拟地址映射到同一块物理地址上,这样在IO操作时就不用来回复制来。
mmap()
系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。当DMA将数据拷贝到内核缓冲区中后,应用进程跟内核共享这个缓冲区。
应用进程再调用
write()
,操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据。但这还不是最理想的零拷贝,因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,而且仍然需要 4 次上下文切换,因为系统调用还是 2 次。
sendfile
在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数
sendfile()
。首先,它可以替代前面的
read()
和write()
这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。其次,该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝。
这也不是真正的零拷贝,因为还是有CPU的参与。
于是,从 Linux 内核
2.4
版本开始起,对于支持网卡支持 SG-DMA 技术的情况下,sendfile()
系统调用的过程发生了点变化。CPU不做数据拷贝,而只是将缓存区里的内存地址和偏移量记录到相应的socket缓冲区里,SG-DMA 控制器直接将内核缓存中的数据拷贝到网卡的缓冲区里。整个过程只有2次数据拷贝,两次上下文切换。整体来说可以把文件传输性能提升一倍以上。
这就是所谓的零拷贝技术,全程没有CPU来搬运数据,所有数据都是通过DMA完成的。
splice
splice 系统调用可以在内核空间的读缓冲区(read buffer)和网络缓冲区(socket buffer)之间建立管道(pipeline),从而避免了两者之间的 CPU 拷贝操作。
splice使用了 Linux 的管道缓冲机制,可以用于任意两个文件描述符中传输数据,但是它的两个文件描述符参数中有一个必须是管道设备。
整个过程发生2次上下文切换和2次DMA拷贝,CPU只是在内核缓冲区和socket缓冲区之间建立管道。
kafka就使用了零拷贝技术,这也是 Kafka 在处理海量数据为什么这么快的原因之一。
kafka文件传输的代码最终调用的是JAVA NIO库里的transferTo方法,如果 Linux 系统支持 sendfile()
系统调用,那么 transferTo()
实际上最后就会使用到 sendfile()
系统调用函数。
nginx默认也是开启零拷贝技术。
PageCache(内核缓冲区)作用
PageCache就是上面所说的内核缓冲区,它实际上是磁盘高速缓存。
零拷贝技术是基于 PageCache 的,PageCache 会缓存最近访问的数据,提升了访问缓存数据的性能,同时,为了解决机械硬盘寻址慢的问题,它还协助 I/O 调度算法实现了 IO 合并与预读,这也是顺序读比随机读性能好的原因。这些优势,进一步提升了零拷贝的性能。
用PageCache 来缓存最近被访问的数据,读磁盘数据的时候,优先在 PageCache 找,如果数据存在则可以直接返回;如果没有,则从磁盘中读取,然后缓存 PageCache 中。
还有一点,读取磁盘数据的时候,需要找到数据所在的位置,但是对于机械磁盘来说,就是通过磁头旋转到数据所在的扇区,再开始「顺序」读取数据,但是旋转磁头这个物理动作是非常耗时的,为了降低它的影响,PageCache 使用了「预读功能」。
比如,假设 read 方法每次只会读 32 KB
的字节,虽然 read 刚开始只会读 0 ~ 32 KB 的字节,但内核会把其后面的 32~64 KB 也读取到 PageCache,这样后面读取 32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出 PageCache 前,进程读取到它了,收益就非常大。
所以,PageCache 的优点主要是两个:
- 缓存最近被访问的数据;
- 预读功能;
这两个做法,将大大提高读写磁盘的性能。
但是,在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DMA 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能。
这是因为如果你有很多 GB 级别文件需要传输,每当用户访问这些大文件的时候,内核就会把它们载入 PageCache 中,于是 PageCache 空间很快被这些大文件占满。
另外,由于文件太大,可能某些部分的文件数据被再次访问的概率比较低,无法享受缓存带来的优势。
所以,针对大文件的传输,不应该使用 PageCache,也就是说不应该使用零拷贝技术,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache,反而由于多做一次的数据拷贝损失性能。这样在高并发的环境下,会带来严重的性能问题。
大文件传输实现方式
前面说到的所有方式都有一个特点:就是用户进程需要阻塞等待,因此,这种操作被称为同步阻塞IO。
对于阻塞的问题,可以用异步 I/O 来解决。
用户进程向内核发起IO请求后直接返回,处理其他任务。
而内核将磁盘控制器缓冲区的数据直接拷贝到用户缓冲区,然后通知用户进程来读。
异步IO没有涉及到PageCache,这种绕开PageCache的IO也叫直接IO,使用PageCache的IO叫做缓存IO。
由于 CPU 和磁盘 I/O 之间的执行时间差距,会造成大量资源的浪费,因此直接IO一般都是和异步IO结合才有意义。
前面也提到,大文件的传输不应该使用 PageCache,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache。
于是,在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术。
而传输小文件时,使用零拷贝技术。
知识点回顾
虚拟内存
虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。
虚拟内存通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来。 每个进程所能使用的虚拟地址大小和 CPU 位数有关。在 32 位的系统上,虚拟地址空间大小是 2 ^ 32 = 4G。
虚拟内存的好处:
- 更大的地址空间,并且地址空间时连续的,使得程序编写链接更简单
- 地址隔离:进程之间不会影响
- 数据保护:每块虚拟内存都有相应的读写属性,保护程序代码不被修改,增加系统安全性
- 内存映射:不同的虚拟内存可以映射到同一块物理内存上,可以实现进程之间相互共享数据
- 物理内存管理:应用程序申请和释放的空间操作都是针对虚拟内存的,实际的物理内存管理是由操作系统完成,从而可以更好的利用内存,平衡进程间对内存的需求。
内核态和用户态
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间(Kernel-space),一部分是用户空间(User-space)。 内核进程和用户进程所占的虚拟内存比例是 1:3
在 Linux 系统中,内核模块运行在内核空间,对应的进程处于内核态;内核空间总是驻留在内存中,它是为操作系统的内核保留的。内核态可以执行任意命令,调用系统的一切资源。
而用户程序运行在用户空间,对应的进程处于用户态。处于用户态的进程不能访问内核空间中的数据,也不能直接调用内核函数的 ,因此要进行系统调用的时候,就要将进程切换到内核态才行。用户态只能执行简单的运算,不能直接调用系统资源。用户态必须通过系统接口(System Call),才能向内核发出指令。
写时复制
在某些情况下,内核缓冲区可能被多个进程所共享,如果某个进程想要这个共享区进行 write 操作,由于 write 不提供任何的锁操作,那么就会对共享区中的数据造成破坏,写时复制的引入就是 Linux 用来保护数据的。
写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中。这样做并不影响其他进程对这块数据的操作,每个进程要修改的时候才会进行拷贝,所以叫写时拷贝。这种方法在某种程度上能够降低系统开销,如果某个进程永远不会对所访问的数据进行更改,那么也就永远不需要拷贝。
BIO、NIO、AIO
这些是网络编程模型,由于socket也是一种特殊的文件,从网络中收到数据和从磁盘读取数据过程差不多,所以被称为IO模型,一个IO请求就可以看成一个网络连接。
同步:同步是用户线程需要等待内核IO操作完成,才能继续执行;
异步:异步不需要等内核完成,内核完成后会通知用户线程来读取。
阻塞:阻塞时用户线程需要等IO操作彻底完成后才能返回用户空间。
非阻塞:非阻塞是IO操作被调用后立即返回用户状态值。
BIO(Blocking IO):同步阻塞模型,传统的IO模型,每个IO操作对应一个Thread,上面我们举的例子都是传统IO。
NIO(NonBlocking IO):同步非阻塞模型,虽然可以立即返回用户空间,但用户线程需要轮询来读取数据。
AIO:异步非阻塞模型。
Linux零拷贝原理的更多相关文章
- 【面试普通人VS高手】Kafka的零拷贝原理?
最近一个学员去滴滴面试,在第二面的时候遇到了这个问题: "请你简单说一下Kafka的零拷贝原理" 然后那个学员努力在大脑里检索了很久,没有回答上来. 那么今天,我们基于这个问题来看 ...
- Netty 零拷贝(一)Linux 零拷贝
Netty 零拷贝(一)Linux 零拷贝 本文探讨 Linux 中主要的几种零拷贝技术以及零拷贝技术适用的场景. 一.几个重要的概念 1.1 用户空间与内核空间 操作系统的核心是内核,独立于普通的应 ...
- Linux零拷贝技术 直接 io
Linux零拷贝技术 .https://kknews.cc/code/2yeazxe.html https://zhuanlan.zhihu.com/p/76640160 https://clou ...
- NIO学习笔记,从Linux IO演化模型到Netty—— Linux零拷贝
这里只是感性地认识Linux零拷贝,不涉及具体细节. 1.Linux传统的数据拷贝 用户进程是不能直接访问文件系统的,要先切换到内核态,发起系统调用,DMA把磁盘中的数据写入内核空间,内核再把数据拷贝 ...
- Linux零拷贝技术
本文转载自Linux零拷贝技术 导语 本文讲解 Linux 的零拷贝技术,云计算是一门很庞大的技术学科,融合了很多技术,Linux 算是比较基础的技术,所以,学好 Linux 对于云计算的学习会有比较 ...
- Linux零拷贝技术,看完这篇文章就懂了
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫. 本文讲解 ...
- Linux 零拷贝技术
简介 零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道.实现高速服务器和路由器的关键技术之一. ...
- Linux "零拷贝" sendfile函数中文说明及实际操作分析
Sendfile函数说明 #include ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); sendfile ...
- 框架篇:Linux零拷贝机制和FileChannel
前言 大白话解释,零拷贝就是没有把数据从一个存储区域拷贝到另一个存储区域.但是没有数据的复制,怎么可能实现数据的传输呢?其实我们在java NIO.netty.kafka遇到的零拷贝,并不是不复制数据 ...
随机推荐
- # Vue3 toRef 和 toRefs 函数
Vue3 toRef 和 toRefs 函数 上一篇博文介绍了 vue3 里面的 ref 函数和 reactive 函数,实现响应式数据,今天主要来说一下 toRef 函数和 toRefs 函数的基本 ...
- P6622 信号传递 做题感想
题目链接 前言 在这里分享两种的做法. 一种是我第一直觉的 模拟退火.(也就是骗分) 还有一种是看题解才搞懂的神仙折半搜索加上 dp . 模拟退火 众所周知,模拟退火 是我这种没脑子选手用来骗分的好算 ...
- PTA(BasicLevel)-1018 锤子剪刀布
一.问题定义 大家应该都会玩"锤子剪刀布"的游戏:两人同时给出手势,胜负规则如下: 剪刀 > 布, 锤子 > 剪刀, 布 > 锤子 现给出两人的交 ...
- mesi--cpu内存一致性协议
目录 cpu缓存一致性问题 mesi协议 mesi协议4种状态,及状态转换 模拟工具演示 cpu缓存一致性问题 一个服务器中有多个核,每个核中有多个cpu,每个cpu有多个线程.缓存最少分为3级,1级 ...
- JDK的下载与安装和环境变量的配置
一.jdk下载打开浏览器在地址栏输入: http://www.oracle.com ,进入Oracle官网主页面,选择 Products-----Java---->Download Java . ...
- 集合-Collection工具类
一.概念 二.常用方法 1.Collection和Collections的区别 Collection:是创建集合的接口,Collections是一个操作Collection工具类 2.常用方法 点击查 ...
- SpringMVC pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...
- 云ATM架构设计
云ATM架构设计 启动程序(Start.java) public class Start { public static void main(String[] args) { MainView vie ...
- 中国顶级程序员,从金山WPS走出来,自研了“表格编程”神器
程序员的圈子里有很多如明星般闪耀的牛人! 有中国"第一代程序员"--求伯君,有在微信获得巨大成功的张小龙,有图灵奖获得者姚期智,有商业巨子张一鸣,更有开源影响力人物--章亦春. 章 ...
- 【机器学习基础】——另一个视角解释SVM
SVM的另一种解释 前面已经较为详细地对SVM进行了推导,前面有提到SVM可以利用梯度下降来进行求解,但并未进行详细的解释,本节主要从另一个视角对SVM进行解释,首先先回顾之前有关SVM的有关内容,然 ...