零拷贝Zero-copy技术详解

普通模式数据交互

普通模式数据交互分为仅CPU和CPU&DMA两种方式。

仅CPU方式

read流程:

  1. 当程序执行read()时,调用syscall从用户态切换到内核态;
  2. CPU向磁盘发起IO请求,磁盘收到后开始Ready数据;
  3. 磁盘将数据放到磁盘缓冲区之后,向CPU发起I/O中断,报告CPU数据已经Ready了(可见其使用了 Page Cache 机制);
  4. CPU收到磁盘控制器的I/O中断之后,把磁盘缓冲区数据拷贝到内核缓冲区中;
  5. 然后再把数据从内核缓冲区拷贝到用户缓冲区中
  6. 完成之后syscall返回,也就是read()的返回,从内核态切换到用户态;
CPU.png

小结:

  • 仅CPU方式read流程经历了2次CPU拷贝,2次空间切换。
  • 而write流程也是2次CPU拷贝,2次空间切换。

CPU&DMA

我们可以看出仅CPU方式需要跟硬件交互,很耗CPU,这是在浪费CPU资源。
于是就有了DMA(Direct Memory Access, 直接内存访问)来分担CPU的活。

有必要先讲一下DMA是什么,DMA是一种硬件设备绕开CPU独立直接访问内存的机制。所以DMA在一定程度上解放了CPU,与硬件交互的工作让硬件直接自己做了,提高了CPU效率。支持DMA的有网卡、声卡、显卡、磁盘控制器等。

CPU&DMA方式对比仅CPU方式的区别就是,CPU不再和磁盘直接交互,而是DMA和磁盘交互,使得读写运行效率更高。
并且在DMA读写硬件时,CPU可以执行其他任务。

read流程:

  1. 当程序执行read()时,调用syscall从用户态切换到内核态;
  2. DMA向磁盘发起IO请求,磁盘收到后开始Ready数据;
  3. 磁盘将数据放到磁盘缓冲区之后,向DMA发起信号,报告DMA数据已经Ready了;
  4. DMA收到磁盘控制器的I/O中断之后,把磁盘缓冲区数据拷贝到内核缓冲区中;
  5. 然后CPU再把数据从内核缓冲区拷贝到用户缓冲区中;
  6. 完成之后syscall返回,也就是read()的返回,从内核态切换到用户态。
CPU&DMA.png

小结:

  • CPU&DMA方式read流程经历了1次CPU拷贝,1次DMA拷贝,2次空间切换。
  • 而write流程也是1次CPU拷贝,1次DMA拷贝,2次空间切换。

Zero-Copy技术

通过上面两种普通模式数据交互的方式发现,如果我们执行read和write操作,会经历四次数据拷贝和四次空间切换。基于这两点我们可以做如下优化:

  • 内核缓冲区->用户缓冲区->套接字缓冲区这两次数据拷贝是有冗余的,我们可以优化成从内核缓冲区直接拷贝到套接字缓冲区;
  • 四次空间切换是比较耗时的操作,我们可以减少空间切换次数来增加效率。

当程序中有系统调用语句,程序执行到系统调用时,首先使用类似int 80H的软中断指令,保存现场,去系统调用,在内核态执行,然后恢复现场。每个进程都会有两个栈,一个内核态栈和一个用户态栈。当int中断执行时就会由用户态栈转向内核态栈,系统调用时需要进行栈的切换。而且内核代码对用户不信任,需要进行额外的检查。系统调用的返回过程有很多额外工作,比如检查是否需要调度等。

系统调用一般都需要保存用户程序得上下文(context),在进入内核的时候需要保存用户态的寄存器,在内核态返回用户态的时候会恢复这些寄存器的内容。这是一个开销的地方。 如果需要在不同用户程序间切换的话,还要更新cr3寄存器,这样会更换每个程序的虚拟内存到物理内存映射表的地址,也是一个比较高负担的操作

基于上面两点优化,实现了这些零拷贝技术:mmap+write、sendfile、sendfile+DMA收集、splice等。

mmap+write

mmap即memory map,也就是内存映射。我在mmap的使用一文中详细介绍了mmap的用法、特点、注意事项等信息,感兴趣的同学可以去看看。

mmap+write流程:

  1. 用户进程调用 mmap(),从用户态切换到内核态,将内核缓冲区映射到用户缓存区;
  2. DMA 控制器将数据从硬盘拷贝到内核缓冲区;
  3. mmap() 返回,上下文从内核态切换回用户态;
  4. 用户进程调用 write(),从用户态切换到内核态;
  5. CPU 将内核缓冲区中的数据拷贝到的套接字缓冲区;
  6. DMA 控制器将数据从套接字缓冲区拷贝到网卡完成数据传输;
  7. write() 返回,从内核态切换回用户态。
MMap.png

小结:

  • MMap+write流程经历了1次CPU拷贝,2次DMA拷贝,4次空间切换。比CPU&DMA少了一次CPU拷贝。

sendfile

不管是ready+write还是mmap+write,都是使用两个接口来做数据传输,按照第二点优化思路,我们可以用一个系统调用接口来实现,这样空间切换就会从四次缩为两次。
sendfile是Linux 内核2.1版本中被引入,与mmap+write一样,sendfile是从内核缓冲区拷贝到socket缓冲区,流程如下:

