零拷贝通过sendfile()将read/write两次系统调用压缩为一次内核态操作,仅触发一次上下文切换,避免CPU数据拷贝与调度开销;生效需满足真实磁盘文件、准确Content-Length、未启用响应体修改模块等条件。

零拷贝能节省 CPU 上下文切换,核心在于它把原本需要两次系统调用(read + write)的流程,压缩为一次内核态内部完成的 sendfile() 调用,彻底绕过了用户空间。
传统 read/write 流程触发 2 次上下文切换
当 Nginx 用传统方式发文件时:
- 调用
read():用户态 → 内核态(第一次切换),DMA 把磁盘数据读入内核页缓存,再由 CPU 拷贝到用户缓冲区 - 调用
write():用户态 → 内核态(第二次切换),CPU 把用户缓冲区数据拷贝进 socket 缓冲区,再由 DMA 发往网卡
sendfile 只需 1 次系统调用,全程在内核态完成
sendfile() 是一个系统调用,它让内核直接在两个内核对象间搬运数据:
- 输入 fd 指向磁盘文件(已由 open 打开),输出 fd 指向已连接的 socket
- 数据从磁盘经 DMA 进入页缓存后,内核直接将其“推送”到 socket 缓冲区,不经过用户空间
- 整个过程只发生一次用户态到内核态的切换(发起 sendfile 系统调用时),之后全在内核中流转
省掉的不只是拷贝,更是调度开销
每次上下文切换都要保存/恢复寄存器、切换栈、更新调度状态——对高频小文件(如图标、CSS)尤为敏感。sendfile 不仅省去一次 CPU 数据拷贝,更避免了第二次陷入内核的调度成本,使 CPU 更专注处理连接、路由等逻辑任务。
生效前提决定是否真走零拷贝路径
sendfile 不是“开了就一定零拷贝”,必须同时满足:
- 请求的是真实磁盘文件(非 proxy_pass、FastCGI 或内存映射文件)
- 响应头含准确
Content-Length - 未启用 gzip、sub_filter、add_before_body 等修改响应体的模块
- 目标 socket 已建立连接,且文件系统支持 mmap 语义(ext4/xfs 默认支持)


















