缘起
我们的计算机大致包含内核、用户两种形态:
- 内核态:操作系统层,负责各种系统指令、空间管理等。
- 用户态:应用程序层,我们的业务程序。
当然,我们绝大部分开发的应用都是属于应用程序层,在用户态环境运行。用户态 与 内核态交互关系如下:
两层之间通过 API 交互,不能直接访问操作,避免相互影响产生各类安全问题。
正因为如此,两层数据传输经常需要多次数据拷贝。比如文件拷贝时,要从内核态拷贝到用户态(A文件读取),然后从用户态拷贝到内核态(B文件写入[拷贝])。
这种模式在请求量小的情况下不会有性能问题,一旦并发量上去了,消耗大量CPU、两态上下文切换、频繁I/O等,性能将极大受限。
有没有方式减少数据拷贝次数(减少 用户态 -> 内核态,内核态 -> 用户态)?
有,这就是零拷贝技术,比如上面的文件拷贝,直接从A内核态拷贝到B内核态,减少了内核态->用户态,用户态->内核态的拷贝次数,从而提升效率。
零拷贝
什么是零拷贝?
零拷贝(Zero Copy)是一种优化技术,旨在提高数据传输效率,减少 CPU 占用和内存带宽消耗。它通过尽量减少数据在内存中的拷贝次数来实现这一目标。
零拷贝技术在操作系统和网络编程中尤为重要,特别是在需要高吞吐量和低延迟的应用场景中,如视频流、文件传输和网络服务器。
[零]指不需要拷贝?
零拷贝(Zero Copy)并不意味着完全没有数据拷贝,而是指在数据传输过程中尽量减少不必要的数据拷贝操作。
传统的数据传输通常涉及多次数据拷贝,例如从磁盘到内核缓冲区,再从内核缓冲区到用户空间,最后从用户空间到网络缓冲区。
而零拷贝技术通过优化这些步骤,减少了数据在内存中的拷贝次数。
零拷贝带来哪些提升?
零拷贝,主要用于提高数据传输效率,减少 CPU 占用和内存带宽消耗。它通过避免不必要的数据拷贝操作来提升性能。零拷贝解决的问题:
- 减少 CPU 占用:传统的数据传输需要多次拷贝数据(例如,从磁盘到内核缓冲区,再从内核缓冲区到用户缓冲区),每次拷贝都需要 CPU 参与。零拷贝技术通过减少这些拷贝次数,降低了 CPU 的负载。
- 提高数据传输速度:由于减少了数据在内存中的拷贝次数,数据传输速度得以提升。这对于需要高吞吐量的应用程序(如视频流、文件传输)尤为重要。
- 降低内存带宽消耗:每次数据拷贝都会消耗内存带宽。通过减少拷贝次数,零拷贝技术可以降低内存带宽的消耗,从而提高系统的整体性能。
- 减少延迟:在某些应用场景中,减少数据拷贝可以降低数据传输的延迟,提高实时性。
零拷贝方案
零拷贝技术在操作系统和网络编程中有多种实现方式,例如使用 mmap、sendfile、splice 等系统调用。
在不同的操作系统中,零拷贝的实现细节可能有所不同,但其核心目标都是减少数据在内存中的拷贝次数。
1. mmap
mmap(内存映射)系统调用可以将文件映射到进程的地址空间,从而避免了从内核缓冲区到用户缓冲区的数据拷贝。
使用 mmap 可以直接在用户空间访问文件内容。
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
int main() {
int fd = open("my.txt", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
char *mapped = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
return 1;
}
// 直接访问文件内容
write(STDOUT_FILENO, mapped, sb.st_size);
munmap(mapped, sb.st_size);
close(fd);
return 0;
}
2. sendfile
sendfile 系统调用可以在两个文件描述符之间直接传输数据,而无需在用户空间进行数据拷贝。通常用于在网络套接字和文件之间传输数据。
#include <sys/sendfile.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int source_fd = open("source.txt", O_RDONLY);
int dest_fd = open("dest.txt", O_WRONLY | O_CREAT, 0644);
off_t offset = 0;
struct stat stat_buf;
fstat(source_fd, &stat_buf);
sendfile(dest_fd, source_fd, &offset, stat_buf.st_size);
close(source_fd);
close(dest_fd);
return 0;
}
...
小结
零拷贝是以减少用户态、内核态数据拷贝次数为目的,从而提升数据传输效率。
常见的零拷贝实现方案有:
- 减少用户态和内核态之间的拷贝:通过直接在内核态完成数据传输,避免了数据在用户态和内核态之间的来回拷贝。例如,sendfile 可以直接在文件描述符之间传输数据,而无需将数据拷贝到用户空间。
- 利用内存映射:通过 mmap 将文件映射到用户空间,允许应用程序直接访问文件内容,而无需显式的数据拷贝。
- 使用管道和缓冲区:通过 splice 和 vmsplice 等系统调用,可以在管道和文件描述符之间移动数据,而无需在用户空间进行拷贝。
- 硬件支持:在某些情况下,硬件可以直接参与数据传输,例如 RDMA 技术允许直接在不同计算机的内存之间传输数据。
简言之,零拷贝的核心思想是通过减少不必要的拷贝操作来提高数据传输的效率,而不是完全消除所有的数据拷贝。
值得注意的是,某些情况下,仍然需要进行一些必要的拷贝操作,但总体上,零拷贝技术能够显著降低 CPU 和内存带宽的消耗。