欢迎光临
我们一直在努力

Zero-Copy零拷贝技术详解

深入理解零拷贝技术:从 I/O 瓶颈到高性能网络

本文从传统 I/O 的性能瓶颈出发,剖析零拷贝(Zero-Copy)的原理、实现方式(mmap、sendfile、SG-DMA)及其在高性能 Web、消息队列、文件系统等场景中的应用,帮助开发者理解并解决高并发下的 I/O 性能问题。


目录

  • 为什么需要零拷贝?
  • 传统 I/O 模型的性能瓶颈
  • 零拷贝的核心目标与实现
  • PageCache 与零拷贝
  • mmap + write:减少一次拷贝
  • sendfile:内核态直传
  • sendfile + SG-DMA:真正的零拷贝
  • splice 与 tee
  • 应用场景与价值
  • 实践:Java NIO 与 Netty
  • 小结与速查
  • 延伸阅读

  • 一、为什么需要零拷贝?

    在高速网络与海量数据场景下,用户对传输速度和响应延迟要求越来越高。无论是高清视频点播、大文件下载,还是高频交易系统,都依赖高效的 I/O 能力。传统 I/O 模型在数据路径上存在多次数据拷贝和频繁的内核态–用户态切换,在高并发、高带宽下会迅速耗尽 CPU,成为性能瓶颈。零拷贝技术通过减少拷贝次数与上下文切换,从根本上优化这条路径。

    阅读导图:

    #mermaid-svg-0ddfJvkzujn4jgyz{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0ddfJvkzujn4jgyz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0ddfJvkzujn4jgyz .error-icon{fill:#552222;}#mermaid-svg-0ddfJvkzujn4jgyz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0ddfJvkzujn4jgyz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0ddfJvkzujn4jgyz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0ddfJvkzujn4jgyz .marker.cross{stroke:#333333;}#mermaid-svg-0ddfJvkzujn4jgyz svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0ddfJvkzujn4jgyz p{margin:0;}#mermaid-svg-0ddfJvkzujn4jgyz .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-0ddfJvkzujn4jgyz .cluster-label text{fill:#333;}#mermaid-svg-0ddfJvkzujn4jgyz .cluster-label span{color:#333;}#mermaid-svg-0ddfJvkzujn4jgyz .cluster-label span p{background-color:transparent;}#mermaid-svg-0ddfJvkzujn4jgyz .label text,#mermaid-svg-0ddfJvkzujn4jgyz span{fill:#333;color:#333;}#mermaid-svg-0ddfJvkzujn4jgyz .node rect,#mermaid-svg-0ddfJvkzujn4jgyz .node circle,#mermaid-svg-0ddfJvkzujn4jgyz .node ellipse,#mermaid-svg-0ddfJvkzujn4jgyz .node polygon,#mermaid-svg-0ddfJvkzujn4jgyz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0ddfJvkzujn4jgyz .rough-node .label text,#mermaid-svg-0ddfJvkzujn4jgyz .node .label text,#mermaid-svg-0ddfJvkzujn4jgyz .image-shape .label,#mermaid-svg-0ddfJvkzujn4jgyz .icon-shape .label{text-anchor:middle;}#mermaid-svg-0ddfJvkzujn4jgyz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0ddfJvkzujn4jgyz .rough-node .label,#mermaid-svg-0ddfJvkzujn4jgyz .node .label,#mermaid-svg-0ddfJvkzujn4jgyz .image-shape .label,#mermaid-svg-0ddfJvkzujn4jgyz .icon-shape .label{text-align:center;}#mermaid-svg-0ddfJvkzujn4jgyz .node.clickable{cursor:pointer;}#mermaid-svg-0ddfJvkzujn4jgyz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0ddfJvkzujn4jgyz .arrowheadPath{fill:#333333;}#mermaid-svg-0ddfJvkzujn4jgyz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0ddfJvkzujn4jgyz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0ddfJvkzujn4jgyz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0ddfJvkzujn4jgyz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0ddfJvkzujn4jgyz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0ddfJvkzujn4jgyz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0ddfJvkzujn4jgyz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0ddfJvkzujn4jgyz .cluster text{fill:#333;}#mermaid-svg-0ddfJvkzujn4jgyz .cluster span{color:#333;}#mermaid-svg-0ddfJvkzujn4jgyz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0ddfJvkzujn4jgyz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0ddfJvkzujn4jgyz rect.text{fill:none;stroke-width:0;}#mermaid-svg-0ddfJvkzujn4jgyz .icon-shape,#mermaid-svg-0ddfJvkzujn4jgyz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0ddfJvkzujn4jgyz .icon-shape p,#mermaid-svg-0ddfJvkzujn4jgyz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0ddfJvkzujn4jgyz .icon-shape rect,#mermaid-svg-0ddfJvkzujn4jgyz .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0ddfJvkzujn4jgyz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0ddfJvkzujn4jgyz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0ddfJvkzujn4jgyz :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    传统 read/write

    4 次拷贝 + 4 次切换

    mmap/sendfile

    零拷贝

    高吞吐 / 低 CPU


    二、传统 I/O 模型的性能瓶颈

    以典型场景「从服务器读文件并经由网络发给客户端」为例:应用先 read() 把数据从磁盘读到用户缓冲区,再 write() 把数据从用户缓冲区写到 Socket,数据会经过内核缓冲区、用户缓冲区、Socket 缓冲区的多次搬运。

    2.1 数据路径示意

    #mermaid-svg-2tLU2rW6Up0OxIuX{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2tLU2rW6Up0OxIuX .error-icon{fill:#552222;}#mermaid-svg-2tLU2rW6Up0OxIuX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2tLU2rW6Up0OxIuX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2tLU2rW6Up0OxIuX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2tLU2rW6Up0OxIuX .marker.cross{stroke:#333333;}#mermaid-svg-2tLU2rW6Up0OxIuX svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2tLU2rW6Up0OxIuX p{margin:0;}#mermaid-svg-2tLU2rW6Up0OxIuX .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX .cluster-label text{fill:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX .cluster-label span{color:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX .cluster-label span p{background-color:transparent;}#mermaid-svg-2tLU2rW6Up0OxIuX .label text,#mermaid-svg-2tLU2rW6Up0OxIuX span{fill:#333;color:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX .node rect,#mermaid-svg-2tLU2rW6Up0OxIuX .node circle,#mermaid-svg-2tLU2rW6Up0OxIuX .node ellipse,#mermaid-svg-2tLU2rW6Up0OxIuX .node polygon,#mermaid-svg-2tLU2rW6Up0OxIuX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2tLU2rW6Up0OxIuX .rough-node .label text,#mermaid-svg-2tLU2rW6Up0OxIuX .node .label text,#mermaid-svg-2tLU2rW6Up0OxIuX .image-shape .label,#mermaid-svg-2tLU2rW6Up0OxIuX .icon-shape .label{text-anchor:middle;}#mermaid-svg-2tLU2rW6Up0OxIuX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2tLU2rW6Up0OxIuX .rough-node .label,#mermaid-svg-2tLU2rW6Up0OxIuX .node .label,#mermaid-svg-2tLU2rW6Up0OxIuX .image-shape .label,#mermaid-svg-2tLU2rW6Up0OxIuX .icon-shape .label{text-align:center;}#mermaid-svg-2tLU2rW6Up0OxIuX .node.clickable{cursor:pointer;}#mermaid-svg-2tLU2rW6Up0OxIuX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2tLU2rW6Up0OxIuX .arrowheadPath{fill:#333333;}#mermaid-svg-2tLU2rW6Up0OxIuX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2tLU2rW6Up0OxIuX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2tLU2rW6Up0OxIuX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2tLU2rW6Up0OxIuX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2tLU2rW6Up0OxIuX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2tLU2rW6Up0OxIuX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2tLU2rW6Up0OxIuX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2tLU2rW6Up0OxIuX .cluster text{fill:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX .cluster span{color:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2tLU2rW6Up0OxIuX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2tLU2rW6Up0OxIuX rect.text{fill:none;stroke-width:0;}#mermaid-svg-2tLU2rW6Up0OxIuX .icon-shape,#mermaid-svg-2tLU2rW6Up0OxIuX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2tLU2rW6Up0OxIuX .icon-shape p,#mermaid-svg-2tLU2rW6Up0OxIuX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2tLU2rW6Up0OxIuX .icon-shape rect,#mermaid-svg-2tLU2rW6Up0OxIuX .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2tLU2rW6Up0OxIuX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2tLU2rW6Up0OxIuX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2tLU2rW6Up0OxIuX :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    传统IO

    1. DMA Copy

    2. CPU Copy

    3. CPU Copy

    4. DMA Copy

    硬盘

    内核 Page Cache

    用户空间 App Buffer

    内核 Socket Buffer

    网卡

    2.2 四次上下文切换与四次拷贝

    类型次数说明
    上下文切换 4 次 read() 进入内核 → 返回用户态 → write() 进入内核 → 返回用户态;每次涉及寄存器、栈、MMU 等切换
    DMA 拷贝 2 次 磁盘 → 内核缓冲区;Socket 缓冲区 → 网卡;由 DMA 完成,不占 CPU
    CPU 拷贝 2 次 内核缓冲区 → 用户缓冲区;用户缓冲区 → Socket 缓冲区;占用 CPU 与内存带宽

    在低速率 I/O 下尚可接受;在 1G/10G/100G 等高带宽、高并发下,两次 CPU 拷贝会迅速把 CPU 打满,吞吐触顶,成为瓶颈。


    三、零拷贝的核心目标与实现

    零拷贝并非字面意义的「零次拷贝」,而是尽可能减少冗余拷贝与上下文切换,尤其是消除或减少 CPU 参与的拷贝。

    3.1 核心目标

    目标说明
    最小化数据拷贝 尤其减少或消除 CPU 拷贝,让数据尽量在内核内流转
    最小化上下文切换 数据路径尽量停留在内核态,少在用户态与内核态之间往返
    提高 I/O 效率 充分利用 DMA、PageCache 及内核提供的专用系统调用

    3.2 关键技术手段

    手段说明
    DMA 磁盘、网卡与内存直接交互,减少 CPU 参与的数据搬运
    PageCache 数据在内核缓冲区中复用与流转,减少重复读盘与用户态参与
    专用系统调用 sendfile、mmap、splice 等,实现内核内或内核到网卡的直传,避免经用户空间

    四、PageCache 与零拷贝

    PageCache(页缓存) 是内核中的磁盘数据缓存,将磁盘块缓存到物理内存页中(通常页大小为 4KB),后续对同一数据的访问可直接从内存命中,减少磁盘 I/O。它与零拷贝协同工作:sendfile 等系统调用正是从 PageCache 读数据再发到 Socket/网卡,若数据已在 PageCache 中则可避免重复读盘。

    机制说明
    读路径 先查 PageCache;命中则直接返回,未命中则 DMA 从磁盘读到 PageCache,并可做预读
    写路径 延迟写(write-back):先写 PageCache 并标为脏页,由回写线程或内存压力、fsync 等时机写回磁盘
    与 sendfile 数据从磁盘 → PageCache(DMA),再经 sendfile 从 PageCache → Socket/网卡(或 SG-DMA 直传),不经过用户空间

    Nginx 等服务器在处理静态文件时,会利用 PageCache 缓存文件内容,再通过 sendfile 将缓存数据直接发到网络,减少拷贝与上下文切换。


    五、mmap + write:减少一次拷贝

    mmap() 将内核中的 PageCache 映射到用户进程地址空间,进程可直接读写该映射区,从而省去「内核缓冲区 → 用户缓冲区」这一次 CPU 拷贝。

    5.1 数据流

    #mermaid-svg-W8fqXgPdvbNGeQO1{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W8fqXgPdvbNGeQO1 .error-icon{fill:#552222;}#mermaid-svg-W8fqXgPdvbNGeQO1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W8fqXgPdvbNGeQO1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .marker.cross{stroke:#333333;}#mermaid-svg-W8fqXgPdvbNGeQO1 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W8fqXgPdvbNGeQO1 p{margin:0;}#mermaid-svg-W8fqXgPdvbNGeQO1 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .cluster-label text{fill:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .cluster-label span{color:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .cluster-label span p{background-color:transparent;}#mermaid-svg-W8fqXgPdvbNGeQO1 .label text,#mermaid-svg-W8fqXgPdvbNGeQO1 span{fill:#333;color:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .node rect,#mermaid-svg-W8fqXgPdvbNGeQO1 .node circle,#mermaid-svg-W8fqXgPdvbNGeQO1 .node ellipse,#mermaid-svg-W8fqXgPdvbNGeQO1 .node polygon,#mermaid-svg-W8fqXgPdvbNGeQO1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .rough-node .label text,#mermaid-svg-W8fqXgPdvbNGeQO1 .node .label text,#mermaid-svg-W8fqXgPdvbNGeQO1 .image-shape .label,#mermaid-svg-W8fqXgPdvbNGeQO1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-W8fqXgPdvbNGeQO1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .rough-node .label,#mermaid-svg-W8fqXgPdvbNGeQO1 .node .label,#mermaid-svg-W8fqXgPdvbNGeQO1 .image-shape .label,#mermaid-svg-W8fqXgPdvbNGeQO1 .icon-shape .label{text-align:center;}#mermaid-svg-W8fqXgPdvbNGeQO1 .node.clickable{cursor:pointer;}#mermaid-svg-W8fqXgPdvbNGeQO1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .arrowheadPath{fill:#333333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W8fqXgPdvbNGeQO1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W8fqXgPdvbNGeQO1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W8fqXgPdvbNGeQO1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W8fqXgPdvbNGeQO1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .cluster text{fill:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 .cluster span{color:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W8fqXgPdvbNGeQO1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W8fqXgPdvbNGeQO1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-W8fqXgPdvbNGeQO1 .icon-shape,#mermaid-svg-W8fqXgPdvbNGeQO1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W8fqXgPdvbNGeQO1 .icon-shape p,#mermaid-svg-W8fqXgPdvbNGeQO1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W8fqXgPdvbNGeQO1 .icon-shape rect,#mermaid-svg-W8fqXgPdvbNGeQO1 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W8fqXgPdvbNGeQO1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W8fqXgPdvbNGeQO1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W8fqXgPdvbNGeQO1 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    mmap_write

    1. DMA Copy

    2. 内存映射

    3. CPU Copy

    4. DMA Copy

    硬盘

    内核 Page Cache

    用户空间 共享映射区

    内核 Socket Buffer

    网卡

    5.2 流程简述

  • mmap() 建立映射,数据不必再拷贝到单独的用户缓冲区。
  • write() 时,数据从 PageCache(经映射区)由 CPU 拷贝到 Socket 缓冲区。
  • DMA 将 Socket 缓冲区数据发送到网卡。
  • 结果:仍为 4 次上下文切换,但仅 3 次数据拷贝(2 次 DMA + 1 次 CPU),相比传统方式减少 1 次 CPU 拷贝。

    注意:mmap 对大文件传输更有优势;小文件可能产生较多碎片,且多进程并发映射同一文件并写入时,可能触发信号(如 SIGBUS)导致 coredump,需注意同步与错误处理。


    六、sendfile:内核态直传

    Linux 2.1 引入 sendfile(out_fd, in_fd, offset, count):数据从文件描述符(如磁盘文件)在内核内直接传到 Socket 描述符,完全不经过用户空间。

    6.1 数据流

    #mermaid-svg-AmMIy8JEYhlP9i0o{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AmMIy8JEYhlP9i0o .error-icon{fill:#552222;}#mermaid-svg-AmMIy8JEYhlP9i0o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AmMIy8JEYhlP9i0o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AmMIy8JEYhlP9i0o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AmMIy8JEYhlP9i0o .marker.cross{stroke:#333333;}#mermaid-svg-AmMIy8JEYhlP9i0o svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AmMIy8JEYhlP9i0o p{margin:0;}#mermaid-svg-AmMIy8JEYhlP9i0o .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o .cluster-label text{fill:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o .cluster-label span{color:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o .cluster-label span p{background-color:transparent;}#mermaid-svg-AmMIy8JEYhlP9i0o .label text,#mermaid-svg-AmMIy8JEYhlP9i0o span{fill:#333;color:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o .node rect,#mermaid-svg-AmMIy8JEYhlP9i0o .node circle,#mermaid-svg-AmMIy8JEYhlP9i0o .node ellipse,#mermaid-svg-AmMIy8JEYhlP9i0o .node polygon,#mermaid-svg-AmMIy8JEYhlP9i0o .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AmMIy8JEYhlP9i0o .rough-node .label text,#mermaid-svg-AmMIy8JEYhlP9i0o .node .label text,#mermaid-svg-AmMIy8JEYhlP9i0o .image-shape .label,#mermaid-svg-AmMIy8JEYhlP9i0o .icon-shape .label{text-anchor:middle;}#mermaid-svg-AmMIy8JEYhlP9i0o .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AmMIy8JEYhlP9i0o .rough-node .label,#mermaid-svg-AmMIy8JEYhlP9i0o .node .label,#mermaid-svg-AmMIy8JEYhlP9i0o .image-shape .label,#mermaid-svg-AmMIy8JEYhlP9i0o .icon-shape .label{text-align:center;}#mermaid-svg-AmMIy8JEYhlP9i0o .node.clickable{cursor:pointer;}#mermaid-svg-AmMIy8JEYhlP9i0o .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AmMIy8JEYhlP9i0o .arrowheadPath{fill:#333333;}#mermaid-svg-AmMIy8JEYhlP9i0o .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AmMIy8JEYhlP9i0o .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AmMIy8JEYhlP9i0o .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AmMIy8JEYhlP9i0o .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AmMIy8JEYhlP9i0o .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AmMIy8JEYhlP9i0o .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AmMIy8JEYhlP9i0o .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AmMIy8JEYhlP9i0o .cluster text{fill:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o .cluster span{color:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AmMIy8JEYhlP9i0o .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AmMIy8JEYhlP9i0o rect.text{fill:none;stroke-width:0;}#mermaid-svg-AmMIy8JEYhlP9i0o .icon-shape,#mermaid-svg-AmMIy8JEYhlP9i0o .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AmMIy8JEYhlP9i0o .icon-shape p,#mermaid-svg-AmMIy8JEYhlP9i0o .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AmMIy8JEYhlP9i0o .icon-shape rect,#mermaid-svg-AmMIy8JEYhlP9i0o .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AmMIy8JEYhlP9i0o .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AmMIy8JEYhlP9i0o .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AmMIy8JEYhlP9i0o :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    sendfile

    1. DMA Copy

    2. CPU Copy 内核内

    3. DMA Copy

    硬盘

    内核 Page Cache

    内核 Socket Buffer

    网卡

    6.2 流程简述

  • sendfile() 一次系统调用完成「读文件 + 写 Socket」。
  • 数据从 PageCache 由 CPU 在内核内拷贝到 Socket 缓冲区。
  • DMA 将数据从 Socket 缓冲区发到网卡。
  • 结果:2 次上下文切换,3 次数据拷贝(2 次 DMA + 1 次 CPU)。用户态不再参与数据搬运,是零拷贝演进中的重要一步。


    七、sendfile + SG-DMA:真正的零拷贝

    Linux 2.4 起,sendfile() 结合 Scatter-Gather DMA(SG-DMA):DMA 引擎可从 PageCache 的多个页直接读取并发送到网卡,无需 CPU 先把数据拷贝到连续的 Socket 缓冲区。

    7.1 数据流

    #mermaid-svg-SSEn4nkSVB8A5zjL{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SSEn4nkSVB8A5zjL .error-icon{fill:#552222;}#mermaid-svg-SSEn4nkSVB8A5zjL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SSEn4nkSVB8A5zjL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SSEn4nkSVB8A5zjL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SSEn4nkSVB8A5zjL .marker.cross{stroke:#333333;}#mermaid-svg-SSEn4nkSVB8A5zjL svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SSEn4nkSVB8A5zjL p{margin:0;}#mermaid-svg-SSEn4nkSVB8A5zjL .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL .cluster-label text{fill:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL .cluster-label span{color:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL .cluster-label span p{background-color:transparent;}#mermaid-svg-SSEn4nkSVB8A5zjL .label text,#mermaid-svg-SSEn4nkSVB8A5zjL span{fill:#333;color:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL .node rect,#mermaid-svg-SSEn4nkSVB8A5zjL .node circle,#mermaid-svg-SSEn4nkSVB8A5zjL .node ellipse,#mermaid-svg-SSEn4nkSVB8A5zjL .node polygon,#mermaid-svg-SSEn4nkSVB8A5zjL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SSEn4nkSVB8A5zjL .rough-node .label text,#mermaid-svg-SSEn4nkSVB8A5zjL .node .label text,#mermaid-svg-SSEn4nkSVB8A5zjL .image-shape .label,#mermaid-svg-SSEn4nkSVB8A5zjL .icon-shape .label{text-anchor:middle;}#mermaid-svg-SSEn4nkSVB8A5zjL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SSEn4nkSVB8A5zjL .rough-node .label,#mermaid-svg-SSEn4nkSVB8A5zjL .node .label,#mermaid-svg-SSEn4nkSVB8A5zjL .image-shape .label,#mermaid-svg-SSEn4nkSVB8A5zjL .icon-shape .label{text-align:center;}#mermaid-svg-SSEn4nkSVB8A5zjL .node.clickable{cursor:pointer;}#mermaid-svg-SSEn4nkSVB8A5zjL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SSEn4nkSVB8A5zjL .arrowheadPath{fill:#333333;}#mermaid-svg-SSEn4nkSVB8A5zjL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SSEn4nkSVB8A5zjL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SSEn4nkSVB8A5zjL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SSEn4nkSVB8A5zjL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SSEn4nkSVB8A5zjL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SSEn4nkSVB8A5zjL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SSEn4nkSVB8A5zjL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SSEn4nkSVB8A5zjL .cluster text{fill:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL .cluster span{color:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SSEn4nkSVB8A5zjL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SSEn4nkSVB8A5zjL rect.text{fill:none;stroke-width:0;}#mermaid-svg-SSEn4nkSVB8A5zjL .icon-shape,#mermaid-svg-SSEn4nkSVB8A5zjL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SSEn4nkSVB8A5zjL .icon-shape p,#mermaid-svg-SSEn4nkSVB8A5zjL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SSEn4nkSVB8A5zjL .icon-shape rect,#mermaid-svg-SSEn4nkSVB8A5zjL .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SSEn4nkSVB8A5zjL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SSEn4nkSVB8A5zjL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SSEn4nkSVB8A5zjL :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    sendfile_sgdma

    1. DMA Copy

    2. SG-DMA 直传

    硬盘

    内核 Page Cache

    网卡

    7.2 流程简述

  • 应用调用 sendfile()。
  • 内核将 PageCache 的页描述信息(地址、长度)交给网卡。
  • SG-DMA 按这些描述从内存多段直接取数发到网卡。
  • 结果:2 次上下文切换,仅 2 次数据拷贝(均为 DMA:磁盘→内核、内核→网卡)。CPU 不再参与任何数据拷贝,即通常所说的「零拷贝」。

    7.3 三种方式对比

    方式上下文切换DMA 拷贝CPU 拷贝总拷贝次数
    传统 read + write 4 次 2 次 2 次 4 次
    mmap + write 4 次 2 次 1 次 3 次
    sendfile 2 次 2 次 1 次 3 次
    sendfile + SG-DMA 2 次 2 次 0 次 2 次

    八、splice 与 tee

    除 mmap、sendfile 外,Linux 还提供 splice 与 tee,用于在文件描述符之间高效搬数据,且不经过用户空间。

    系统调用说明
    splice 在两个 fd 之间通过管道传输数据,数据在内核缓冲区与管道间移动,无需经过用户空间。限制:两个 fd 中至少有一个必须是管道。常用于代理、回显等「从一个 socket 搬到另一个 socket」的场景。
    tee 在两个管道 fd 之间复制数据,且不消耗源管道数据(复制后源数据仍在),适合将同一份数据发往多处(如日志与转发)。

    两者均在内核态完成数据搬运,减少 CPU 拷贝与上下文切换,可与 sendfile 互补(splice 更灵活,但受管道限制)。


    九、应用场景与价值

    9.1 核心价值

    价值说明
    降低 CPU 占用 减少或消除 CPU 拷贝,释放算力给业务逻辑与更多连接
    提升吞吐 高带宽场景下系统 I/O 吞吐可提升数倍甚至一个数量级
    降低延迟 减少中间拷贝与切换,端到端响应更快

    9.2 典型应用场景

    场景说明
    高性能 Web 服务器 Nginx、Apache 等静态文件服务中大量使用 sendfile
    消息中间件 Kafka 将磁盘日志高效传给消费者,零拷贝是其高吞吐的重要基础
    分布式文件系统 如 HDFS,节点间大块数据传输可受益于零拷贝
    数据库 MySQL 等在做备份、主从同步时也可采用零拷贝路径
    视频/大文件服务 流媒体、下载服务在高带宽下避免 CPU 成为瓶颈
    CDN 内容分发节点将缓存内容通过零拷贝快速发给用户,减少延迟与卡顿

    十、实践:Java NIO 与 Netty

    应用层可借助 Java NIO 与 Netty 使用内核的零拷贝能力,而无需直接调用 Linux 系统调用。

    API / 组件对应内核机制说明
    MappedByteBuffer mmap 将文件映射到堆外内存,进程通过内存地址访问文件,适合大文件随机读写。
    FileChannel#transferTo() / transferFrom() sendfile 将文件数据从 FileChannel 直接传到 SocketChannel(或反向),底层即 sendfile(),Kafka 在消费日志时大量使用,是其高吞吐的重要原因之一。
    Netty FileRegion sendfile 封装了基于 transferTo 的零拷贝发送,适合大文件或大缓冲区的网络发送。

    示例思路:服务端监听端口,接受连接后打开文件并获取 FileChannel,调用 transferTo(0, fileSize, socketChannel) 将文件直传至客户端,数据路径为磁盘 → PageCache → 网卡,不经用户缓冲区。


    小结与速查

    要点归纳

    主题要点
    传统 I/O read + write:4 次上下文切换,4 次拷贝(2 次 DMA + 2 次 CPU);CPU 拷贝是主要瓶颈
    零拷贝目标 减少拷贝(尤其 CPU 拷贝)、减少切换、用好 DMA 与 PageCache
    PageCache 内核页缓存,读命中/未命中、延迟写与脏页回写;与 sendfile 协同减少读盘与拷贝
    mmap + write 省 1 次 CPU 拷贝,仍 4 次切换、3 次拷贝;大文件更合适,小文件/多进程写需注意
    sendfile 数据不经过用户态,2 次切换、3 次拷贝(1 次 CPU 在内核内)
    sendfile + SG-DMA 仅 2 次 DMA 拷贝,0 次 CPU 拷贝,即「零拷贝」
    splice / tee 内核内在 fd 间(含管道)搬数据;tee 为管道间复制且不消耗源数据
    Java / Netty MappedByteBuffer→mmap;transferTo/FileRegion→sendfile;Kafka 等常用

    术语速查

    术语含义
    DMA Direct Memory Access,直接内存访问,外设与内存间不经过 CPU 的数据传输
    PageCache 内核页缓存,缓存文件数据,减少磁盘 I/O;延迟写、脏页回写
    SG-DMA Scatter-Gather DMA,可对多块不连续内存做一次 DMA 传输
    sendfile Linux 系统调用,文件到 Socket 的内核内传输
    splice / tee 内核内在 fd/管道间传输或复制数据,不经过用户空间

    延伸阅读

    • RDMA(Remote Direct Memory Access):网卡级零拷贝与内核旁路技术,本机零拷贝的“升级版”。数据在用户态缓冲区与远程主机内存间直接传输,绕过内核协议栈,进一步减少 CPU 与延迟。常见实现有 InfiniBand、RoCE(RDMA over Converged Ethernet)、iWARP(基于 TCP),多用于 HPC、分布式存储、金融低延迟等场景;需专用网卡与驱动。
    • Linux 内核与 man:man 2 sendfile、man 2 mmap、man 2 splice、man 2 tee;内核文档可查阅 Documentation/filesystems、网络/IO 相关小节。
    • Java / 框架:Oracle 官方 Java NIO 与 FileChannel、MappedByteBuffer 文档;Netty 官方文档中 FileRegion、零拷贝相关说明;Kafka 文档中关于日志传输与高吞吐的设计说明。
    • Web 服务器:Nginx 的 sendfile on、tcp_nopush 等配置与静态文件服务优化;其他高性能服务器(如 Caddy、OpenResty)的零拷贝与 IO 模型说明。

    根据公开技术资料与 Linux 内核文档整理。

    赞(0)
    未经允许不得转载:171主机测评 » Zero-Copy零拷贝技术详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址