linux 零拷贝

简介: linux 零拷贝

1、传统IO

传统 io 的执行流程

  • read:将数据从 IO 设备读取到内核缓存区中,再将数据从内核缓冲区拷贝到用户缓冲区
  • write:将数据从用户缓冲区写入到内核缓冲区中,再将数据从内核缓冲区拷贝到 IO 设备

read/write 属于系统调用 syscall,每一次系统调用 ,发生两次上下文切换

  • 调用 syscall 从用户态切换到内核态
  • syscall 返回从内核态切换到用户态

如图所示,传统 IO 的过程中,发生了4次空间切换 + 4次拷贝

io 流程

不难看出,传统模式下的IO,涉及多次空间切换和数据冗余拷贝,效率并不高。而零拷贝 Zero-Copy 目的就是降低冗余数据拷贝,解放 CPU

  • 减少数据在内核缓冲区和用户缓冲区之间的冗余拷贝(CPU拷贝)
  • 减少系统调用导致的空间切换

目前来看,零拷贝技术的实现手段主要包括:mmap+write、sendfile、sendfile+DMA、splice

2、零拷贝

2.1、mmap

内存映射 memory mapping, mmap 是 linux 提供的内存映射文件的机制,将一个虚拟内存区域与一个磁盘上的对象(普通文件或匿名文件)关联起来。

可以将内核读缓冲区地址与用户缓冲区地址进行映射,实现内核缓冲区与用户缓冲区的共享。这样就减少了一次用户态和内核态的CPU拷贝。

mmap + write 流程如图所示,发生了4次切换 + 2次DMA拷贝 + 1次CPU拷贝

mmap

函数原型

#include <sys/mman.h>
 // 内存映射
 void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
 // 解除映射
 int munmap(void *addr, size_t length);

函数参数:

  • start:指定映射的虚拟内存地址,通常定义为 NULL,由内核选定地址
  • length:映射的长度
  • prot:描述映射内存的访问权限
    PROT_EXEC页面可以被 cpu 执行指令组成,PROT_NONE 页面不能访问
    PROT_READ 页面可读,PROT_WRITE 页面可写,
  • flags:指定映射的类型,MAP_SHARED共享对象,MAP_PRIVATE私有的,写时复制对象
  • fd:要进行映射的文件句柄
  • offset:文件偏移量

范例

发送方

