简介

  1. 零拷贝技术主要是为了提高读写性能。

实现零拷贝的方式

1.sendfile
  1. Linux提供了一个系统调用sendfile,用于在内核中完成从一个文件描述符到另一个文件描述符数据的拷贝。其函数原型如下所示:
#include <sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
函数参数:
    out_fd:a descriptor opened for writing
    in_fd:a file descriptor opened for reading
    offset:offset不为null时,这是传入传出参数。作为传入参数表示偏移量,指定从in_fd的起始位置开始读
        作为传出参数,表示sendfile从in_fd读数据的结束位置
    count:两个文件描述符之间拷贝的字节数
函数返回值:
    成功返回写入out_fd文件描述符的字节数
    失败返回
  1. sendfile是实现零拷贝的一种方式。假设这样一个场景:一个客户端需要将本地磁盘上的文件通过网络上传到服务端。如果没有这个系统调用,为了实现这个需求,一定会经历这样一个过程:通过read系统调用读取内核缓冲区的数据到用户缓冲区,再通过write系统调用将用户缓冲区的数据写入套接字缓冲区。其中包含了四次内核态与用户态之间的转换,两次拷贝。而使用sendfile以后,只需要两次状态转换,一次从内核缓冲区到套接字缓冲区的拷贝。
2.splice
  1. Linux提供了一个系统调用splice,用于在内核中完成从一个文件描述符到另一个文件描述符数据的拷贝。其函数原型如下所示:
#include <fcntl.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out,
              loff_t *off_out, size_t len, unsigned int flags);
函数参数:
    fd_in:a file descriptor opened for reading
    off_in:指定从fd_in的起始位置开始读
    fd_out:a descriptor opened for writing
    off_out:指定从fd_out的起始位置开始写
    len:两个文件描述符之间拷贝的字节数
    flags:下面标志的位或
        SPLICE_F_NONBLOCK
        SPLICE_F_MORE
        SPLICE_F_GIFT:未使用
函数返回值:
    成功返回两个文件描述符之间拷贝的字节数
    返回-1表示失败
    返回0表示there  was no data to transfer
  1. splice也是实现零拷贝的一种方式,使用这个系统调用,两个文件描述符之间必须有一个是管道。
3.mmap+write
  1. Linux提供了一个系统调用mmap来实现用户级内存映射,将内核中读缓冲区地址与用户空间缓冲区地址进行映射,从而实现内核缓冲区与用户缓冲区的共享。这样可以减少一次数据从内核缓冲区到用户缓冲区之间的拷贝,但是还需要使用write系统调用将用户缓冲区的数据写入套接字缓冲区。其函数原型如下所示:
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
          int fd, off_t offset);
函数参数:
    addr:指定新映射内存区域的起始地址,一般0即可
    length:指定映射区的长度,必须大于0
    prot:指定新映射的虚拟内存区域的访问权限位
        PROT_READ: Pages may be read.
        PROT_WRITE: Pages may be written.
        PROT_NONE:Pages may not be accessed
        PROT_EXEC:Pages may be executed,这个区域内的页面由可以被CPU执行的指令组成
    flags:描述被映射对象类型
        MAP_SHARED:表示被映射的对象是一个共享对象,对其他映射到相同区域的进程来说可见
        MAP_PRIVATE:表示被映射的对象是一个私有,写时复制对象,
    fd:指定需要映射的文件
    offset:指定从fd的那个文件的指定位置开始映射,一般offset为0,因为offset必须为4K(page size)的倍数。
函数返回值:成功时返回映射区的首地址,失败返回MAP_FAILED。