深入理解零拷贝技术:从 I/O 瓶颈到高性能网络
本文从传统 I/O 的性能瓶颈出发,剖析零拷贝(Zero-Copy)的原理、实现方式(mmap、sendfile、SG-DMA)及其在高性能 Web、消息队列、文件系统等场景中的应用,帮助开发者理解并解决高并发下的 I/O 性能问题。
目录
一、为什么需要零拷贝?
在高速网络与海量数据场景下,用户对传输速度和响应延迟要求越来越高。无论是高清视频点播、大文件下载,还是高频交易系统,都依赖高效的 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 流程简述
结果:仍为 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 流程简述
结果: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 流程简述
结果:2 次上下文切换,仅 2 次数据拷贝(均为 DMA:磁盘→内核、内核→网卡)。CPU 不再参与任何数据拷贝,即通常所说的「零拷贝」。
7.3 三种方式对比
| 传统 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 系统调用。
| 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 内核文档整理。




