Linux中的零拷贝技术

一.传统的Linux数据传输方式

传统的 Linux 系统的标准 I/O 接口(read、write)是基于数据拷贝的,也就是数据需要从内核高速缓存拷贝到用户缓存,或者从用户缓存拷贝到内核高速缓冲区的过程。

  1.传统数据传输的优缺点:

      好处:数据已经加载到了缓存中,一旦数据加载到缓存中就不再需要做磁盘IO操作

     坏处: 需要做大量的用户态到内核态的切换,打打损耗了cpu的效率,同时也影响了数据的传输的速率;

二.什么是零拷贝

 零拷贝技术的使用就是为了减少数据的拷贝次数,从而提高cpu的性能;虽然叫做“零拷贝”但是也并不是拷贝次数真的为0,意思是尽可能的减少数据的拷贝次数而已;

零拷贝技术大致分为一下几大类:

三.零拷贝技术的详细分析

(一)原始数据拷贝方式

 我们以从磁盘读取数据到发送数据到网络为例,为大家讲解传统数据是如何进行拷贝的。

  Linux系统中传统的数据传输是通过read()和write()函数实现的大致过程如下所示:

(1)read()借助DMA将磁盘上的数据读取到操作系统的内核缓存中;

(2)用户空间的应用想使用数据需要将数据从内核空间拷贝用户空间;

(3)write()将数据从用户存储空间将数据拷贝用户态空间;

(4)数据从内核空间通过DMA发送网卡;

从上图中我们可以看出数据想与硬件进行交互必须使用到DMA,所以整个过程两次DMA操作必不可少,但是两次与cpu操作完全没有必要,大大浪费cpu的性能;

(二)零拷贝主要实现方法

    一.内核态直接IO

这种方法可以使应用程序或者运行在用户态下的库函数直接访问硬件设备,数据直接跨过内核进行传输,内核在整个数据传输过程除了会进行必要的虚拟存储配置工作之外,不参与其他任何工作,这种方式能够直接绕过内核,极大提高了性能。

注:这种是用户态的应用直接访问硬件设备进行数据交换,不通过内核态进行数据传输,这种方式看上去减少的数据传输的次数,实际上使用硬件IO,实际上性能大大降低;

缺陷:

  1. 这种方法只能适用于那些不需要内核缓冲区处理的应用程序,这些应用程序通常在进程地址空间有自己的数据缓存机制,称为自缓存应用程序,如数据库管理系统就是一个代表。
  2. 这种方法直接操作磁盘 I/O,由于 CPU 和磁盘 I/O 之间的执行时间差距,会造成资源的浪费,解决这个问题需要和异步 I/O 结合使用.

    二.mmap 方法

这种方法,使用 mmap 来代替 read,可以减少一次拷贝操作,如下:

buf = mmap(diskfd, len);write(sockfd, buf, len);

应用程序调用 mmap ,磁盘文件中的数据通过 DMA 拷贝到内核缓冲区,接着操作系统会将这个缓冲区与应用程序共享,这样就不用往用户空间拷贝。应用程序调用write ,操作系统直接将数据从内核缓冲区拷贝到 socket 缓冲区,最后再通过 DMA 拷贝到网卡发出去。

缺陷:

mmap 隐藏着一个陷阱,当 mmap 一个文件时,如果这个文件被另一个进程所截获,那么 write 系统调用会因为访问非法地址被 SIGBUS 信号终止,SIGBUS 默认会杀死进程并产生一个 coredump,如果服务器被这样终止了,那损失就可能不小了

解决方法:

使用文件的租借锁:首先为文件申请一个租借锁,当其他进程想要截断这个文件时,内核会发送一个实时的 RT_SIGNAL_LEASE 信号,告诉当前进程有进程在试图破坏文件,这样 write 在被 SIGBUS 杀死之前,会被中断,返回已经写入的字节数,并设置 errno 为 success。

