sendfile 通过单次系统调用实现零拷贝,将上下文切换从4次降至1次,避免用户态内存拷贝,但需配合 tcp_nopush on、gzip off 等配置及规避 range、gzip 等限制才能生效。

sendfile 在 Nginx 中直接将文件数据从磁盘页缓存(PageCache)内核缓冲区,零拷贝直送 socket 发送队列,跳过用户空间,从而把原本必须的 read() + write() 两次系统调用压缩为 单次 sendfile() 调用。这是它降低系统调用开销最核心、最直接的表现。
从 4 次上下文切换降到 1 次
传统 read/write 路径触发 4 次上下文切换(user ↔ kernel 来回各两次),而 sendfile 只需一次系统调用进入内核,全程在内核态完成数据搬运。这大幅减少了 CPU 在任务调度上的开销,尤其在高并发小响应或大文件流式分发时效果显著。
彻底规避用户态内存拷贝
read() 必须把数据从 PageCache 拷到用户缓冲区,write() 再把用户缓冲区数据拷到 socket 缓冲区——两次 CPU 参与的内存拷贝。sendfile 完全绕过用户空间,数据只在内核不同 buffer 间流转(甚至借助 SG-DMA 连内核 buffer 拷贝都可省略),CPU 不再搬运字节,只做控制流调度。
依赖真实生效,不是“开了就有效”
以下情况会让 Nginx 自动退回到 read/write 模式,导致系统调用次数回升:
- 启用了 gzip on 或任何运行时内容改写(如 sub_filter、add_header 动态计算 ETag)
- 响应含 Range 请求(视频拖拽等),需切片处理
- 文件挂载自 NFSv3、FUSE、加密文件系统(fscrypt)等不支持 sendfile 的后端
- 使用了 HTTP/1.0 短连接,或 LB 做了 L7 代理,破坏了原始 socket 复用
可通过 strace -e trace=sendfile,read,write -p $(pgrep nginx) 实时观察 worker 进程是否持续打出 sendfile64 调用,而非交替出现 read + write。
配套指令决定最终收益
单独 sendfile on 不足以释放全部潜力,必须协同配置才能稳定维持低系统调用状态:
- tcp_nopush on:让 sendfile 数据攒满 TCP 报文再发,减少小包数量和对应系统调用频次
- gzip off(或改用 gzip_static on):避免因压缩强制进入用户态
- open_file_cache max=10000 inactive=60s:缓存 stat/open 结果,抑制高频小文件访问带来的额外系统调用
- 对大文件路径做精确 location 匹配(如
location ~ \.(mp4|zip|iso)$),防止误触其他模块干扰











