零拷贝

Posted by fjw on March 14, 2023

零拷贝

含义

零拷贝(Zero-Copy) 的核心含义是:

在数据从存储介质(如磁盘)→ 内核缓冲区 → 用户空间 → 内核socket缓冲区 → 网卡的传输路径中,尽量避免或完全消除由CPU执行的内存拷贝(memcpy)操作,把数据搬运的任务尽可能交给DMA(直接内存访问)硬件来完成,从而大幅降低CPU开销、上下文切换次数和内存带宽占用。

零拷贝 不等于 完全没有发生任何拷贝,其实更准确的说法是“零CPU拷贝”“避免不必要的用户态↔内核态拷贝”

方式

传统 read + write 方式(4次拷贝 + 4次上下文切换)

1
2
3
4
磁盘 → DMA → 内核Page Cache     (拷贝1:DMA)
内核Page Cache → 用户缓冲区       (拷贝2:CPU)
用户缓冲区 → 内核socket缓冲区     (拷贝3:CPU)
内核socket缓冲区 → 网卡           (拷贝4:DMA)

上下文切换:read → write → read → write(共4次)

目前Linux上最常用的几种“零拷贝”方式对比

方式 系统调用 CPU拷贝次数 DMA拷贝次数 上下文切换次数 典型适用场景 备注
传统 read+write read + write 2 2 4 通用,但低效 基准对比
mmap + write mmap + write 1 2 4 需要对文件内容做少量修改的场景 内存映射,适合随机读写
sendfile(普通) sendfile 1 2 2 静态文件服务(Nginx经典) Linux 2.2+
sendfile + SG-DMA(收集式) sendfile 0 2 2 最理想的静态文件传输 需要网卡支持DMA Scatter-Gather,主流网卡基本都支持
splice / tee splice / vmsplice 0 2(管道间) 2 管道间零拷贝、代理转发 内核2.6.17+,不能跨主机
MSG_ZEROCOPY setsockopt + send(MSG_ZEROCOPY) 0 减少 用户态缓冲区直接发网络(大包) 内核4.14+ TCP,5.0+ UDP,适合10KB+大包
io_uring + 注册缓冲区 io_uring + IORING_OP_提供缓冲区 0 大幅减少 高性能网络服务器、数据库 目前最激进的异步零拷贝方案

最常用的三种零拷贝方式简要原理

  1. mmap + write(内存映射方式)
1
2
3
4
5
磁盘 → DMA → Page Cache
         ↑
       mmap 映射(共享同一物理页)
         ↓
用户进程直接读写这块内存 → write(内核直接从Page Cache取) → socket缓冲区 → DMA → 网卡
  • 省掉了一次用户缓冲区 ←→ 内核缓冲区的CPU拷贝
  • 但仍然有一次内核 → socket缓冲区的CPU拷贝
  1. sendfile(Linux静态文件发送神器)

普通sendfile(2.4以前或没开启SG-DMA):

磁盘 → DMA → Page Cache → CPU拷贝 → socket缓冲区 → DMA → 网卡

sendfile + DMA Scatter-Gather(现代主流):

磁盘 → DMA → Page Cache 内核只把文件偏移 + 长度信息传给网卡驱动 网卡驱动通过DMA Scatter-Gather直接从Page Cache读取数据发出去

真正全程零CPU拷贝,只有2次DMA

  1. splice / vmsplice(管道零拷贝)

用于两个文件描述符之间(或用户缓冲区与管道)零拷贝移动数据,常用于:

  • nginx proxy
  • redis AOF rewrite
  • 数据中转不需解析内容的场景