文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

浅谈Linux的零拷贝技术

2023-04-28 11:01

关注

前言

在linux系统内部缓存和内存容量都是有限的,更多的数据都是存储在磁盘中。对于Web服务器来说经常需要从磁盘中读取数据到内存,然后再通过网卡传输给用户

img

那么这也算一次I O的过程,都知道IO过程中需要状态的切换还有一系列拷贝过程,都是要时间开销的,那么怎么优化用户态和内核态的状态的切换次数和各种缓冲区之间的拷贝次数,也是linux的服务器实现高并发的重要技术了!

传统数据交互

传统 io 的执行流程: 下面将图左半部分read过程的硬件抽象为磁盘; 图右半部分write过程的硬件设为网卡,模拟webserver进行一次IO的过程; 方便理解;

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

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

在这里插入图片描述

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

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

零拷贝

首先解释一下,零拷贝中的0,指的是CPU级别的数据拷贝(比如内核缓冲区到用户缓冲区的拷贝,用户缓冲区再到socket缓冲区; 或者内核缓冲区直接到socket缓冲区的拷贝!),并不是DMA硬件的拷贝,否则数据不靠DMA怎么转移呢?

mmap+write

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

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

在这里插入图片描述

函数原型

#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);

例: 发送方:

// 建立内存映射
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充当read的功能,进行一次完整的IO,减少了传统方式read数据的时候,从内核态CPU拷贝到用户态的这次拷贝; (发生了4次切换 + 2次DMA拷贝 + 1次CPU拷贝;)

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

sendfile

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

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

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

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

在这里插入图片描述

sendfile + DMA

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

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

在这里插入图片描述

sendfile 函数原型

#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);



例:

发送方

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

小结

早期sendfile : 2次切换 (sendfile后,数据不用过用户层了,导致不能修改了,不过也少了两次状态切换!)+ 2次DMA拷贝(磁盘到内核,socket缓冲区到网卡)+ 1次CPU拷贝(内核到socket缓冲区)

改良的sendfile + DMA : 发生了2次切换 + 2次DMA拷贝(磁盘到内核,内核直接到网卡) + 0次CPU拷贝

sendfile 存在的问题:无法对数据进行修改(数据没上到用户层,也没必要,webserver一般都不需要修改,返回的本地的资源!),并且需要硬件层面DMA的支持,并且 sendfile 只能将文件数据拷贝到 socketfd,有一定的局限性。

splice

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

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

在这里插入图片描述

函数原型

#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);



例:web服务器端代码: 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 引入管道机制,实现了普通文件之间的0拷贝,突破了仅限于socket的sendfile0拷贝;

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

到此这篇关于浅谈Linux的零拷

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