// 建立内存映射
 char *pMap = (char*) mmap (NULL, fileInfo.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 
 send(clientFd, pMap, fileInfo.st_size, 0);
 // 解除映射
 munmap(pMap, fileInfo.st_size);

接收方

// 使用 mmap 前用使用 ftruncate 来扩大文件大小
 ftruncate(fd, fileSize);
 char *pMap = (char*) mmap (NULL, fileSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
 recvCycle(sfd, pMap, fileSize);
 munmap(pMap, fileSize);

mmap 存在的问题:mmap 对大文件传输有一定优势,但是小文件可能出现碎片,并且在多个进程同时操作文件时可能产生引发 coredump 的 signal。

4.2.2、sendfile

mmap+write 方式有一定改进,但是由系统调用引起的状态切换并没有减少,因此在 Linux 内核2.1版本中引入了 sendfile 系统调用。

sendfile 在两个文件之间通过内核直接传输数据,避免了内核缓冲区和用户缓冲区之间的数据拷贝操作。sendfile 只能用于发送数据,不能用于接收数据。

sendfile 方式只使用一个函数就可以完成之前的 read+write 和 mmap+write 的功能,这样减少一个系统调用(2次状态切换),由于数据不经过用户缓冲区,因此该数据无法被修改。

sendfile 的流程如图所示, 发生了2次切换 + 2次DMA拷贝+1次CPU拷贝

sendfile

linux2.4版本后,对 sendfile 系统调用进行优化,配合硬件 DMA,可以直接从内核空间缓冲区中将数据拷贝到网卡,彻底省去了CPU拷贝。

如图所示,sendfile + DMA 的过程中发生了2次切换 + 2次DMA拷贝 + 0次CPU拷贝

sendfile + DMA

sendfile 函数原型

#include <sys/sendfile.h>
 ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
 /*
 参数
 - out_fd:待写入内容的文件描述符
 - in_fd:待读出内容的文件描述符
 - offset:文件偏移量
 - count:传输的字节数
 */

sendfile(clientFd, fd, 0, fileInfo.st_size);

sendfile 存在的问题:无法对数据进行修改,并且需要硬件层面DMA的支持,并且 sendfile 只能将文件数据拷贝到 socketfd,有一定的局限性。

4.2.3、splice

splice 系统调用在 Linux 2.6 版本引入,不需要硬件支持,并且不再限定于 socket 上,实现了两个普通文件之间的数据零拷贝。

可以在内核缓冲区和 socket 缓冲区间建立管道来传输数据,避免了两者之间的 CPU 拷贝操作。

splice

函数原型

#define _GNU_SOURCE 
 #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);
 /*
 返回值;成功返回接收到的字节数,失败-1
 参数
 - fd_in:待输入数据的文件描述符。
 - off_in: 输入流偏移量。若 fd_in 是管道文件描述符,则设置为 NULL,表示从当前偏移读入。   否则,off_in 表示从输入数据流的某处开始读取。
 - fd_out:待输出数据的文件描述符。
 - off_out:输出流偏移量,同上。
 - len:单次写入的数据长度,最多65536
 - flags:0
 */

例:服务器端代码: transFile.c

int fds[2];
 pipe(fds);
 int recvLen = 0;
 //当读到的数据量超过文件大小时,即已经读取数据完成
 while(recvLen < fileInfo.st_size){
     //将数据从服务器端本地读到管道
     ret = splice(fd, 0, fds[1], 0, 65536, 0);
     //将数据从管道读到客户端
     ret = splice(fds[0], 0, clientFd, 0, ret, 0);
     //计算已经读到的数据量
     recvLen += ret;
 }

splice 存在的问题:它的两个文件描述符中有一个必须是管道设备

4.3、零拷贝的问题

  • 数据传输的瓶颈在于网络带宽,而不是数据的拷贝速度。
  • mmap 和 sendfile 不能传输超过 2GB 的文件
  • 零拷贝接口可移植性差

4.4、参考

相关文章
|
8天前
|
Shell Linux C++
【Shell 命令集合 文件管理】Linux 拷贝命令 cp命令使用指南
【Shell 命令集合 文件管理】Linux 拷贝命令 cp命令使用指南
85 0
|
8天前
|
Linux 数据处理 C++
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(一)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
84 0
|
8天前
|
自然语言处理 数据挖掘 Linux
ModelScope问题之拷贝到内网linux系统运行代码报错如何解决
本合集将提供ModelScope安装步骤、配置要求和环境准备,以便用户顺利启动ModelScope进行模型开发和测试。
76 0
|
8天前
|
存储 Linux API
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(三)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
40 1
|
8天前
|
消息中间件 Linux 数据处理
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(二)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
42 1
|
8天前
|
Linux
Linux 零拷贝splice函数
splice是 Linux 系统中用于在两个文件描述符之间移动数据的系统调用。它的主要作用是在两个文件描述符之间传输数据,而无需在用户空间进行数据拷贝。也是零拷贝操作.
42 0
Linux 零拷贝splice函数
|
8天前
|
Linux C语言
Linux 零拷贝sendfile函数
sendfile函数允许在两个文件描述符之间直接传输数据,而无需将数据从内核空间复制到用户空间再发送。它在 Linux 系统上首次出现于 2.2 内核版本。效率很高,这被称为零拷贝。out_fd是输出文件描述符,通常是网络套接字描述符。in_fd是输入文件描述符,通常是打开的文件或套接字。offset是一个指向 off_t 类型的指针,用于指定从输入文件的哪个位置开始传输数据。如果为NULL,则从当前文件偏移量开始传输。count是要传输的字节数。
52 0
|
8天前
|
NoSQL Java Linux
Linux常用命令(文件目录操作、拷贝移动、打包压缩、文本编辑、查找)
Linux常用命令(文件目录操作、拷贝移动、打包压缩、文本编辑、查找)
|
8天前
|
Shell Linux
Linux|shell编程|拷贝大文件之显示进度条
Linux|shell编程|拷贝大文件之显示进度条
66 0
|
8天前
|
存储 缓存 算法
Linux系统中内核态、用户态和零拷贝技术解析
Linux系统中内核态、用户态和零拷贝技术解析
86 0