sendfile能绕过用户空间、跳过cpu拷贝,将静态文件从磁盘页缓存直送socket缓冲区,但必须同时满足四点前提:源fd为open打开的真实磁盘文件、目标fd为已连接socket、文件系统支持mmap语义、内核启用config_sock_sendpage。

直接用 sendfile() 系统调用,就能绕过用户空间、跳过 CPU 拷贝,把静态文件从磁盘页缓存直送 socket 缓冲区。它不是靠“开启开关”生效,而是在代码里正确调用并满足运行条件后,内核自动走零拷贝路径——这才是大幅降 CPU 的关键。
确保基础前提全部满足
sendfile 不会自动生效,必须同时满足以下四点,否则退化为普通 read/write:
- 源文件描述符(in_fd)必须由
open()打开的真实磁盘文件(如/var/www/logo.png),不支持管道、socket、/proc 或 tmpfs 文件; - 目标描述符(out_fd)必须是已建立连接的 socket(TCP/UDP 均可,但 UDP 需控制单次传输 ≤ MTU);
- 文件系统需支持 mmap 语义(ext4、xfs 默认支持;NFSv4 可行,但 NFSv3 或挂载选项含
noac时易阻塞导致 D 态); - Linux 内核 ≥ 2.4(现代发行版均满足),且内核配置启用
CONFIG_SOCK_SENDPAGE(主流发行版默认开启)。
小文件场景:避免高频 syscall 开销
对图标、CSS、JS 等 KB 级小文件,反复调用 sendfile 反而增加系统调用开销。应结合业务逻辑做合并或节流:
- 单次 sendfile 传输不宜低于 4KB;小于 1KB 的文件,优先考虑用
writev()合并多个小资源一次性发送; - 启用
TCP_CORK(而非TCP_NODELAY),让内核暂存小数据包,等凑够 MSS 或显式取消 cork 再发,减少网络小包和软中断压力; - 若使用 epoll,确保在
EPOLLOUT就绪后再调 sendfile,并检查返回值——非阻塞 socket 下常返回EAGAIN,需轮询或重投事件,不可忽略。
大文件与高并发下的协同调优
视频、安装包等 MB 级文件分发时,sendfile 效益最明显,但需配合其他机制防瓶颈:
- 传入 offset 参数地址(如
&off),而非 NULL,既支持断点续传,也避免多线程共享 fd 时因lseek竞态导致读错位置; - 文件打开时加
O_SEQUENTIAL(ext4 下提示内核预读),但禁用O_DIRECT——后者绕过 pagecache,会使 sendfile 失效; - 搭配 epoll 使用:epoll 管理连接就绪状态,sendfile 负责高效搬运;二者分工明确,前者决定“何时发”,后者决定“怎么发得省”。
规避常见误用陷阱
很多 CPU 占用没降下来,其实是踩了这些坑:
- 对需要加 HTTP header、gzip 压缩或 token 验证的响应,硬套 sendfile——它不经过用户空间,无法修改内容,此时应改用
splice()或传统 read/write; - 源文件被频繁修改(如日志轮转中的活跃文件),导致 pagecache 失效或 sendfile 返回
EINVAL;建议静态资源用只读 inode 或 hardlink 固定路径; - 未监控进程状态:若后端存储慢(如 NFS hang、机械盘抖动),sendfile 会卡在 D 态,表现为 CPU 占用不高但请求堆积——需用
ps aux | grep 'D'排查。











