Linux Nginx 的 sendfile 机制本质是跳过用户态,让数据在内核页缓存与 socket 发送队列间直接映射转发,避免两次 CPU 内存拷贝;仅需两次 DMA 传输,实现零次 CPU 拷贝、一次系统调用和上下文切换。

Linux Nginx 的 sendfile 机制并不减少“内核态内存复制”的次数,而是**彻底绕过用户态,让数据在内核内部直接流转,从而避免了原本必须发生的两次 CPU 参与的内存拷贝**。
它不复制,而是映射和转发
传统 read/write 流程中,CPU 要执行两次显式拷贝:
- read():把页缓存里的数据,由 CPU 拷贝进用户态缓冲区
- write():再把用户态缓冲区数据,由 CPU 拷贝进 socket 缓冲区
而 sendfile() 系统调用告诉内核:“把文件 fd 对应的页缓存数据,直接送到 socket fd 的发送队列里”。内核不做数据搬运,只做两件事:
- 检查文件页是否已在 page cache;若未加载,触发异步预读(仍由 DMA 完成)
- 将对应物理页帧地址、偏移、长度等信息,填入 socket 的
sk_write_queue中的skb(socket buffer)结构
真正发出去靠的是 SG-DMA
网卡驱动在发送时,并不从某个连续缓冲区取数据,而是通过 Scatter-Gather DMA(分散-聚集 DMA)直接读取多个离散的物理页帧。这意味着:
- 数据始终留在内核页缓存中,没有被“复制”到别处
- CPU 不参与任何字节级搬运,只负责设置描述符链
- 整个路径只有两次 DMA:磁盘 → 页缓存、页缓存 → 网卡
所以“减少复制”其实是误读,本质是“跳过用户空间”
所谓“零拷贝”,指的不是内存里完全没发生数据移动(DMA 移动不算 CPU 拷贝),而是:
- 0 次 CPU 发起的内存拷贝(
memcpy类操作) - 0 次用户态内存分配与访问
- 1 次系统调用(而非 read+write 的 2 次)
- 1 次上下文切换(而非 2 次)
真正的节省,来自 CPU 解放和调度开销下降,不是因为“少搬了一次内存”。











