sendfile 是 nginx 对操作系统 sendfile() 系统调用的封装,本质是让内核在页缓存与 socket 缓冲区间直接搬运数据,全程不经过用户空间;其执行依赖真实文件路径、content-length 已知、禁用 gzip/range/ssl 等条件,否则回退至 read/write。

sendfile 不是 Nginx 自己实现的传输逻辑,而是它对操作系统 sendfile() 系统调用的一层封装调度。它的“底层机制”本质是用户态进程(Nginx worker)向内核发起一次明确指令,让内核在页缓存与 socket 缓冲区之间直接搬运数据——全程不触达用户空间内存。
用户态触发:Nginx 如何调用 sendfile
Nginx 在处理静态文件响应时,若满足条件(真实磁盘路径、无内容改写、Content-Length 已知等),会跳过 ngx_http_read_client_request_body 和常规 write 流程,进入 ngx_http_sendfile 函数路径:
- 先调用
open()获取文件句柄,并通过stat()确认大小,填充Content-Length - 检查当前连接是否支持
sendfile(如非 SSL、非 chunked、非 keepalive 超时中) - 构造
ngx_buf_t缓冲区结构,但不分配用户态内存;仅设置in_file = 1、file = &file_info等标记 - 最终调用
ngx_linux_sendfile_chain(Linux 平台),内部执行sendfile(fd_out, fd_in, &offset, size)系统调用
内核态执行:Linux 中 sendfile() 的实际行为
以 Linux 2.6+ 为例,内核收到该系统调用后,并不真正“拷贝”数据字节,而是建立页表映射和 DMA 描述符链:
- 若文件页尚未加载,内核异步触发 page cache 填充(即“预读”,受
read_ahead影响) - 若页已缓存,内核直接将对应物理页帧地址 + 偏移/长度,填入 socket 的
sk_write_queue中的skb(socket buffer) - 网卡驱动在发送时,通过 SG-DMA(Scatter-Gather DMA)直接从这些页帧读取数据,无需 CPU 搬运
- 整个过程只发生两次 DMA:磁盘 → 页缓存、页缓存 → 网卡;零次 CPU 内存拷贝,零次用户态参与
关键源码锚点(Nginx 1.24+ / Linux 6.8)
可定位以下核心函数理解调度链路:
-
Nginx 层:
src/http/ngx_http_core_module.c中ngx_http_sendfile是入口;src/os/unix/ngx_linux_sendfile_chain.c实现具体调用 -
内核层:
fs/read_write.c中sys_sendfile64是系统调用入口;实际搬运由splice_direct_to_actor或do_splice_to完成,依赖 VFS 层的file_operations.sendpage接口(如 ext4 的ext4_file_sendpage) - 注意:当文件位于 NFS 或某些 FUSE 文件系统时,
sendpage方法可能未实现或返回错误,Nginx 会自动 fallback 到 read/write
为什么不是所有场景都走这条路径?
因为 sendfile 是纯“转发”动作,任何需要修改响应体的行为都会打断它:
- gzip on:必须进用户态压缩,Nginx 要调用 zlib,强制走
ngx_http_gzip_filter+writev - Range 请求:需计算切片偏移,Nginx 必须用
read()读指定段落,再write()发送 - ETag/Last-Modified 动态生成:需 stat 后拼响应头,但只要不改 body,仍可走 sendfile;而
add_header若导致 header 缓冲区重排,也可能干扰 tcp_nopush 对齐 - SSL/TLS 连接:OpenSSL 的 BIO 层不支持 sendfile 直传,必须解密/加密上下文介入,故 HTTPS 下默认禁用(除非启用
ssl_buffer_size配合特定内核补丁)











