Nginx 的 sendfile 并非跳过所有拷贝,而是消除传统 read/write 中两次 CPU 参与的用户空间拷贝,使数据始终在内核空间流动;其通过 sendfile() 系统调用让内核直接将页缓存数据送入 socket 缓冲区,配合 DMA 实现高效传输,显著降低 CPU 占用与上下文切换。

nginx 的 sendfile 不是“跳过所有拷贝”,而是把原本必须经过用户空间的两次 CPU 拷贝彻底去掉,让数据始终留在内核空间流动。它的核心价值在于降低 CPU 占用、减少上下文切换、提升静态文件吞吐能力。
为什么传统 read/write 要四次拷贝?
当 Nginx 用 read() + write() 发送一个文件时:
- 第一次拷贝:DMA 将磁盘数据搬入内核页缓存(无需 CPU)
- 第二次拷贝:CPU 将页缓存数据复制到用户空间缓冲区(消耗 CPU)
- 第三次拷贝:CPU 将用户缓冲区数据复制到 socket 内核缓冲区(再次消耗 CPU)
- 第四次拷贝:DMA 将 socket 缓冲区数据发往网卡(无需 CPU)
同时伴随 4 次用户态 ↔ 内核态切换,开销集中于 CPU 和调度器。
sendfile 是怎么绕过用户空间的?
启用 sendfile on; 后,Nginx 调用内核 sendfile() 系统调用,由内核直接完成传输:
- 数据从磁盘经 DMA 进入页缓存(和之前一样)
- 内核直接将页缓存中对应文件段的 offset + length 信息写入 socket 缓冲区
- 后续由 DMA 控制器根据该描述符,直接从页缓存读取并发送至网卡
此时不再有用户缓冲区参与,CPU 不搬运实际数据,只做控制指令下发。Linux 2.4+ 支持 SG-DMA(分散/聚集 DMA),可进一步跳过 socket 缓冲区中转,实现“页缓存 → 网卡”直传。
真正走零拷贝路径的硬性条件
仅配置 sendfile on; 并不等于生效。以下任一不满足,Nginx 自动回落到 read/write 模式:
- 响应体未被修改:禁用
gzip、sub_filter、ssi、chunked_transfer_encoding - 文件为普通磁盘文件:不能是管道、套接字、设备文件或内存映射特殊文件
- 非 TLS 场景:HTTPS 下 OpenSSL 必须解密/加密,数据必须进出用户空间
- 未启用冲突指令:如
directio(大文件直读)会强制关闭 sendfile - OS 与硬件支持:Linux ≥ 2.4,且网卡需支持 SG-DMA(可用
ethtool -k eth0 | grep scatter-gather验证)
如何确认零拷贝是否真实启用?
日志里看不到,得靠运行时观测:
- 用
strace -p <worker-pid> -e trace=sendfile,read,write:若大量出现sendfile(...)而几乎无read(),说明走通 - 对比 CPU 使用率:启用后用户态 CPU 显著下降,内核态小幅上升(DMA 和调度开销)
- 观察网络指标:高并发下
TCP: out segments增长快、重传率低,间接反映传输效率提升
它不是银弹,但对静态资源服务而言,是成本最低、效果最直接的 I/O 加速手段之一。


