Sendfile.png

小结:

  • sendfile流程经历了1次CPU拷贝,2次DMA拷贝,2次空间切换。比CPU&DMA少了一次CPU拷贝和两次空间切换,由于数据不经过用户缓冲区,因此数据无法被修改。

sendfile+DMA

从 Linux 内核 2.4 版本开始起,sendfile() 系统调用的过程发生了点变化,具体过程如下:

  • 通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
  • 将内核空间缓冲区中对应的数据描述信息(文件描述符、地址偏移量等信息)记录到socket缓冲区中。
  • DMA控制器根据socket缓冲区中的地址和偏移量将数据从内核缓冲区拷贝到网卡中,从而省去了内核空间中仅剩1次CPU拷贝。

这种方式才是实现了真正的零拷贝,真正的解放了CPU。但是这种方式需要硬件DMA控制器的配合。流程图示如下:


sendfile+dma.png

小结:

  • sendfile+DMA流程经历了0次CPU拷贝,2次DMA拷贝,2次空间切换。比CPU&DMA少了两次CPU拷贝和两次空间切换,需要DMA支持,数据仍然无法被修改。

splice

  • splice系统调用是Linux 在 2.6 版本引入的,其不需要硬件支持,并且不再限定于socket上,实现两个普通文件之间的数据零拷贝。
  • splice 系统调用可以在内核缓冲区和socket缓冲区之间建立管道来传输数据,避免了两者之间的 CPU 拷贝操作。
  • splice也有一些局限,它的两个文件描述符参数中有一个必须是管道设备。
splice.png

小结:

  • splice流程经历了0次CPU拷贝,2次DMA拷贝,2次空间切换。比CPU&DMA少了两次CPU拷贝和两次空间切换,数据仍然无法被修改,但是不依赖硬件,只要有一个管道设备即可。

大文件传输

内核缓冲区

为什么磁盘数据拷贝到网卡中,需要经过内核缓冲区呢?原因是磁盘的读写速度太慢。
内核缓冲区工作:

  • 缓存最近被访问的数据。最近访问过的数据接下来很可能还会被访问,所以利用PageCache 缓存最近被访问的数据,读磁盘数据的时候,优先在 PageCache 找,如果数据存在则可以直接返回;如果没有,则从磁盘中读取,然后缓存在 PageCache 中。当PageCache的空间不足时,淘汰最久未被访问的缓存。
  • 预读功能。利用空间局部性原理,假设 read 方法每次只会读 32 KB 的字节,虽然 read 刚开始只会读 0 ~ 32 KB 的字节,但内核会把其后面的 32~64 KB 也读取到 PageCache,这样后面读取 32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出 PageCache 前,进程读取到它了,收益就非常大。
  • 内核的 I/O 调度算法会缓存尽可能多的 I/O 请求在 内核缓存区中,最后合并成一个更大的 I/O 请求再发给磁盘,这样做是为了减少磁盘的寻址操作。

大文件的缓存命中率不高,并且可能内核缓存区被大文件占据,而导致其他的热点小文件无法利用内核缓存区。
所以适用内核缓冲器时,不适合大文件传输。也就是说零拷贝技术不适合大文件传输。

异步IO

我们了解了为什么使用内核缓冲区后,就能自然而然的想到,如果可以忍受磁盘读写速度就可以避免使用内核缓冲区。
异步IO就是可以忍受磁盘读写速度,因为线程不用等待异步IO的执行结果。
所以异步IO可以做到直接从磁盘缓冲区拷贝到用户缓冲区,适用于大文件传输。

总结

本文介绍了CPU和CPU&DMA两种数据传输的流程:

  • 仅CPU方式的read+write流程经历了4次CPU拷贝,4次空间切换
  • CPU&DMA方式的read+write流程经历了2次CPU拷贝,2次DMA拷贝,4次空间切换。

然后介绍了优化的点分别是减少数据拷贝次数和介绍空间切换次数,于是引出了零拷贝技术:mmap+write、sendfile、sendfile+DMA收集、splice等。

  • MMap+write流程经历了1次CPU拷贝,2次DMA拷贝,4次空间切换。比CPU&DMA少了一次CPU拷贝。
  • sendfile流程经历了1次CPU拷贝,2次DMA拷贝,2次空间切换。比CPU&DMA少了一次CPU拷贝和两次空间切换,由于数据不经过用户缓冲区,因此数据无法被修改。
  • sendfile+DMA流程经历了0次CPU拷贝,2次DMA拷贝,2次空间切换。比CPU&DMA少了两次CPU拷贝和两次空间切换,需要DMA支持,数据仍然无法被修改。
  • splice流程经历了0次CPU拷贝,2次DMA拷贝,2次空间切换。比CPU&DMA少了两次CPU拷贝和两次空间切换,数据仍然无法被修改,但是不依赖硬件,只要有一个管道设备即可。

最后介绍了内核缓冲区的作用,也引出了异步IO可以处理大文件传输工作。

引用

wiki Page_cache
wiki Zero-copy
wiki Mmap
man2 sendfile
wiki Splice

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容