sendfile能加速传输,核心在于dma接管数据搬运:磁盘→内核页缓存→网卡全程无需cpu拷贝,仅需2次dma搬运、0次cpu拷贝、2次上下文切换,依赖sendfile on+tcp_nopush on+禁用gzip等条件协同生效。

sendfile 能加速传输,核心在于它把数据搬运工作从 CPU 手里交给了 DMA 控制器——CPU 不再参与拷贝,只发指令、等通知。
DMA 是怎么接管搬运任务的
DMA(Direct Memory Access)是独立于 CPU 的硬件模块,专干“内存 ↔ 外设”之间的数据搬运。当 sendfile 触发时:
- Nginx 调用内核 sendfile() 系统调用,告诉内核:“把磁盘上这个文件,直接送到网卡去”
- 内核启动 DMA 控制器:先让磁盘控制器用 DMA 把文件数据搬进内核缓冲区
- 接着,内核让同一块 DMA 控制器(或支持 SG-DMA 的网卡)直接把这段内核缓冲区的数据,按描述符地址和长度,送入网卡发送队列
- CPU 全程不碰数据字节,只做初始化和中断响应,空出来处理其他请求
为什么必须走内核态直通路径
传统 read()+write() 流程中,数据必须经过用户空间缓冲区,这就强制 CPU 参与两次内存拷贝(内核→用户→socket 缓冲区)。而 sendfile 跳过用户空间,让 DMA 在内核内部完成“磁盘→内核缓冲区→网卡”的链路:
- 第一次 DMA:磁盘 → 内核页缓存(由内核 page cache 管理)
- 第二次 DMA:内核页缓存 → 网卡(若网卡支持 scatter-gather,甚至可跳过中间缓冲,直接映射传输)
- 全程无 CPU 拷贝,无用户态上下文切换,延迟低、吞吐高
配置要对,DMA 才能真正跑起来
光写 sendfile on 不够,DMA 加速需要完整配合:
- tcp_nopush on:让响应头和文件数据合并进一个 TCP 报文段,避免小包触发多次 DMA 启动
- tcp_nodelay off:关闭 Nagle 算法,否则会强行拆包,破坏 tcp_nopush 的攒包逻辑,间接干扰 DMA 批量传输效率
- 禁用 gzip / proxy_pass / sub_filter:这些功能迫使数据流经用户态,DMA 链路被切断,回退为 CPU 搬运
- 文件必须在本地 ext4/xfs 等原生文件系统上:NFS、FUSE 或某些容器存储驱动不支持 sendfile() 系统调用,DMA 无法介入
怎么确认 DMA 真正在干活
不能只看配置是否加载成功,得验证运行时行为:
- 用 strace -e trace=sendfile64 -p $(pgrep nginx) 观察 worker 进程是否频繁调用 sendfile64()
- 压测相同静态文件,对比开启/关闭 sendfile 时的 CPU 使用率(下降 30%+ 通常说明 DMA 正在生效)
- 检查 error.log 是否有 sendfile() failed 报错,常见于文件挂载方式不兼容或权限问题











