文件拷贝--零拷贝技术
前言:零拷贝技术是一种优化数据传输和存储性能的技术,它可以避免数据在用户空间和内核空间之间的复制以及减少CPU的参与。本文将介绍零拷贝技术的原理、应用场景以及现有的一些实现方式。
一、传统拷贝
传统数据拷贝是指使用传统方式进行数据复制或数据传输的过程。在传统数据拷贝中,通常涉及将数据从源位置复制到目标位置,这可以是在同一台计算机内部的不同存储介质之间,也可以是在网络上的不同计算机之间。
以下是传统数据拷贝的一般过程:
①打开源位置:首先,需要打开源位置以便读取源数据。
②创建目标位置:在进行数据拷贝之前,需要在目标位置分配足够的存储空间来存储拷贝后的数据。
③从源位置读取数据:读取源位置中的数据。
④将数据写入目标位置:将读取到的数据写入到目标位置。
⑤关闭源位置和目标位置:在拷贝完成后,需要关闭源位置和目标位置。
拷贝数据传输简图如下:
图中可以看出,在单次拷贝时,总共涉及到4次空间切换、2次DMA拷贝、2次CPU拷贝;
由此可以看出,传统数据拷贝通常涉及将数据从源位置复制到内核缓冲区(或称为用户缓冲区),然后再从内核缓冲区复制到目标位置。效率会非常低下
二、零拷贝技术介绍
1.技术说明
零拷贝(Zero-copy)是一种计算机系统优化技术,旨在减少数据在内存和设备之间的复制操作。传统上,在数据从一个存储区域(如磁盘)传输到另一个存储区域(如网络卡)时,需要将数据从一个缓冲区复制到另一个缓冲区,这涉及 CPU 和内存之间的数据传输。
零拷贝技术的目标是消除不必要的数据复制,从而提高系统性能和效率。它通过直接在数据源和目标之间共享内存区域,避免了中间的复制操作,从而减少了CPU和内存之间的数据传输次数。这种技术在高性能、低延迟的场景中特别有用,例如网络数据传输、文件系统、数据库等。
2.主要技术手段
①零拷贝文件传输
在传统的文件传输中,数据从磁盘读取到内核缓冲区,再从内核缓冲区复制到用户缓冲区,最后再传输到网络。而零拷贝技术通过直接将数据从内核缓冲区传输到网络,省去了数据拷贝的步骤。
②零拷贝套接字传输
在传统的套接字传输中,数据从应用程序传输到内核缓冲区,再从内核缓冲区传输到网络。而零拷贝技术通过使用DMA(直接内存访问)等技术,直接将应用程序的数据传输到网络,减少了数据在内核与用户空间之间的拷贝操作。
以sendfile的零拷贝为例,数据传输简图如下:
使用零拷贝技术后,只有2次状态切换、1次CPU拷贝、2次DMA拷贝。极大的提升了流程效率;总体看,对比传统拷贝,零拷贝技术通过减少数据拷贝次数、降低内存复制开销、减少上下文切换,提高了数据传输的效率和系统性能。
三、零拷贝实现介绍
目前,主要有以下几种零拷贝技术的实现方式:
1.mmap+memcpy方式
该方式利用了内存映射+内存拷贝的方式实现零拷贝技术,在传输过程中不需要将数据从内核空间拷贝到用户空间。其中内存映射(mmap)是一种将磁盘文件映射到进程的虚拟内存空间的技术。mmap系统调用会创造一个虚拟内存映射区域,使得应用程序可以直接读写磁盘文件。当文件通过mmap映射到进程地址空间后,可以直接使用memcpy函数将文件数据复制到其他内存区域,实现零拷贝操作。
代码示例:
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
void fileCopyMmap(const std::string& srcFile, const std::string& destFile) {
int srcFd = open(srcFile.c_str(), O_RDONLY);
int destFd = open(destFile.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
// 获取源文件的大小
off_t fileSize = lseek(srcFd, 0, SEEK_END);
lseek(srcFd, 0, SEEK_SET);
// 将源文件映射到内存
char* srcData = static_cast<char*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, srcFd, 0));
// 将目标文件扩展到与源文件相同的大小
ftruncate(destFd, fileSize);
// 将映射的内存区域拷贝到目标文件
char* destData = static_cast<char*>(mmap(nullptr, fileSize, PROT_WRITE, MAP_SHARED, destFd, 0));
memcpy(destData, srcData, fileSize);
// 解除内存映射
munmap(srcData, fileSize);
munmap(destData, fileSize);
close(srcFd);
close(destFd);
}
2.sendfile方式
sendfile是一个在两个文件描述符之间直接传输数据的系统调用。它通常用于在网络套接字和文件之间进行数据传输,从一个文件描述符读取数据并直接将其发送到另一个文件描述符,而无需将数据从内核缓冲区复制到用户缓冲区。值得注意的是,sendfile在内核中实现了数据的零拷贝过程,减少了数据在内核空间和用户空间之间的拷贝
代码示例:
#include <iostream>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <unistd.h>
void fileCopySendfile(const std::string& srcFile, const std::string& destFile) {
int srcFd = open(srcFile.c_str(), O_RDONLY);
int destFd = open(destFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
// 获取源文件的大小
off_t fileSize = lseek(srcFd, 0, SEEK_END);
lseek(srcFd, 0, SEEK_SET);
// 使用 sendfile 进行文件拷贝
off_t offset = 0;
sendfile(destFd, srcFd, &offset, fileSize);
close(srcFd);
close(destFd);
}
3.copy_file_range方式:
copy_file_range是一个Linux上的系统调用,用于将文件的数据从一个文件描述符拷贝到另一个文件描述符,支持在两个文件描述符之间进行零拷贝操作。这个系统调用的目的是减少从一个文件描述符到另一个文件描述符的数据复制次数,以减少数据的传输时间和消耗。
代码示例:
#include <unistd.h>
#include <fcntl.h>
void fileCopyRange(const std::string& srcFile, const std::string& destFile) {
int src_fd = open(srcFile.c_str(), O_RDONLY);
int dest_fd = open(destFile.c_str(), O_WRONLY | O_CREAT, 0644);
//使用copy_file_range进行拷贝
ssize_t ret = copy_file_range(src_fd, NULL, dest_fd, NULL, 0, 0);
close(src_fd);
close(dest_fd);
return 0;
}
4.实现区别
mmap+memcpy 和 sendfile 都是基于内核中间缓冲区的零拷贝技术,而 copy_fiile_range 则是直接从一个文件描述符拷贝到另一个文件描述符。
mmap+memcpy 需要将文件内容映射到进程的虚拟内存空间,可能会占用较多的内存资源。
sendfile 是专为网络传输设计的,适用于大量数据的传输,不需要中间内存缓冲区。
copy_file_range 可以用于文件和文件之间的数据传输,不依赖于中间内存缓冲区,但要求文件系统支持该系统调用。
不同的零拷贝方式适用于不同的场景,具体的选择应根据应用程序的需求和操作系统的支持来确定。
四、本文小结
零拷贝技术是优化数据传输效率和提高系统性能的重要手段。通过避免数据拷贝操作,零拷贝技术可以显著减少资源开销,提高系统的吞吐量和响应速度。从长远来看,零拷贝技术有望成为软件开发中的重要技术趋势,助力构建高性能、高效率的系统。