通常的做法是在 mmap 之前加锁,操作完之后解锁。

    三.sendfile方法

从Linux 2.1版内核开始,Linux引入了sendfile,也能减少一次拷贝。

#include<sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

sendfile 是只发生在内核态的数据传输接口,没有用户态的参与,自然避免了用户态数据拷贝。它指定在 in_fd 和 out_fd 之间传输数据,其中,它规定 in_fd 指向的文件必须是可以 mmap 的,out_fd 必须指向一个套接字,也就是规定数据只能从文件传输到套接字,反之则不行。sendfile 不存在像 mmap 时文件被截获的情况,它自带异常处理机制

缺陷:

  1. 只能适用于那些不需要用户态处理的应用程序。

     四.DMA辅助的sendfile方法

常规 sendfile 还有一次内核态的拷贝操作,能不能也把这次拷贝给去掉呢

答案就是这种 DMA 辅助的 sendfile。

这种方法借助硬件的帮助,在数据从内核缓冲区到 socket 缓冲区这一步操作上,并不是拷贝数据,而是拷贝缓冲区描述符,待完成后,DMA 引擎直接将数据从内核缓冲区拷贝到协议引擎中去,避免了最后一次拷贝

缺陷:

  1. 除了3.4 中的缺陷,还需要硬件以及驱动程序支持。
  2. 只适用于将数据从文件拷贝到套接字上。(使用这种方式进行数据传输数据的流向有明确的要求)

   五.splice方法  

splice 去掉 sendfile 的使用范围限制,可以用于任意两个文件描述符中传输数据。

#define _GNU_SOURCE /* See feature_test_macros(7) */

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

但是 splice 也有局限,它使用了 Linux 的管道缓冲机制,所以,它的两个文件描述符参数中至少有一个必须是管道设备。

splice 提供了一种流控制的机制,通过预先定义的水印(watermark)来阻塞写请求,有实验表明,利用这种方法将数据从一个磁盘传输到另外一个磁盘会增加 30%-70% 的吞吐量,CPU负责也会减少一半。

缺陷:

  1. 同样只适用于不需要用户态处理的程序
  2. 传输描述符至少有一个是管道设备。

六.写时复制

在某些情况下,内核缓冲区可能被多个进程所共享,如果某个进程想要这个共享区进行 write 操作,由于 write 不提供任何的锁操作,那么就会对共享区中的数据造成破坏,写时复制就是 Linux 引入来保护数据的。

写时复制,就是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中,这样做并不影响其他进程对这块数据的操作,每个进程要修改的时候才会进行拷贝,所以叫写时拷贝。这种方法在某种程度上能够降低系统开销,如果某个进程永远不会对所访问的数据进行更改,那么也就永远不需要拷贝。

缺陷:

需要 MMU 的支持,MMU 需要知道进程地址空间中哪些页面是只读的,当需要往这些页面写数据时,发出一个异常给操作系统内核,内核会分配新的存储空间来供写入的需求。

七.缓冲区共享

这种方法完全改写 I/O 操作,因为传统 I/O 接口都是基于数据拷贝的,要避免拷贝,就去掉原先的那套接口,重新改写,所以这种方法是比较全面的零拷贝技术,目前比较成熟的一个方案是最先在 Solaris 上实现的 fbuf (Fast Buffer,快速缓冲区)。

Fbuf 的思想是每个进程都维护着一个缓冲区池,这个缓冲区池能被同时映射到程序地址空间和内核地址空间,内核和用户共享这个缓冲区池,这样就避免了拷贝。

缺陷:

  1. 管理共享缓冲区池需要应用程序、网络软件、以及设备驱动程序之间的紧密合作
  2. 改写 API ,尚处于试验阶段。

  四.高性能网络 I/O 框架——netmap

netmap框架就是零拷贝技术应用

参考文档:https://blog.youkuaiyun.com/fengfengdiandia/article/details/52869290

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值