linux 零拷贝goodlen,linux零拷贝技术

简介零复制(英语:Zero-copy;也译零拷贝)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。

零拷贝操作减少了在用户空间与内核空间之间切换模式的次数。

8dadc7772c87bf60586939f0ff72eeb1.png

传统的I/O操作进行了3次用户空间与内核空间的上下文切换,以及4次数据拷贝。其中4次数据拷贝中包括了2次DMA拷贝和2次CPU拷贝。

Linux 中零拷贝技术的实现方向

一、直接 I/O

对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输。这种方式依旧存在用户空间和内核空间的上下文切换,但是硬件上的数据不会拷贝一份到内核空间,而是直接拷贝至了用户空间,因此直接I/O不存在内核空间缓冲区和用户空间缓冲区之间的数据拷贝。

二、copy-on-write(写时复制技术)

在某些情况下,Linux操作系统的内核空间缓冲区可能被多个应用程序所共享,操作系统有可能会将用户空间缓冲区地址映射到内核空间缓存区中。当应用程序需要对共享的数据进行修改的时候,才需要真正地拷贝数据到应用程序的用户空间缓冲区中,并且对自己用户空间的缓冲区的数据进行修改不会影响到其他共享数据的应用程序。所以,如果应用程序不需要对数据进行任何修改的话,就不会存在数据从系统内核空间缓冲区拷贝到用户空间缓冲区的操作。

三、数据传输不经过用户进程地址空间

在数据传输的过程中,避免数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间进行拷贝。有的时候,应用程序在数据进行传输的过程中不需要对数据进行访问,那么,将数据从 Linux 的页缓存拷贝到用户进程的缓冲区中就可以完全避免,传输的数据在页缓存中就可以得到处理。在某些特殊的情况下,这种零拷贝技术可以获得较好的性能。Linux 中提供类似的系统调用主要有 mmap(),sendfile() 以及 splice()。

mmap()1

2buf = mmap(diskfd, len);

write(sockfd, buf, len);

7c6a9a1c39cdf9f10d319a02a29dcba0.png

过程:

应用进程调用了 mmap() 之后,数据会先通过 DMA 拷贝到操作系统内核缓冲区中去。接着,应用进程跟操作系统共享这个缓冲区。这样,操作系统内核和应用进程存储空间就不需要再进行任何的数据拷贝操作。

应用进程再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态 。

socket缓冲区再把数据发到网卡。

使用 mmap 并不一定能获得理想的数据传输性能。数据传输的过程中仍然需要一次 CPU 拷贝操作,而且映射操作也是一个开销很大的虚拟存储操作,这种操作需要通过更改页表以及冲刷 TLB (使得 TLB 的内容无效)来维持存储的一致性。

sendfile()

为了简化用户接口,同时减少 CPU 的拷贝次数,Linux 在版本 2.1 中引入了 sendfile() 这个系统调用。

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

01ae3cad3451fd58fd502b3518edd57c.png

过程:

sendfile() 系统调用利用 DMA 引擎将文件中的数据拷贝到操作系统内核缓冲区中。

然后数据被拷贝到与 socket 相关的内核缓冲区中去。

接下来,DMA 引擎将数据从内核 socket 缓冲区中拷贝到协议引擎中去。

sendfile() 系统调用不需要将数据拷贝或者映射到应用程序地址空间中去,所以 sendfile() 只是适用于应用程序地址空间不需要对所访问数据进行处理的情况。相对于 mmap() 方法来说,因为 sendfile 传输的数据没有越过用户应用程序 / 操作系统内核的边界线,所以 sendfile () 也极大地减少了存储管理的开销。

现在,已经减少了数据拷贝的次数,但是仍然存在一次CPU拷贝,就是页缓存到socket缓存的拷贝。

借助于硬件的帮助,可以把这个拷贝省略掉。即:

c2aaa73b39daaf0a89f2d4174410a98a.png

sendfile系统调用利用DMA引擎将文件内容拷贝到内核缓冲区去,然后将带有文件位置和长度信息的缓冲区描述符添加socket缓冲区去,这一步不会将内核中的数据拷贝到socket缓冲区中,DMA引擎会将内核缓冲区的数据拷贝到协议引擎中去,避免了最后一次拷贝。

splice()

splice() 可以被看成是类似于基于流的管道的实现,管道可以使得两个文件描述符相互连接,splice 的调用者则可以控制两个设备(或者协议栈)在操作系统内核中的相互连接。

1ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

splice()在两个文件描述符之间移动数据,而不在内核地址空间和用户地址空间之间进行复制。它将文件描述符fd_in中的len个字节的数据传输到文件描述符fd_out,其中一个描述符必须引用一个管道。

如果fd_in引用一个管道,那么off_in必须为NULL。如果fd_in没有引用管道并且off_in为NULL,则从当前文件偏移量开始从fd_in读取字节,并且适当地调整当前文件偏移量。如果fd_in没有引用管道并且off_in不是NULL,那么off_in必须指向一个缓冲区,该缓冲区指定从fd_in读取字节的起始偏移量; 在这种情况下,fd_in的当前文件偏移量不会改变。类似的语句适用于fd_out和off_out。

flags参数是一个位掩码,它由零个或多个下列值组成:

1

2

3

4

5

6

7

8SPLICE_F_NONBLOCK: splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O

,那么调用 splice 有可能仍然被阻塞。

SPLICE_F_MORE: 告知操作系统内核下一个 splice 系统调用将会有更多的数据传来。

SPLICE_F_MOVE: 如果输出是文件,这个值则会使得操作系统内核尝试从输入管道缓冲区直接将数据读入

到输出地址空间,这个数据传输过程没有任何数据拷贝操作发生。如果内核不能从pipe

移动数据或者pipe的缓存不是一个整页面,仍然需要拷贝数据。

splice() 和 sendfile() 的区别与联系

联系:用户应用进程必须拥有两个已经打开的文件描述符,一个用于表示输入设备,一个用于表示输出设备。

区别:

1. splice() 允许任意两个文件之间互相连接

2. sendfile()只适用于文件到 socket 进行数据传输。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值