sendfile 的核心作用是将静态文件传输从用户态移至内核态完成,跳过用户空间参与,从而将上下文切换从4次减至2次、消除2次cpu内存拷贝;必须配合tcp_nopush on且禁用gzip等修改响应体的功能,并确保文件为本地真实静态资源、响应含content-length才生效。

sendfile 的核心作用是把静态文件传输过程从用户态搬进内核态,直接在内核中完成数据搬运,从而跳过用户空间的参与。这直接减少了两次上下文切换——传统 read()+write() 模式下,每次都要进出用户态,共 4 次;启用 sendfile 后,只需 2 次系统调用(一次触发、一次完成),上下文切换降到最低。
为什么能省掉两次上下文切换
传统方式要先 read() 把数据从磁盘页缓存拷到用户缓冲区,再 write() 拷到 socket 缓冲区。这两个操作各触发一次用户态与内核态的切换,加起来就是 4 次。sendfile 则让内核自己在 fd 之间搬运:数据从文件描述符直接送入 socket 描述符,全程不经过用户内存,自然省掉中间两次进出用户态的过程。
sendfile 生效的前提条件
- 只对本地静态文件生效(比如 location 匹配 .jpg、.css、.js 等)
- 不能和 gzip、sub_filter、proxy_buffering 等需要修改响应体的功能共存
- 响应必须有明确的 Content-Length,否则 Nginx 会自动禁用 sendfile
- 需搭配 tcp_nopush on; 才能真正发挥 TCP 层面的优化效果
配合使用的必要配置
单独开 sendfile 效果有限,必须协同其他参数才能闭环优化:
- sendfile on; —— 启用内核态零拷贝路径
- tcp_nopush on; —— 让内核攒满 TCP 包再发,避免小包泛滥
- tcp_nodelay on; —— 在 keepalive 场景下防止最后一点数据被延迟发送
实际节省效果对比
以一个 1MB 的图片请求为例:
- 默认 read+write:4 次上下文切换 + 2 次 CPU 内存拷贝
- 启用 sendfile + tcp_nopush:仅 2 次上下文切换 + 0 次用户态拷贝
- 若再配合 multi_accept on 和 epoll 高效事件分发,单次系统调用可处理多个连接,进一步摊薄切换成本











