目 录CONTENT

文章目录

C++ 零拷贝(Zero-Copy)

TalentQ
2025-09-11 / 0 评论 / 0 点赞 / 6 阅读 / 0 字

C++ 中的零拷贝是一系列旨在消除不必要内存拷贝的技术,核心思想是让不同实体共享数据而非复制数据,以此大幅提升 I/O 密集型应用的性能。

传统的数据操作:涉及 4 次上下文切换(用户态/内核态切换)和 至少 2 次数据拷贝(内核态->用户态,用户态->内核态)

主要实现手段包括:

  1. 使用 sendfile 等系统调用在内核中直接传输数据, 绕过 用户空间,实现了 2 次 DMA 拷贝 + 0 次 CPU 拷贝,是真正的零拷贝。

  2. 使用 mmap 将文件映射到内存,避免了用户空间和内核空间之间的数据拷贝,但仍然需要 CPU 来管理内存映射和处理缺页中断。因此,它通常被称为“一种减少拷贝的优化”,而非像 sendfile 那样的“真正零拷贝”。

传统的数据操作

在传统的数据操作中(例如,从磁盘读取文件并通过网络发送),数据流经的路径会涉及多次冗余拷贝和上下文切换,如下图所示:

  1. 磁盘驱动程序将文件数据拷贝到内核空间的缓冲区(Page Cache)。

  2. 应用程序发起读操作,内核将数据从内核缓冲区拷贝到用户空间的应用程序缓冲区。

  3. 应用程序发起写操作(如发送网络数据),系统再将数据从用户缓冲区拷贝到内核空间的网络协议栈缓冲区(Socket Buffer)。

  4. 最后,网卡驱动程序从 Socket Buffer 中读取数据并发送出去。

这个过程涉及 4 次上下文切换(用户态/内核态切换)和 至少 2 次数据拷贝(内核态->用户态,用户态->内核态)。拷贝操作需要 CPU 参与,大量占用宝贵的 CPU 周期。

sendfile()

  • 功能:直接在两个文件描述符(in_fdout_fd)之间传输数据,数据完全在内核空间中进行操作,绕过了用户空间。

  • 经典用例:一个静态文件服务器(如 Nginx, Apache)使用 sendfile 将磁盘上的文件直接发送到网络套接字,无需将文件数据读入用户空间进程。

  1. DMA 将数据从磁盘拷贝到内核缓冲区(Page Cache):这一步由 DMA(Direct Memory Access)完成,无需 CPU 参与。

  2. sendfile() 系统调用被执行:数据不会被拷贝到用户空间。取而代之的是,内核直接将文件数据(来自 in_fd 对应的 Page Cache)的相关信息和目标(out_fd)一起打包。

  3. DMA 将数据从内核缓冲区直接拷贝到网卡:内核将准备好的文件数据(仍然在内核缓冲区中)的地址和长度信息交给网卡驱动的 Socket Buffer。网卡驱动程序利用 DMA直接将数据从内核的 Page Cache 传输到网络协议栈中,最终发送出去

在整个过程中,数据始终待在内核地址空间,没有在用户空间和内核空间之间来回拷贝,也最大限度地减少了 CPU 的参与。现代的网卡甚至支持 Gather Operation,使得内核只需要传递一个描述内存地址和长度的数组给网卡,网卡就能直接从多个不连续的内存位置(如多个 Page Cache 页)收集数据并发送,进一步避免了最后一次 CPU 拷贝。

内存映射文件 (mmap)

  • 原理:它将一个文件(或设备)的一部分直接映射到进程的虚拟地址空间。应用程序可以像访问普通内存一样(通过指针)直接读写这块内存区域,操作系统负责在后台将修改写回磁盘文件。

  • 优势:避免了使用 read()write() 系统调用所带来的数据拷贝(从内核 Page Cache 到用户缓冲区的拷贝)。它并非完全“零拷贝”,但省去了一次拷贝操作。

  1. DMA 将数据从磁盘拷贝到内核缓冲区(Page Cache):这一步由 DMA(Direct Memory Access)完成,无需 CPU 参与。

  2. 建立内存映射:操作系统在用户的虚拟地址空间中分配一段空间,并将其映射到上一步的内核缓冲区(Page Cache)上。注意:这里并没有发生数据拷贝。没有数据被复制到用户空间的缓冲区。

  3. 应用程序直接访问内存:应用程序现在可以通过指针(mmap 返回的地址)直接读写那块映射的内存区域。实际上,这些读写操作直接作用于内核的 Page Cache。

    • 读操作:如果数据已在 Page Cache 中,则完全无需系统调用和拷贝。

    • 写操作:会直接修改 Page Cache,由操作系统在后台定期将脏页写回磁盘。

0

评论区