Skip to content

Linux零拷贝

  • mmap: map core buffer to user buffer
  • 使用mmap代替read,减少内核缓冲区到用户缓冲区的拷贝
  • tmp_buf = mmap(file, len);
  • write(socket, tmp_buf, len);
  • 应用程序通过mmap,磁盘的数据通过DMA拷贝至core buffer,此段buffer于用户进程共享,然后write操作,操作系统直接拿core buffer内容拷贝至socket的缓冲区,然后通过DMA copy发送至网卡。
  • 相比常规的 read & write,减少了一些copy,利用映射代替copy,适用于大范围的数据。
  • 但由于是对文件的内存映射,所以当前文件若被其他进程截断等操作,write将会报SIGBUS中断,导致进程被杀死。(解决方法:1. 为SIGBUS注册新的信号处理器;2. 利用文件租界锁(推荐)
  • sendfile: fd -> socketfd, transfer
  • 数据对应用程序不可见,适用于应用进程地址空间不需要对所访问的数据进行处理的情况,局限于基于文件服务的网络应用程序。
  • 磁盘文件 -DMA copy-> 内核页缓存 -CPU copy-> socket缓存 -DMA copy-> 网络/网卡
  • 借助文件描述符来实现数据拷贝:直接将文件描述in_fd的数据拷贝给文件描述符out_fd,其中in_fd是数据提供方,out_fd是数据接收方。文件描述符的操作都是在内核进行的,不会经过用户空间,所以数据不用拷贝到app buffer,实现了零复制。
  • 基于硬件提供的数据收集拷贝功能,可以减少中间的CPU copy
  • splice: pipe to-ot fd, move
  • 在两个文件描述符之间移动数据,且其中一个描述符必须是管道描述符。由于不需要在kernel buffer和app buffer之间拷贝数据,所以实现了零复制
  • tee: pipe inner copy
  • 在两个管道描述符之间复制数据。由于从in_fd复制给另一个管道out_fd时,不认为数据是来自于in_fd的,所以复制数据后,in_fd仍可使用splice()函数进行数据移动。由于没有经过用户空间,所以实现了零复制。
  • Linux下的tee程序就是使用tee函数结合splice函数实现的,先将数据通过tee()函数拷贝给管道,再使用splice()函数将数据移动给另一个文件描述符。
C++
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/uio.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <pthread.h>
int main(int argc, char* argv[])
{
    if (argc != 2) {
        printf("usage: %s <file?\n", argv[0]);
        return 1;
    }
    int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
    assert(filefd > 0);

    int pipefd_stdout[2];
    int ret = pipe(pipefd_stdout);
    assert(ret != -1);

    int pipefd_file[2];
    ret = pipe(pipefd_file);
    assert(ret != -1);
    // in -> fdstdout
    // splice: pipe to-ot fd
    ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_NONBLOCK);
    assert(ret != -1);
    // fdstdout -> fdfile
    // tee : pipe inner copy
    ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK);
    assert(ret != -1);
    // fdfile -> filefd
    ret = splice(pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    // fdstdout -> stdout
    ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    close(filefd);
    close(pipefd_stdout[0]);
    close(pipefd_stdout[1]);
    close(pipefd_file[0]);
    close(pipefd_file[1]);
    return 0;
}