什么是零拷贝?以及数据在内存中的流动途径
零拷贝就是在以下的流动途径中,避免一些用户空间和内核缓冲区的拷贝。
“零拷贝”这个名字的“零”是指在数据传输过程中减少到最少甚至完全避免用户空间和内核空间之间的内存拷贝。传统的 I/O 操作中,数据通常要在内核空间和用户空间之间来回复制,造成了多次内存拷贝。零拷贝技术的目标是通过优化这些操作,尽可能减少数据拷贝的次数,理想情况下做到“零次拷贝”。
零拷贝的含义
- “零”并不总是意味着绝对没有数据拷贝,而是指避免不必要的内存拷贝,尤其是避免内核空间和用户空间之间的多次拷贝。
传统方式 vs. 零拷贝
在传统的文件传输过程中(如通过网络发送文件),数据的拷贝过程大致如下:
- 从磁盘到内核缓冲区(第一次拷贝):数据从磁盘读入内核空间的缓冲区。
- 从内核缓冲区到用户空间缓冲区(第二次拷贝):数据从内核缓冲区复制到用户空间缓冲区。
- 从用户空间缓冲区回到内核缓冲区(第三次拷贝):数据从用户空间缓冲区复制回内核缓冲区,准备通过网络发送。
- 通过网络发送(可能有 DMA,但涉及系统总线传输):数据从内核缓冲区传输到网络接口卡(NIC),并通过网络发送。
零拷贝的目标是减少或消除步骤 2 和 3 中的数据拷贝。例如,通过 sendfile
系统调用,可以直接从内核缓冲区将数据发送到网络 socket,而无需将数据复制到用户空间再复制回来。这种方式避免了用户空间和内核空间之间的两次拷贝。
零拷贝的典型实现方式
-
sendfile
系统调用:直接将文件数据从内核缓冲区发送到网络 socket,减少了一次数据拷贝。 -
内存映射 (
mmap
):将文件映射到用户空间,减少对文件的读操作拷贝数据次数。 -
DMA(Direct Memory Access):使用 DMA 技术让硬件设备直接从内存读取数据,绕过 CPU 的干预,从而减少拷贝和 CPU 资源的占用。
总结
“零拷贝”这个术语强调了减少甚至避免内存拷贝,特别是在内核空间和用户空间之间。这不仅可以减少 CPU 占用,还能提高系统的 I/O 性能,特别适用于高性能网络服务器、文件传输等场景。
在传统 I/O 操作中,数据在内存中的流动路径,尤其是在处理文件读取和网络发送时数据如何在内核空间和用户空间之间传递。让我们逐步解析这些操作:
1. 从磁盘读取数据到内核缓冲区
-
内核缓冲区(Kernel Buffer):当你从磁盘读取数据时,操作系统首先将数据从磁盘读取到内核空间的一个缓冲区中。内核空间是操作系统核心代码运行的地方,具有最高权限。普通应用程序无法直接访问内核空间。
-
过程:当应用程序执行诸如
read()
之类的系统调用时,操作系统会通过文件系统层将磁盘上的数据块加载到内核缓冲区中。这通常使用 DMA(直接内存访问)技术,由硬件控制器直接将数据从磁盘传输到内存,而无需占用 CPU 资源。
2. 将数据从内核缓冲区复制到用户空间缓冲区
-
用户空间缓冲区(User Space Buffer):用户空间是应用程序运行的区域。为了确保系统的稳定性和安全性,应用程序运行在用户空间,不能直接访问内核空间中的数据。
-
过程:内核完成数据读取后,会将数据从内核缓冲区复制到应用程序在用户空间分配的缓冲区中。这是通过
copy_to_user()
等机制完成的。这一步的原因是用户空间和内核空间隔离,应用程序无法直接访问内核缓冲区中的数据,所以需要通过拷贝将数据传递给用户空间。
3. 将数据从用户空间缓冲区复制回内核缓冲区
-
为什么复制回内核缓冲区?:如果应用程序需要将数据发送到网络或写回磁盘,数据必须再次进入内核空间。这是因为网络栈和文件系统都运行在内核空间,而用户空间无法直接操作这些设备。
-
过程:当应用程序需要发送数据(例如,通过
write()
或send()
调用),数据会从用户空间缓冲区再次复制回内核缓冲区。这个操作确保数据能够被内核控制的网络栈或文件系统处理。
4. 通过网络将数据从内核缓冲区发送出去
-
网络发送的过程:一旦数据回到内核缓冲区,操作系统通过网络栈处理数据,并通过网络接口卡(NIC)将数据发送到网络上。这通常涉及到网络协议栈中的各层(如 TCP/IP 协议栈)的处理。
-
网络接口卡(NIC):数据从内核缓冲区传输到 NIC,NIC 再将数据打包成网络帧,通过物理网络发送到目标地址。网络接口卡可能使用 DMA 直接从内存中读取数据,从而减轻 CPU 的负担。
总结这些步骤
- 读取数据:从磁盘到内核缓冲区(内核空间),通常使用 DMA 完成。
- 用户读取:将数据从内核缓冲区复制到用户空间缓冲区。
- 数据准备发送:将数据从用户空间缓冲区复制回内核缓冲区,准备通过网络发送。
- 网络发送:通过网络接口卡将数据发送到网络上。
这些步骤反映了传统 I/O 操作的常规数据流动路径,其中每次内核空间与用户空间之间的数据交互都会涉及一次数据拷贝,这也是为什么在高性能应用中,减少这些拷贝(通过零拷贝技术)会显著提升效率的原因。