linux限制拷贝带宽_linux c文件锁限制拷贝 - CSDN
  • linux scp限速传输

    2015-07-07 10:08:44
    在将生产库的备份拷贝到UAT环境时,为降低对生产环境的影响,需要对SCP工具进行限速。 1 [oracle@xxDB01 xxxdb]$ scp -r lv0_20150607 10.0.97.33:/data/ivldb 2 Address 10.0.97....

    在将生产库的备份拷贝到UAT环境时,为降低对生产环境的影响,需要对SCP工具进行限速。

    1 [oracle@xxDB01 xxxdb]$ scp -r lv0_20150607 10.0.97.33:/data/ivldb
    2 Address 10.0.97.33 maps to localhost, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!
    3 oracle@10.0.97.33's password:
    4 20150607.log                                                                       100%   71KB  71.2KB/s   00:00
    5 level_0_ivldb_datafile_700_1                                  10%  787MB  11.2MB/s   10:06 ETAKilled by signal 2.

    上面是未做限速的情况,我看到每秒传送11.2MB,当时以为生产库和UAT数据库之间的网络是百兆网,这不把带宽给跑满了吗,当时吓的汗毛都立起来了,现在想想应该不是百兆网,SCP工具很难把网络跑满,一般会使用百分之四五十的带宽,至于每秒11.2MB应该是交换机做了限制。可是问了几个同事都不能确定到底是多少的带宽,只好对SCP工具进行限速了。

    SCP工具可是使用-l参数进行速度限制,单位是字节。

    1 NAME
    2      scp - secure copy (remote file copy program)
    3  
    4 SYNOPSIS
    5      scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file] [-l limit] [-o ssh_option] [-P port] [-S program] [[user@]host1:]file1 [...]
    6          [[user@]host2:]file2
    7      -l limit
    8              Limits the used bandwidth, specified in Kbit/s.

    限制每秒传输20000字节,也就约等于每秒传输2M的数据。

    1 [oracle@XXDB01 xxdb]$ scp -rl 20000 lv0_20150607 10.0.97.33:/data/xxxdb
    2 Address 10.0.97.33 maps to localhost, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!
    3 oracle@10.0.97.33's password:
    4 20150607.log                                                                    100%   71KB  71.2KB/s   00:00
    5 level_0_ivldb_datafile_700_1                                                     50% 3823MB   2.1MB/s   29:50
    6 ETASlevel_0_ivldb_datafile_700_1                                                100% 7593MB   2.1MB/s   59:49
    7 level_0_ivldb_datafile_699_1                                                    100% 8709MB   2.1MB/s 1:08:3
    展开全文
  • 双核cpu,Linux系统 限制网络宽带: cat /proc/cpuinfo |grep "physical id" | wc -l 可以获得CPU的个数, 我们将其表示为N. seq 1 N 用来生成1到N之间的数字 for i in `seq 1 N`; 就是循环执行命令,从1到N ...

    双核cpu,Linux系统

    限制网络宽带:

    cat /proc/cpuinfo |grep "physical id" | wc -l 可以获得CPU的个数, 我们将其表示为N.
    seq 1 N 用来生成1到N之间的数字
    for i in `seq 1 N`; 就是循环执行命令,从1到N

     

    dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。

     /dev/zero主要的用处是用来创建一个指定长度用于初始化的空文件,就像临时交换文件

    把/dev/null看作"黑洞", 它等价于一个只写文件,所有写入它的内容都会永远丢失.,而尝试从它那儿读取内容则什么也读不到。 

    /dev/null对命令行和脚本都非常的有用.


    dd if=/dev/zero of=/dev/null 执行dd命令, 输出到/dev/null, 实际上只占用CPU, 没有IO操作.


    由于连续执行N个(N是CPU个数)的dd 命令, 且使用率为100%, 这时调度器会调度每个dd命令在不同的CPU上处理.
    最终就实现所有CPU占用率100%

     

    linux限制网络带宽 : http://blog.csdn.net/jacson_bai/article/details/46485725

     

    模拟丢包:

    1、tc qdisc add dev eth0 root netem loss 1% 模拟丢包命令

    2、通过其他机子ping修改后的机子

    3、tc qdisc del dev eth0 root  删除丢包命令

     

    展开全文
  • Linux 中的零拷贝技术

    2016-03-31 12:57:22
    传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为...

    引言

    传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为如果所请求的数据已经存放在操作系统的高速缓冲存储器中,那么就不需要再进行实际的物理磁盘 I/O 操作。但是数据传输过程中的数据拷贝操作却导致了极大的 CPU 开销,限制了操作系统有效进行数据传输操作的能力。

    零拷贝( zero-copy )这种技术可以有效地改善数据传输的性能,在内核驱动程序(比如网络堆栈或者磁盘存储驱动程序)处理 I/O 数据的时候,零拷贝技术可以在某种程度上减少甚至完全避免不必要 CPU 数据拷贝操作。现代的 CPU 和存储体系结构提供了很多特征可以有效地实现零拷贝技术,但是因为存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面的影响,甚至会导致零拷贝技术自身的优点完全丧失。

    为什么需要零拷贝技术

    如今,很多网络服务器都是基于客户端 - 服务器这一模型的。在这种模型中,客户端向服务器端请求数据或者服务;服务器端则需要响应客户端发出的请求,并为客户端提供它所需要的数据。随着网络服务的逐渐普及,video 这类应用程序发展迅速。当今的计算机系统已经具备足够的能力去处理 video 这类应用程序对客户端所造成的重负荷,但是对于服务器端来说,它应付由 video 这类应用程序引起的网络通信量就显得捉襟见肘了。而且,客户端的数量增长迅速,那么服务器端就更容易成为性能瓶颈。而对于负荷很重的服务器来说,操作系统通常都是引起性能瓶颈的罪魁祸首。举个例子来说,当数据“写”操作或者数据“发送”操作的系统调用发出时,操作系统通常都会将数据从应用程序地址空间的缓冲区拷贝到操作系统内核的缓冲区中去。操作系统这样做的好处是接口简单,但是却在很大程度上损失了系统性能,因为这种数据拷贝操作不单需要占用 CPU 时间片,同时也需要占用额外的内存带宽。

    一般来说,客户端通过网络接口卡向服务器端发送请求,操作系统将这些客户端的请求传递给服务器端应用程序,服务器端应用程序会处理这些请求,请求处理完成以后,操作系统还需要将处理得到的结果通过网络适配器传递回去。

    下边这一小节会跟读者简单介绍一下传统的服务器是如何进行数据传输的,以及这种数据传输的处理过程存在哪些问题有可能会造成服务器的性能损失。

    Linux  中传统服务器进行数据传输的流程

    Linux  中传统的 I/O 操作是一种缓冲 I/O,I/O 过程中产生的数据传输通常需要在缓冲区中进行多次的拷贝操作。一般来说,在传输数据的时候,用户应用程序需要分配一块大小合适的缓冲区用来存放需要传输的数据。应用程序从文件中读取一块数据,然后把这块数据通过网络发送到接收端去。用户应用程序只是需要调用两个系统调用 read() 和 write() 就可以完成这个数据传输操作,应用程序并不知晓在这个数据传输的过程中操作系统所做的数据拷贝操作。对于 Linux 操作系统来说,基于数据排序或者校验等各方面因素的考虑,操作系统内核会在处理数据传输的过程中进行多次拷贝操作。在某些情况下,这些数据拷贝操作会极大地降低数据传输的性能。

    当应用程序需要访问某块数据的时候,操作系统内核会先检查这块数据是不是因为前一次对相同文件的访问而已经被存放在操作系统内核地址空间的缓冲区内,如果在内核缓冲区中找不到这块数据,Linux 操作系统内核会先将这块数据从磁盘读出来放到操作系统内核的缓冲区里去。如果这个数据读取操作是由 DMA 完成的,那么在 DMA 进行数据读取的这一过程中,CPU 只是需要进行缓冲区管理,以及创建和处理 DMA ,除此之外,CPU 不需要再做更多的事情,DMA 执行完数据读取操作之后,会通知操作系统做进一步的处理。Linux 操作系统会根据 read() 系统调用指定的应用程序地址空间的地址,把这块数据存放到请求这块数据的应用程序的地址空间中去,在接下来的处理过程中,操作系统需要将数据再一次从用户应用程序地址空间的缓冲区拷贝到与网络堆栈相关的内核缓冲区中去,这个过程也是需要占用 CPU 的。数据拷贝操作结束以后,数据会被打包,然后发送到网络接口卡上去。在数据传输的过程中,应用程序可以先返回进而执行其他的操作。之后,在调用 write() 系统调用的时候,用户应用程序缓冲区中的数据内容可以被安全的丢弃或者更改,因为操作系统已经在内核缓冲区中保留了一份数据拷贝,当数据被成功传送到硬件上之后,这份数据拷贝就可以被丢弃。

    从上面的描述可以看出,在这种传统的数据传输过程中,数据至少发生了四次拷贝操作,即便是使用了 DMA 来进行与硬件的通讯,CPU 仍然需要访问数据两次。在 read() 读数据的过程中,数据并不是直接来自于硬盘,而是必须先经过操作系统的文件系统层。在 write() 写数据的过程中,为了和要传输的数据包的大小相吻合,数据必须要先被分割成块,而且还要预先考虑包头,并且要进行数据校验和操作。

    图 1. 传统使用 read 和 write 系统调用的数据传输
    图 1. 传统使用 read 和 write 系统调用的数据传输

    零拷贝(zero copy)技术概述

    什么是零拷贝?

    简单一点来说,零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。针对操作系统中的设备驱动程序、文件系统以及网络协议堆栈而出现的各种零拷贝技术极大地提升了特定应用程序的性能,并且使得这些应用程序可以更加有效地利用系统资源。这种性能的提升就是通过在数据拷贝进行的同时,允许 CPU 执行其他的任务来实现的。零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。而且,零拷贝技术减少了用户应用程序地址空间和操作系统内核地址空间之间因为上下文切换而带来的开销。进行大量的数据拷贝操作其实是一件简单的任务,从操作系统的角度来说,如果 CPU 一直被占用着去执行这项简单的任务,那么这将会是很浪费资源的;如果有其他比较简单的系统部件可以代劳这件事情,从而使得 CPU 解脱出来可以做别的事情,那么系统资源的利用则会更加有效。综上所述,零拷贝技术的目标可以概括如下:

    避免数据拷贝

    • 避免操作系统内核缓冲区之间进行数据拷贝操作。
    • 避免操作系统内核和用户应用程序地址空间这两者之间进行数据拷贝操作。
    • 用户应用程序可以避开操作系统直接访问硬件存储。
    • 数据传输尽量让 DMA 来做。

    将多种操作结合在一起

    • 避免不必要的系统调用和上下文切换。
    • 需要拷贝的数据可以先被缓存起来。
    • 对数据进行处理尽量让硬件来做。

    前文提到过,对于高速网络来说,零拷贝技术是非常重要的。这是因为高速网络的网络链接能力与 CPU 的处理能力接近,甚至会超过 CPU 的处理能力。如果是这样的话,那么 CPU 就有可能需要花费几乎所有的时间去拷贝要传输的数据,而没有能力再去做别的事情,这就产生了性能瓶颈,限制了通讯速率,从而降低了网络链接的能力。一般来说,一个 CPU 时钟周期可以处理一位的数据。举例来说,一个 1 GHz 的处理器可以对 1Gbit/s 的网络链接进行传统的数据拷贝操作,但是如果是 10 Gbit/s 的网络,那么对于相同的处理器来说,零拷贝技术就变得非常重要了。对于超过 1 Gbit/s 的网络链接来说,零拷贝技术在超级计算机集群以及大型的商业数据中心中都有所应用。然而,随着信息技术的发展,1 Gbit/s,10 Gbit/s 以及 100 Gbit/s 的网络会越来越普及,那么零拷贝技术也会变得越来越普及,这是因为网络链接的处理能力比 CPU 的处理能力的增长要快得多。传统的数据拷贝受限于传统的操作系统或者通信协议,这就限制了数据传输性能。零拷贝技术通过减少数据拷贝次数,简化协议处理的层次,在应用程序和网络之间提供更快的数据传输方法,从而可以有效地降低通信延迟,提高网络吞吐率。零拷贝技术是实现主机或者路由器等设备高速网络接口的主要技术之一。

    现代的 CPU 和存储体系结构提供了很多相关的功能来减少或避免 I/O 操作过程中产生的不必要的 CPU 数据拷贝操作,但是,CPU 和存储体系结构的这种优势经常被过高估计。存储体系结构的复杂性以及网络协议中必需的数据传输可能会产生问题,有时甚至会导致零拷贝这种技术的优点完全丧失。在下一章中,我们会介绍几种 Linux 操作系统中出现的零拷贝技术,简单描述一下它们的实现方法,并对它们的弱点进行分析。

    零拷贝技术分类

    零拷贝技术的发展很多样化,现有的零拷贝技术种类也非常多,而当前并没有一个适合于所有场景的零拷贝技术的出现。对于 Linux 来说,现存的零拷贝技术也比较多,这些零拷贝技术大部分存在于不同的 Linux 内核版本,有些旧的技术在不同的 Linux 内核版本间得到了很大的发展或者已经渐渐被新的技术所代替。本文针对这些零拷贝技术所适用的不同场景对它们进行了划分。概括起来,Linux 中的零拷贝技术主要有下面这几种:

    • 直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输:这类零拷贝技术针对的是操作系统内核并不需要对数据进行直接处理的情况,数据可以在应用程序地址空间的缓冲区和磁盘之间直接进行传输,完全不需要 Linux 操作系统内核提供的页缓存的支持。
    • 在数据传输的过程中,避免数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间进行拷贝。有的时候,应用程序在数据进行传输的过程中不需要对数据进行访问,那么,将数据从 Linux 的页缓存拷贝到用户进程的缓冲区中就可以完全避免,传输的数据在页缓存中就可以得到处理。在某些特殊的情况下,这种零拷贝技术可以获得较好的性能。Linux 中提供类似的系统调用主要有 mmap(),sendfile() 以及 splice()。
    • 对数据在 Linux 的页缓存和用户进程的缓冲区之间的传输过程进行优化。该零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统的页缓存之间的拷贝操作。这种方法延续了传统的通信方式,但是更加灵活。在  Linux  中,该方法主要利用了写时复制技术。

    前两类方法的目的主要是为了避免应用程序地址空间和操作系统内核地址空间这两者之间的缓冲区拷贝操作。这两类零拷贝技术通常适用在某些特殊的情况下,比如要传送的数据不需要经过操作系统内核的处理或者不需要经过应用程序的处理。第三类方法则继承了传统的应用程序地址空间和操作系统内核地址空间之间数据传输的概念,进而针对数据传输本身进行优化。我们知道,硬件和软件之间的数据传输可以通过使用 DMA 来进行,DMA  进行数据传输的过程中几乎不需要  CPU  参与,这样就可以把 CPU 解放出来去做更多其他的事情,但是当数据需要在用户地址空间的缓冲区和  Linux  操作系统内核的页缓存之间进行传输的时候,并没有类似  DMA  这种工具可以使用,CPU  需要全程参与到这种数据拷贝操作中,所以这第三类方法的目的是可以有效地改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。

    总结

    本系列文章介绍了 Linux 中的零拷贝技术,本文是其中的第一部分,介绍了零拷贝技术的基本概念,Linux 为什么需要零拷贝这种技术以及简要概述了 Linux 中都存在哪些零拷贝技术这样一些基本背景知识。我们将在本系列文章的第二部分内容中详细介绍本文提到的 Linux 中的几种零拷贝技术。


    第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概述了 Linux 为什么需要零拷贝技术以及 Linux 中都有哪几种零拷贝技术。本文是本系列文章的第二部分,针对第一部分内容中提到的几种零拷贝技术分别进行更详细的介绍,并对这些零拷贝技术的优缺点进行分析。

    Linux 中的直接 I/O

    如果应用程序可以直接访问网络接口存储,那么在应用程序访问数据之前存储总线就不需要被遍历,数据传输所引起的开销将会是最小的。应用程序或者运行在用户模式下的库函数可以直接访问硬件设备的存储,操作系统内核除了进行必要的虚拟存储配置工作之外,不参与数据传输过程中的其它任何事情。直接 I/O 使得数据可以直接在应用程序和外围设备之间进行传输,完全不需要操作系统内核页缓存的支持。关于直接 I/O 技术的具体实现细节可以参看 developerWorks 上的另一篇文章”Linux 中直接 I/O 机制的介绍” ,本文不做过多描述。

    图 1. 使用直接 I/O 的数据传输
    图 1. 使用直接 I/O 的数据传输

    针对数据传输不需要经过应用程序地址空间的零拷贝技术

    利用 mmap()

    在 Linux 中,减少拷贝次数的一种方法是调用 mmap() 来代替调用 read,比如:

    	 tmp_buf = mmap(file, len); 
    	 write(socket, tmp_buf, len);

    首先,应用程序调用了 mmap() 之后,数据会先通过 DMA 拷贝到操作系统内核的缓冲区中去。接着,应用程序跟操作系统共享这个缓冲区,这样,操作系统内核和应用程序存储空间就不需要再进行任何的数据拷贝操作。应用程序调用了 write() 之后,操作系统内核将数据从原来的内核缓冲区中拷贝到与 socket 相关的内核缓冲区中。接下来,数据从内核 socket 缓冲区拷贝到协议引擎中去,这是第三次数据拷贝操作。

    图 2. 利用 mmap() 代替 read()
    图 2. 利用 mmap() 代替 read()

    通过使用 mmap() 来代替 read(), 已经可以减半操作系统需要进行数据拷贝的次数。当大量数据需要传输的时候,这样做就会有一个比较好的效率。但是,这种改进也是需要代价的,使用 mma()p 其实是存在潜在的问题的。当对文件进行了内存映射,然后调用 write() 系统调用,如果此时其他的进程截断了这个文件,那么 write() 系统调用将会被总线错误信号 SIGBUS 中断,因为此时正在执行的是一个错误的存储访问。这个信号将会导致进程被杀死,解决这个问题可以通过以下这两种方法:

    1. 为 SIGBUS 安装一个新的信号处理器,这样,write() 系统调用在它被中断之前就返回已经写入的字节数目,errno 会被设置成 success。但是这种方法也有其缺点,它不能反映出产生这个问题的根源所在,因为 BIGBUS 信号只是显示某进程发生了一些很严重的错误。
    2. 第二种方法是通过文件租借锁来解决这个问题的,这种方法相对来说更好一些。我们可以通过内核对文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断的时候,内核会发送给用户一个实时信号:RT_SIGNAL_LEASE 信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,那么 write() 系统调用则会被中断,并且进程会被 SIGBUS 信号杀死,返回值则是中断前写的字节数,errno 也会被设置为 success。文件租借锁需要在对文件进行内存映射之前设置。

    使用 mmap 是 POSIX 兼容的,但是使用 mmap 并不一定能获得理想的数据传输性能。数据传输的过程中仍然需要一次 CPU 拷贝操作,而且映射操作也是一个开销很大的虚拟存储操作,这种操作需要通过更改页表以及冲刷 TLB (使得 TLB 的内容无效)来维持存储的一致性。但是,因为映射通常适用于较大范围,所以对于相同长度的数据来说,映射所带来的开销远远低于 CPU 拷贝所带来的开销。

    sendfile()

    为了简化用户接口,同时还要继续保留 mmap()/write() 技术的优点:减少 CPU 的拷贝次数,Linux 在版本 2.1 中引入了 sendfile() 这个系统调用。

    sendfile() 不仅减少了数据拷贝操作,它也减少了上下文切换。首先:sendfile() 系统调用利用 DMA 引擎将文件中的数据拷贝到操作系统内核缓冲区中,然后数据被拷贝到与 socket 相关的内核缓冲区中去。接下来,DMA 引擎将数据从内核 socket 缓冲区中拷贝到协议引擎中去。如果在用户调用 sendfile () 系统调用进行数据传输的过程中有其他进程截断了该文件,那么 sendfile () 系统调用会简单地返回给用户应用程序中断前所传输的字节数,errno 会被设置为 success。如果在调用 sendfile() 之前操作系统对文件加上了租借锁,那么 sendfile() 的操作和返回状态将会和 mmap()/write () 一样。

    图 3. 利用 sendfile () 进行数据传输
    图 3. 利用 sendfile () 进行数据传输

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

    • sendfile() 局限于基于文件服务的网络应用程序,比如 web 服务器。据说,在 Linux 内核中实现 sendfile() 只是为了在其他平台上使用 sendfile() 的 Apache 程序。
    • 由于网络传输具有异步性,很难在 sendfile () 系统调用的接收端进行配对的实现方式,所以数据传输的接收端一般没有用到这种技术。
    • 基于性能的考虑来说,sendfile () 仍然需要有一次从文件到 socket 缓冲区的 CPU 拷贝操作,这就导致页缓存有可能会被传输的数据所污染。

    带有 DMA 收集拷贝功能的 sendfile()

    上小节介绍的 sendfile() 技术在进行数据传输仍然还需要一次多余的数据拷贝操作,通过引入一点硬件上的帮助,这仅有的一次数据拷贝操作也可以避免。为了避免操作系统内核造成的数据副本,需要用到一个支持收集操作的网络接口,这也就是说,待传输的数据可以分散在存储的不同位置上,而不需要在连续存储中存放。这样一来,从文件中读出的数据就根本不需要被拷贝到 socket 缓冲区中去,而只是需要将缓冲区描述符传到网络协议栈中去,之后其在缓冲区中建立起数据包的相关结构,然后通过 DMA 收集拷贝功能将所有的数据结合成一个网络数据包。网卡的 DMA 引擎会在一次操作中从多个位置读取包头和数据。Linux 2.4 版本中的 socket 缓冲区就可以满足这种条件,这也就是用于 Linux 中的众所周知的零拷贝技术,这种方法不但减少了因为多次上下文切换所带来开销,同时也减少了处理器造成的数据副本的个数。对于用户应用程序来说,代码没有任何改变。首先,sendfile() 系统调用利用 DMA 引擎将文件内容拷贝到内核缓冲区去;然后,将带有文件位置和长度信息的缓冲区描述符添加到 socket 缓冲区中去,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,DMA 引擎会将数据直接从内核缓冲区拷贝到协议引擎中去,这样就避免了最后一次数据拷贝。

    图 4. 带有 DMA 收集拷贝功能的 sendfile
    图 4. 带有 DMA 收集拷贝功能的 sendfile

    通过这种方法,CPU 在数据传输的过程中不但避免了数据拷贝操作,理论上,CPU 也永远不会跟传输的数据有任何关联,这对于 CPU 的性能来说起到了积极的作用:首先,高速缓冲存储器没有受到污染;其次,高速缓冲存储器的一致性不需要维护,高速缓冲存储器在 DMA 进行数据传输前或者传输后不需要被刷新。然而实际上,后者实现起来非常困难。源缓冲区有可能是页缓存的一部分,这也就是说一般的读操作可以访问它,而且该访问也可以是通过传统方式进行的。只要存储区域可以被 CPU 访问到,那么高速缓冲存储器的一致性就需要通过 DMA 传输之前冲刷新高速缓冲存储器来维护。而且,这种数据收集拷贝功能的实现是需要硬件以及设备驱动程序支持的。

    splice()

    splice() 是  Linux  中与 mmap() 和  sendfile() 类似的一种方法。它也可以用于用户应用程序地址空间和操作系统地址空间之间的数据传输。splice() 适用于可以确定数据传输路径的用户应用程序,它不需要利用用户地址空间的缓冲区进行显式的数据传输操作。那么,当数据只是从一个地方传送到另一个地方,过程中所传输的数据不需要经过用户应用程序的处理的时候,spice() 就成为了一种比较好的选择。splice() 可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。而且,splice() 进行数据传输可以通过异步的方式来进行,用户应用程序可以先从系统调用返回,而操作系统内核进程会控制数据传输过程继续进行下去。splice() 可以被看成是类似于基于流的管道的实现,管道可以使得两个文件描述符相互连接,splice 的调用者则可以控制两个设备(或者协议栈)在操作系统内核中的相互连接。

    splice() 系统调用和 sendfile() 非常类似,用户应用程序必须拥有两个已经打开的文件描述符,一个用于表示输入设备,一个用于表示输出设备。与 sendfile() 不同的是,splice() 允许任意两个文件之间互相连接,而并不只是文件到 socket 进行数据传输。对于从一个文件描述符发送数据到 socket 这种特例来说,一直都是使用 sendfile() 这个系统调用,而 splice 一直以来就只是一种机制,它并不仅限于 sendfile() 的功能。也就是说,sendfile() 只是 splice() 的一个子集,在 Linux 2.6.23 中,sendfile() 这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过 API 以及相应的功能是利用了 splice() 这种机制来实现的。

    在数据传输的过程中,splice() 机制交替地发送相关的文件描述符的读写操作,并且可以将读缓冲区重新用于写操作。它也利用了一种简单的流控制,通过预先定义的水印( watermark )来阻塞写请求。有实验表明,利用这种方法将数据从一个磁盘传输到另一个磁盘会增加 30% 到 70% 的吞吐量,数据传输的过程中, CPU 的负载也会减少一半。

    Linux 2.6.17 内核引入了 splice() 系统调用,但是,这个概念在此之前 ] 其实已经存在了很长一段时间了。1988 年,Larry McVoy 提出了这个概念,它被看成是一种改进服务器端系统的 I/O 性能的一种技术,尽管在之后的若干年中经常被提及,但是 splice 系统调用从来没有在主流的 Linux 操作系统内核中实现过,一直到 Linux 2.6.17 版本的出现。splice 系统调用需要用到四个参数,其中两个是文件描述符,一个表示文件长度,还有一个用于控制如何进行数据拷贝。splice 系统调用可以同步实现,也可以使用异步方式来实现。在使用异步方式的时候,用户应用程序会通过信号 SIGIO 来获知数据传输已经终止。splice() 系统调用的接口如下所示:

    	 long splice(int fdin, int fdout, size_t len, unsigned int flags);

    调用 splice() 系统调用会导致操作系统内核从数据源 fdin 移动最多 len 个字节的数据到 fdout 中去,这个数据的移动过程只是经过操作系统内核空间,需要最少的拷贝次数。使用 splice() 系统调用需要这两个文件描述符中的一个必须是用来表示一个管道设备的。不难看出,这种设计具有局限性,Linux 的后续版本针对这一问题将会有所改进。参数 flags 用于表示拷贝操作的执行方法,当前的 flags 有如下这些取值:

    • SPLICE_F_NONBLOCK:splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞。
    • SPLICE_F_MORE:告知操作系统内核下一个 splice 系统调用将会有更多的数据传来。
    • SPLICE_F_MOVE:如果输出是文件,这个值则会使得操作系统内核尝试从输入管道缓冲区直接将数据读入到输出地址空间,这个数据传输过程没有任何数据拷贝操作发生。

    Splice() 系统调用利用了 Linux 提出的管道缓冲区( pipe buffer )机制,这就是为什么这个系统调用的两个文件描述符参数中至少有一个必须要指代管道设备的原因。为了支持 splice 这种机制,Linux 在用于设备和文件系统的 file_operations 结构中增加了下边这两个定义:

    	 ssize_t (*splice_write)(struct inode *pipe, strucuct file *out, 
    	                       size_t len, unsigned int flags); 
    	 ssize_t (*splice_read)(struct inode *in, strucuct file *pipe, 
    	                       size_t len, unsigned int flags);

    这两个新的操作可以根据 flags 的设定在 pipe 和 in 或者 out 之间移动 len 个字节。Linux 文件系统已经实现了具有上述功能并且可以使用的操作,而且还实现了一个 generic_splice_sendpage() 函数用于和 socket 之间的接合。

    对应用程序地址空间和内核之间的数据传输进行优化的零拷贝技术

    前面提到的几种零拷贝技术都是通过尽量避免用户应用程序和操作系统内核缓冲区之间的数据拷贝来实现的,使用上面那些零拷贝技术的应用程序通常都要局限于某些特殊的情况:要么不能在操作系统内核中处理数据,要么不能在用户地址空间中处理数据。而这一小节提出的零拷贝技术保留了传统在用户应用程序地址空间和操作系统内核地址空间之间传递数据的技术,但却在传输上进行优化。我们知道,数据在系统软件和硬件之间的传递可以通过 DMA 传输来提高效率,但是对于用户应用程序和操作系统之间进行数据传输这种情况来说,并没有类似的工具可以使用。本节介绍的技术就是针对这种情况提出来的。

    利用写时复制

    在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用  write() 系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的 write() 系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。

    什么是写时复制

    写时复制是计算机编程中的一种优化策略,它的基本思想是这样的:如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。

    写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。

    写时复制的最大好处就是可以节约内存。不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。

    数据传输的实现及其局限性

    数据发送端

    对于数据传输的发送端来说,实现相对来说是比较简单的,对与应用程序缓冲区相关的物理页面进行加锁,并将这些页面映射到操作系统内核的地址空间,并标识为“ write only ”。当系统调用返回的时候,用户应用程序和网络堆栈就都可以读取该缓冲区中的数据。在操作系统已经传送完所有的数据之后,应用程序就可以对这些数据进行写操作。如果应用程序尝试在数据传输完成之前对数据进行写操作,那么就会产生异常,这个时候操作系统就会将数据拷贝到应用程序自己的缓冲区中去,并且重置应用程序端的映射。数据传输完成之后,对加锁的页面进行解锁操作,并重置 COW 标识。

    数据接收端

    对于数据接收端来说,该技术的实现则需要处理复杂得多的情况。如果 read() 系统调用是在数据包到达之前发出的,并且应用程序是被阻塞的,那么 read() 系统调用就会告知操作系统接收到的数据包中的数据应该存放到什么地方去。在这种情况下,根本没有必要进行页面重映射,网络接口卡可以提供足够的支持让数据直接存入用户应用程序的缓冲区中去。如果数据接收是异步的,在 read() 系统调用发出之前,操作系统不知道该把数据写到哪里,因为它不知道用户应用程序缓冲区的位置,所以操作系统内核必须要先把数据存放到自己的缓冲区中去。

    局限性

    写时复制技术有可能会导致操作系统的处理开销很大.所有相关的缓冲区都必须要进行页对齐处理,并且使用的 MMU 页面一定要是整数个的。对于发送端来说,这不会造成什么问题。但是对于接收端来说,它需要有能力处理更加复杂的情况。首先,数据包的尺寸大小要合适,大小需要恰到好处能够覆盖一整页的数据,这就限制了那些 MTU 大小大于系统内存页的网络,比如 FDDI 和 ATM。其次,为了在没有任何中断的情况下将页面重映射到数据包的流,数据包中的数据部分必须占用整数个页面。对于异步接收数据的情况来说,为了将数据高效地移动到用户地址空间中去,可以使用这样一种方法:利用网络接口卡的支持,传来的数据包可以被分割成包头和数据两部分,数据被存放在一个单独的缓冲区内,虚拟存储系统然后就会将数据映射到用户地址空间缓冲区去。使用这种方法需要满足两个先决条件,也就是上面提到过的:一是应用程序缓冲区必须是页对齐的,并且在虚拟存储上是连续的;二是传来的数据有一页大小的时候才可以对数据包进行分割。事实上,这两个先决条件是很难满足的。如果应用程序缓冲区不是页对齐的,或者数据包的大小超过一个页,那么数据就需要被拷贝。对于数据发送端来说,就算数据在传输的过程中对于应用程序来说是写保护的,应用程序仍然需要避免使用这些忙缓冲区,这是因为写时拷贝操作所带来的开销是很大的。如果没有端到端这一级别的通知,那么应用程序很难会知道某缓冲区是否已经被释放还是仍然在被占用。

    这种零拷贝技术比较适用于那种写时复制事件发生比较少的情况,因为写时复制事件所产生的开销要远远高于一次 CPU 拷贝所产生的开销。实际情况中,大多数应用程序通常都会多次重复使用相同的缓冲区,所以,一次使用完数据之后,不要从操作系统地址空间解除页面的映射,这样会提高效率。考虑到同样的页面可能会被再次访问,所以保留页面的映射可以节省管理开销,但是,这种映射保留不会减少由于页表往返移动和 TLB 冲刷所带来的开销,这是因为每次页面由于写时复制而进行加锁或者解锁的时候,页面的只读标志都要被更改。

    缓冲区共享

    还有另外一种利用预先映射机制的共享缓冲区的方法也可以在应用程序地址空间和操作系统内核之间快速传输数据。采用缓冲区共享这种思想的架构最先在 Solaris 上实现,该架构使用了“ fbufs ”这个概念。这种方法需要修改 API。应用程序地址空间和操作系统内核地址空间之间的数据传递需要严格按照 fbufs 体系结构来实现,操作系统内核之间的通信也是严格按照 fbufs 体系结构来完成的。每一个应用程序都有一个缓冲区池,这个缓冲区池被同时映射到用户地址空间和内核地址空间,也可以在必要的时候才创建它们。通过完成一次虚拟存储操作来创建缓冲区,fbufs 可以有效地减少由存储一致性维护所引起的大多数性能问题。该技术在 Linux 中还停留在实验阶段。

    为什么要扩展 Linux I/O API

    传统的 Linux 输入输出接口,比如读和写系统调用,都是基于拷贝的,也就是说,数据需要在操作系统内核和应用程序定义的缓冲区之间进行拷贝。对于读系统调用来说,用户应用程序呈现给操作系统内核一个预先分配好的缓冲区,内核必须把读进来的数据放到这个缓冲区内。对于写系统调用来说,只要系统调用返回,用户应用程序就可以自由重新利用数据缓冲区。

    为了支持上面这种机制,Linux 需要能够为每一个操作都进行建立和删除虚拟存储映射。这种页面重映射的机制依赖于机器配置、cache 体系结构、TLB 未命中处理所带来的开销以及处理器是单处理器还是多处理器等多种因素。如果能够避免处理 I/O 请求的时候虚拟存储 / TLB 操作所产生的开销,则会极大地提高 I/O 的性能。fbufs 就是这样一种机制。使用 fbufs 体系结构就可以避免虚拟存储操作。由数据显示,fbufs 这种结构在 DECStation™ 5000/200 这个单处理器工作站上会取得比上面提到的页面重映射方法好得多的性能。如果要使用 fbufs 这种体系结构,必须要扩展 Linux API,从而实现一种有效而且全面的零拷贝技术。

    快速缓冲区( Fast Buffers )原理介绍

    I/O 数据存放在一些被称作 fbufs 的缓冲区内,每一个这样的缓冲区都包含一个或者多个连续的虚拟存储页。应用程序访问 fbuf 是通过保护域来实现的,有如下这两种方式:

    • 如果应用程序分配了 fbuf,那么应用程序就有访问该 fbuf 的权限
    • 如果应用程序通过 IPC 接收到了 fbuf,那么应用程序对这个 fbuf 也有访问的权限

    对于第一种情况来说,这个保护域被称作是 fbuf 的“ originator ”;对于后一种情况来说,这个保护域被称作是 fbuf 的“ receiver ”。

    传统的 Linux I/O 接口支持数据在应用程序地址空间和操作系统内核之间交换,这种交换操作导致所有的数据都需要进行拷贝。如果采用 fbufs 这种方法,需要交换的是包含数据的缓冲区,这样就消除了多余的拷贝操作。应用程序将 fbuf 传递给操作系统内核,这样就能减少传统的 write 系统调用所产生的数据拷贝开销。同样的,应用程序通过 fbuf 来接收数据,这样也可以减少传统 read 系统调用所产生的数据拷贝开销。如下图所示:

    图 5. Linux I/O API
    图 5. Linux I/O API

    I/O 子系统或者应用程序都可以通过 fbufs 管理器来分配 fbufs。一旦分配了 fbufs,这些 fbufs 就可以从程序传递到 I/O 子系统,或者从 I/O 子系统传递到程序。使用完后,这些 fbufs 会被释放回 fbufs 缓冲区池。

    fbufs 在实现上有如下这些特性,如图 9 所示:

    • fbuf 需要从 fbufs 缓冲区池里分配。每一个 fbuf 都存在一个所属对象,要么是应用程序,要么是操作系统内核。fbuf 可以在应用程序和操作系统之间进行传递,fbuf 使用完之后需要被释放回特定的 fbufs 缓冲区池,在 fbuf 传递的过程中它们需要携带关于 fbufs 缓冲区池的相关信息。
    • 每一个 fbufs 缓冲区池都会和一个应用程序相关联,一个应用程序最多只能与一个 fbufs 缓冲区池相关联。应用程序只有资格访问它自己的缓冲区池。
    • fbufs 不需要虚拟地址重映射,这是因为对于每个应用程序来说,它们可以重新使用相同的缓冲区集合。这样,虚拟存储转换的信息就可以被缓存起来,虚拟存储子系统方面的开销就可以消除。
    • I/O 子系统(设备驱动程序,文件系统等)可以分配 fbufs,并将到达的数据直接放到这些 fbuf 里边。这样,缓冲区之间的拷贝操作就可以避免。
    图 6. fbufs 体系结构
    图 6. fbufs 体系结构

    前面提到,这种方法需要修改 API,如果要使用 fbufs 体系结构,应用程序和 Linux 操作系统内核驱动程序都需要使用新的 API,如果应用程序要发送数据,那么它就要从缓冲区池里获取一个 fbuf,将数据填充进去,然后通过文件描述符将数据发送出去。接收到的 fbufs 可以被应用程序保留一段时间,之后,应用程序可以使用它继续发送其他的数据,或者还给缓冲区池。但是,在某些情况下,需要对数据包内的数据进行重新组装,那么通过 fbuf 接收到数据的应用程序就需要将数据拷贝到另外一个缓冲区内。再者,应用程序不能对当前正在被内核处理的数据进行修改,基于这一点,fbufs 体系结构引入了强制锁的概念以保证其实现。对于应用程序来说,如果 fbufs 已经被发送给操作系统内核,那么应用程序就不会再处理这些 fbufs。

    fbufs 存在的一些问题

    管理共享缓冲区池需要应用程序、网络软件、以及设备驱动程序之间的紧密合作。对于数据接收端来说,网络硬件必须要能够将到达的数据包利用 DMA 传输到由接收端分配的正确的存储缓冲区池中去。而且,应用程序稍微不注意就会更改之前发到共享存储中的数据的内容,从而导致数据被破坏,但是这种问题在应用程序端是很难调试的。同时,共享存储这种模型很难与其他类型的存储对象关联使用,但是应用程序、网络软件以及设备驱动程序之间的紧密合作是需要其他存储管理器的支持的。对于共享缓冲区这种技术来说,虽然这种技术看起来前景光明,但是这种技术不但需要对 API 进行更改,而且需要对驱动程序也进行更改,并且这种技术本身也存在一些未解决的问题,这就使得这种技术目前还只是出于试验阶段。在测试系统中,这种技术在性能上有很大的改进,不过这种新的架构的整体安装目前看起来还是不可行的。这种预先分配共享缓冲区的机制有时也因为粒度问题需要将数据拷贝到另外一个缓冲区中去。

    总结

    本系列文章介绍了 Linux 中的零拷贝技术,本文是其中的第二部分。本文对第一部分文章中提出的 Linux 操作系统上出现的几种零拷贝技术进行了更详细的介绍,主要描述了它们各自的优点,缺点以及适用场景。对于网络数据传输来说,零拷贝技术的应用受到了很多体系结构方面因素的阻碍,包括虚拟存储体系结构以及网络协议体系结构等。所以,零拷贝技术仍然只是在某些很特殊的情况中才可以应用,比如文件服务或者使用某种特殊的协议进行高带宽的通信等。但是,零拷贝技术在磁盘操作中的应用的可行性就高得多了,这很可能是因为磁盘操作具有同步的特点,以及数据传输单元是按照页的粒度来进行的。

    针对 Linux 操作系统平台提出并实现了很多种零拷贝技术,但是并不是所有这些零拷贝技术都被广泛应用于现实中的操作系统中的。比如,fbufs 体系结构,它在很多方面看起来都很吸引人,但是使用它需要更改 API 以及驱动程序,它还存在其他一些实现上的困难,这就使得 fbufs 还只是停留在实验的阶段。动态地址重映射技术只是需要对操作系统做少量修改,虽然不需要修改用户软件,但是当前的虚拟存储体系结构并不能很好地支持频繁的虚拟地址重映射操作。而且为了保证存储的一致性,重映射之后还必须对 TLB 和一级缓存进行刷新。事实上,利用地址重映射实现的零拷贝技术适用的范围是很小的,这是因为虚拟存储操作所带来的开销往往要比 CPU 拷贝所产生的开销还要大。此外,为了完全消除 CPU 访问存储,通常都需要额外的硬件来支持,而这种硬件的支持并不是很普及,同时也是非常昂贵的。

    本系列文章的目的是想帮助读者理清这些出现在 Linux 操作系统中的零拷贝技术都是从何种角度来帮助改善数据传输过程中遇到的性能问题的。关于各种零拷贝技术的具体实现细节,本系列文章没有做详细描述。同时,零拷贝技术一直是在不断地发展和完善当中的,本系列文章并没有涵盖 Linux 上出现的所有零拷贝技术。


    展开全文
  • Linux拷贝技术

    2016-01-18 14:31:08
    linux拷贝技术

    本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景。第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概述了 Linux 为什么需要零拷贝技术以及 Linux 中都有哪几种零拷贝技术。本文是本系列文章的第二部分,针对第一部分内容中提到的几种零拷贝技术分别进行更详细的介绍,并对这些零拷贝技术的优缺点进行分析。

    为什么需要零拷贝技术

    如今,很多网络服务器都是基于客户端 - 服务器这一模型的。在这种模型中,客户端向服务器端请求数据或者服务;服务器端则需要响应客户端发出的请求,并为客户端提供它所需要的数据。随着网络服务的逐渐普及,video 这类应用程序发展迅速。当今的计算机系统已经具备足够的能力去处理 video 这类应用程序对客户端所造成的重负荷,但是对于服务器端来说,它应付由 video 这类应用程序引起的网络通信量就显得捉襟见肘了。而且,客户端的数量增长迅速,那么服务器端就更容易成为性能瓶颈。而对于负荷很重的服务器来说,操作系统通常都是引起性能瓶颈的罪魁祸首。举个例子来说,当数据“写”操作或者数据“发送”操作的系统调用发出时,操作系统通常都会将数据从应用程序地址空间的缓冲区拷贝到操作系统内核的缓冲区中去。操作系统这样做的好处是接口简单,但是却在很大程度上损失了系统性能,因为这种数据拷贝操作不单需要占用 CPU 时间片,同时也需要占用额外的内存带宽。

    一般来说,客户端通过网络接口卡向服务器端发送请求,操作系统将这些客户端的请求传递给服务器端应用程序,服务器端应用程序会处理这些请求,请求处理完成以后,操作系统还需要将处理得到的结果通过网络适配器传递回去。

    下边这一小节会跟读者简单介绍一下传统的服务器是如何进行数据传输的,以及这种数据传输的处理过程存在哪些问题有可能会造成服务器的性能损失。

    Linux  中传统服务器进行数据传输的流程

    Linux  中传统的 I/O 操作是一种缓冲 I/O,I/O 过程中产生的数据传输通常需要在缓冲区中进行多次的拷贝操作。一般来说,在传输数据的时候,用户应用程序需要分配一块大小合适的缓冲区用来存放需要传输的数据。应用程序从文件中读取一块数据,然后把这块数据通过网络发送到接收端去。用户应用程序只是需要调用两个系统调用 read() 和 write() 就可以完成这个数据传输操作,应用程序并不知晓在这个数据传输的过程中操作系统所做的数据拷贝操作。对于 Linux 操作系统来说,基于数据排序或者校验等各方面因素的考虑,操作系统内核会在处理数据传输的过程中进行多次拷贝操作。在某些情况下,这些数据拷贝操作会极大地降低数据传输的性能。

    当应用程序需要访问某块数据的时候,操作系统内核会先检查这块数据是不是因为前一次对相同文件的访问而已经被存放在操作系统内核地址空间的缓冲区内,如果在内核缓冲区中找不到这块数据,Linux 操作系统内核会先将这块数据从磁盘读出来放到操作系统内核的缓冲区里去。如果这个数据读取操作是由 DMA 完成的,那么在 DMA 进行数据读取的这一过程中,CPU 只是需要进行缓冲区管理,以及创建和处理 DMA ,除此之外,CPU 不需要再做更多的事情,DMA 执行完数据读取操作之后,会通知操作系统做进一步的处理。Linux 操作系统会根据 read() 系统调用指定的应用程序地址空间的地址,把这块数据存放到请求这块数据的应用程序的地址空间中去,在接下来的处理过程中,操作系统需要将数据再一次从用户应用程序地址空间的缓冲区拷贝到与网络堆栈相关的内核缓冲区中去,这个过程也是需要占用 CPU 的。数据拷贝操作结束以后,数据会被打包,然后发送到网络接口卡上去。在数据传输的过程中,应用程序可以先返回进而执行其他的操作。之后,在调用 write() 系统调用的时候,用户应用程序缓冲区中的数据内容可以被安全的丢弃或者更改,因为操作系统已经在内核缓冲区中保留了一份数据拷贝,当数据被成功传送到硬件上之后,这份数据拷贝就可以被丢弃。

    从上面的描述可以看出,在这种传统的数据传输过程中,数据至少发生了四次拷贝操作,即便是使用了 DMA 来进行与硬件的通讯,CPU 仍然需要访问数据两次。在 read() 读数据的过程中,数据并不是直接来自于硬盘,而是必须先经过操作系统的文件系统层。在 write() 写数据的过程中,为了和要传输的数据包的大小相吻合,数据必须要先被分割成块,而且还要预先考虑包头,并且要进行数据校验和操作。

    图 1. 传统使用 read 和 write 系统调用的数据传输

    零拷贝(zero copy)技术概述

    什么是零拷贝?

    简单一点来说,零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。针对操作系统中的设备驱动程序、文件系统以及网络协议堆栈而出现的各种零拷贝技术极大地提升了特定应用程序的性能,并且使得这些应用程序可以更加有效地利用系统资源。这种性能的提升就是通过在数据拷贝进行的同时,允许 CPU 执行其他的任务来实现的。零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。而且,零拷贝技术减少了用户应用程序地址空间和操作系统内核地址空间之间因为上下文切换而带来的开销。进行大量的数据拷贝操作其实是一件简单的任务,从操作系统的角度来说,如果 CPU 一直被占用着去执行这项简单的任务,那么这将会是很浪费资源的;如果有其他比较简单的系统部件可以代劳这件事情,从而使得 CPU 解脱出来可以做别的事情,那么系统资源的利用则会更加有效。综上所述,零拷贝技术的目标可以概括如下:

    避免数据拷贝

    • 避免操作系统内核缓冲区之间进行数据拷贝操作。
    • 避免操作系统内核和用户应用程序地址空间这两者之间进行数据拷贝操作。
    • 用户应用程序可以避开操作系统直接访问硬件存储。
    • 数据传输尽量让 DMA 来做。

    将多种操作结合在一起

    • 避免不必要的系统调用和上下文切换。
    • 需要拷贝的数据可以先被缓存起来。
    • 对数据进行处理尽量让硬件来做。

    前文提到过,对于高速网络来说,零拷贝技术是非常重要的。这是因为高速网络的网络链接能力与 CPU 的处理能力接近,甚至会超过 CPU 的处理能力。如果是这样的话,那么 CPU 就有可能需要花费几乎所有的时间去拷贝要传输的数据,而没有能力再去做别的事情,这就产生了性能瓶颈,限制了通讯速率,从而降低了网络链接的能力。一般来说,一个 CPU 时钟周期可以处理一位的数据。举例来说,一个 1 GHz 的处理器可以对 1Gbit/s 的网络链接进行传统的数据拷贝操作,但是如果是 10 Gbit/s 的网络,那么对于相同的处理器来说,零拷贝技术就变得非常重要了。对于超过 1 Gbit/s 的网络链接来说,零拷贝技术在超级计算机集群以及大型的商业数据中心中都有所应用。然而,随着信息技术的发展,1 Gbit/s,10 Gbit/s 以及 100 Gbit/s 的网络会越来越普及,那么零拷贝技术也会变得越来越普及,这是因为网络链接的处理能力比 CPU 的处理能力的增长要快得多。传统的数据拷贝受限于传统的操作系统或者通信协议,这就限制了数据传输性能。零拷贝技术通过减少数据拷贝次数,简化协议处理的层次,在应用程序和网络之间提供更快的数据传输方法,从而可以有效地降低通信延迟,提高网络吞吐率。零拷贝技术是实现主机或者路由器等设备高速网络接口的主要技术之一。

    现代的 CPU 和存储体系结构提供了很多相关的功能来减少或避免 I/O 操作过程中产生的不必要的 CPU 数据拷贝操作,但是,CPU 和存储体系结构的这种优势经常被过高估计。存储体系结构的复杂性以及网络协议中必需的数据传输可能会产生问题,有时甚至会导致零拷贝这种技术的优点完全丧失。在下一章中,我们会介绍几种 Linux 操作系统中出现的零拷贝技术,简单描述一下它们的实现方法,并对它们的弱点进行分析。

    零拷贝技术分类

    零拷贝技术的发展很多样化,现有的零拷贝技术种类也非常多,而当前并没有一个适合于所有场景的零拷贝技术的出现。对于 Linux 来说,现存的零拷贝技术也比较多,这些零拷贝技术大部分存在于不同的 Linux 内核版本,有些旧的技术在不同的 Linux 内核版本间得到了很大的发展或者已经渐渐被新的技术所代替。本文针对这些零拷贝技术所适用的不同场景对它们进行了划分。概括起来,Linux 中的零拷贝技术主要有下面这几种:

    • 直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输:这类零拷贝技术针对的是操作系统内核并不需要对数据进行直接处理的情况,数据可以在应用程序地址空间的缓冲区和磁盘之
    • 间直接进行传输,完全不需要 Linux 操作系统内核提供的页缓存的支持。在数据传输的过程中,避免数据在操作系统内核地址空间的缓冲区和用户应用
    • 程序地址空间的缓冲区之间进行拷贝。有的时候,应用程序在数据进行传输的过程中不需要对数据进行访问,那么,将数据从 Linux 的页缓存拷贝到用户进程的缓冲区中就可以完全避免,传输的数据在页缓存中就可以得到处理。在某些特殊的情况下,这种零拷贝技术可以获得较好的性能。Linux 中提供类似的系
    • 统调用主要有 mmap(),sendfile() 以及 splice()。
    • 对数据在 Linux 的页缓存和用户进程的缓冲区之间的传输过程进行优化。该零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统的页缓存之间
    • 的拷贝操作。这种方法延续了传统的通信方式,但是更加灵活。在  Linux  中,该方法主要利用了写时复制技术。

    前两类方法的目的主要是为了避免应用程序地址空间和操作系统内核地址空间这两者之间的缓冲区拷贝操作。这两类零拷贝技术通常适用在某些特殊的情况下,比如要传送的数据不需要经过操作系统内核的处理或者不需要经过应用程序的处理。第三类方法则继承了传统的应用程序地址空间和操作系统内核地址空间之间数据传输的概念,进而针对数据传输本身进行优化。我们知道,硬件和软件之间的数据传输可以通过使用 DMA 来进行,DMA  进行数据传输的过程中几乎不需要  CPU  参与,这样就可以把 CPU 解放出来去做更多其他的事情,但是当数据需要在用户地址空间的缓冲区和  Linux  操作系统内核的页缓存之间进行传输的时候,并没有类似  DMA  这种工具可以使用,CPU  需要全程参与到这种数据拷贝操作中,所以这第三类方法的目的是可以有效地改善数据在用户地址空间和操作系统内核地址空间之间传递的效率。

    总结

    本系列文章介绍了 Linux 中的零拷贝技术,本文是其中的第一部分,介绍了零拷贝技术的基本概念,Linux 为什么需要零拷贝这种技术以及简要概述了 Linux 中都存在哪些零拷贝技术这样一些基本背景知识。我们将在本系列文章的第二部分内容中详细介绍本文提到的 Linux 中的几种零拷贝技术。

    图 4. 带有 DMA 收集拷贝功能的 sendfile

    通过这种方法,CPU 在数据传输的过程中不但避免了数据拷贝操作,理论上,CPU 也永远不会跟传输的数据有任何关联,这对于 CPU 的性能来说起到了积极的作用:首先,高速缓冲存储器没有受到污染;其次,高速缓冲存储器的一致性不需要维护,高速缓冲存储器在 DMA 进行数据传输前或者传输后不需要被刷新。然而实际上,后者实现起来非常困难。源缓冲区有可能是页缓存的一部分,这也就是说一般的读操作可以访问它,而且该访问也可以是通过传统方式进行的。只要存储区域可以被 CPU 访问到,那么高速缓冲存储器的一致性就需要通过 DMA 传输之前冲刷新高速缓冲存储器来维护。而且,这种数据收集拷贝功能的实现是需要硬件以及设备驱动程序支持的。

    splice()

    splice() 是  Linux  中与 mmap() 和  sendfile() 类似的一种方法。它也可以用于用户应用程序地址空间和操作系统地址空间之间的数据传输。splice() 适用于可以确定数据传输路径的用户应用程序,它不需要利用用户地址空间的缓冲区进行显式的数据传输操作。那么,当数据只是从一个地方传送到另一个地方,过程中所传输的数据不需要经过用户应用程序的处理的时候,spice() 就成为了一种比较好的选择。splice() 可以在操作系统地址空间中整块地移动数据,从而减少大多数数据拷贝操作。而且,splice() 进行数据传输可以通过异步的方式来进行,用户应用程序可以先从系统调用返回,而操作系统内核进程会控制数据传输过程继续进行下去。splice() 可以被看成是类似于基于流的管道的实现,管道可以使得两个文件描述符相互连接,splice 的调用者则可以控制两个设备(或者协议栈)在操作系统内核中的相互连接。

    splice() 系统调用和 sendfile() 非常类似,用户应用程序必须拥有两个已经打开的文件描述符,一个用于表示输入设备,一个用于表示输出设备。与 sendfile() 不同的是,splice() 允许任意两个文件之间互相连接,而并不只是文件到 socket 进行数据传输。对于从一个文件描述符发送数据到 socket 这种特例来说,一直都是使用 sendfile() 这个系统调用,而 splice 一直以来就只是一种机制,它并不仅限于 sendfile() 的功能。也就是说,sendfile() 只是 splice() 的一个子集,在 Linux 2.6.23 中,sendfile() 这种机制的实现已经没有了,但是这个 API 以及相应的功能还存在,只不过 API 以及相应的功能是利用了 splice() 这种机制来实现的。

    在数据传输的过程中,splice() 机制交替地发送相关的文件描述符的读写操作,并且可以将读缓冲区重新用于写操作。它也利用了一种简单的流控制,通过预先定义的水印( watermark )来阻塞写请求。有实验表明,利用这种方法将数据从一个磁盘传输到另一个磁盘会增加 30% 到 70% 的吞吐量,数据传输的过程中, CPU 的负载也会减少一半。

    Linux 2.6.17 内核引入了 splice() 系统调用,但是,这个概念在此之前 ] 其实已经存在了很长一段时间了。1988 年,Larry McVoy 提出了这个概念,它被看成是一种改进服务器端系统的 I/O 性能的一种技术,尽管在之后的若干年中经常被提及,但是 splice 系统调用从来没有在主流的 Linux 操作系统内核中实现过,一直到 Linux 2.6.17 版本的出现。splice 系统调用需要用到四个参数,其中两个是文件描述符,一个表示文件长度,还有一个用于控制如何进行数据拷贝。splice 系统调用可以同步实现,也可以使用异步方式来实现。在使用异步方式的时候,用户应用程序会通过信号 SIGIO 来获知数据传输已经终止。splice() 系统调用的接口如下所示:

    	 long splice(int fdin, int fdout, size_t len, unsigned int flags);

    调用 splice() 系统调用会导致操作系统内核从数据源 fdin 移动最多 len 个字节的数据到 fdout 中去,这个数据的移动过程只是经过操作系统内核空间,需要最少的拷贝次数。使用 splice() 系统调用需要这两个文件描述符中的一个必须是用来表示一个管道设备的。不难看出,这种设计具有局限性,Linux 的后续版本针对这一问题将会有所改进。参数 flags 用于表示拷贝操作的执行方法,当前的 flags 有如下这些取值:

    • SPLICE_F_NONBLOCK:splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞。
    • SPLICE_F_MORE:告知操作系统内核下一个 splice 系统调用将会有更多的数据传来。
    • SPLICE_F_MOVE:如果输出是文件,这个值则会使得操作系统内核尝试从输入管道缓冲区直接将数据读入到输出地址空间,这个数据传输过程没有任何数据拷贝操作发生。

    Splice() 系统调用利用了 Linux 提出的管道缓冲区( pipe buffer )机制,这就是为什么这个系统调用的两个文件描述符参数中至少有一个必须要指代管道设备的原因。为了支持 splice 这种机制,Linux 在用于设备和文件系统的 file_operations 结构中增加了下边这两个定义:

    	 ssize_t (*splice_write)(struct inode *pipe, strucuct file *out, 
    	                       size_t len, unsigned int flags); 
    	 ssize_t (*splice_read)(struct inode *in, strucuct file *pipe, 
    	                       size_t len, unsigned int flags);

    这两个新的操作可以根据 flags 的设定在 pipe 和 in 或者 out 之间移动 len 个字节。Linux 文件系统已经实现了具有上述功能并且可以使用的操作,而且还实现了一个 generic_splice_sendpage() 函数用于和 socket 之间的接合。

    对应用程序地址空间和内核之间的数据传输进行优化的零拷贝技术

    前面提到的几种零拷贝技术都是通过尽量避免用户应用程序和操作系统内核缓冲区之间的数据拷贝来实现的,使用上面那些零拷贝技术的应用程序通常都要局限于某些特殊的情况:要么不能在操作系统内核中处理数据,要么不能在用户地址空间中处理数据。而这一小节提出的零拷贝技术保留了传统在用户应用程序地址空间和操作系统内核地址空间之间传递数据的技术,但却在传输上进行优化。我们知道,数据在系统软件和硬件之间的传递可以通过 DMA 传输来提高效率,但是对于用户应用程序和操作系统之间进行数据传输这种情况来说,并没有类似的工具可以使用。本节介绍的技术就是针对这种情况提出来的。

    利用写时复制

    在某些情况下,Linux 操作系统内核中的页缓存可能会被多个应用程序所共享,操作系统有可能会将用户应用程序地址空间缓冲区中的页面映射到操作系统内核地址空间中去。如果某个应用程序想要对这共享的数据调用  write() 系统调用,那么它就可能破坏内核缓冲区中的共享数据,传统的 write() 系统调用并没有提供任何显示的加锁操作,Linux 中引入了写时复制这样一种技术用来保护数据。

    什么是写时复制

    写时复制是计算机编程中的一种优化策略,它的基本思想是这样的:如果有多个应用程序需要同时访问同一块数据,那么可以为这些应用程序分配指向这块数据的指针,在每一个应用程序看来,它们都拥有这块数据的一份数据拷贝,当其中一个应用程序需要对自己的这份数据拷贝进行修改的时候,就需要将数据真正地拷贝到该应用程序的地址空间中去,也就是说,该应用程序拥有了一份真正的私有数据拷贝,这样做是为了避免该应用程序对这块数据做的更改被其他应用程序看到。这个过程对于应用程序来说是透明的,如果应用程序永远不会对所访问的这块数据进行任何更改,那么就永远不需要将数据拷贝到应用程序自己的地址空间中去。这也是写时复制的最主要的优点。

    写时复制的实现需要 MMU 的支持,MMU 需要知晓进程地址空间中哪些特殊的页面是只读的,当需要往这些页面中写数据的时候,MMU 就会发出一个异常给操作系统内核,操作系统内核就会分配新的物理存储空间,即将被写入数据的页面需要与新的物理存储位置相对应。

    写时复制的最大好处就是可以节约内存。不过对于操作系统内核来说,写时复制增加了其处理过程的复杂性。

    数据传输的实现及其局限性

    数据发送端

    对于数据传输的发送端来说,实现相对来说是比较简单的,对与应用程序缓冲区相关的物理页面进行加锁,并将这些页面映射到操作系统内核的地址空间,并标识为“ write only ”。当系统调用返回的时候,用户应用程序和网络堆栈就都可以读取该缓冲区中的数据。在操作系统已经传送完所有的数据之后,应用程序就可以对这些数据进行写操作。如果应用程序尝试在数据传输完成之前对数据进行写操作,那么就会产生异常,这个时候操作系统就会将数据拷贝到应用程序自己的缓冲区中去,并且重置应用程序端的映射。数据传输完成之后,对加锁的页面进行解锁操作,并重置 COW 标识。

    数据接收端

    对于数据接收端来说,该技术的实现则需要处理复杂得多的情况。如果 read() 系统调用是在数据包到达之前发出的,并且应用程序是被阻塞的,那么 read() 系统调用就会告知操作系统接收到的数据包中的数据应该存放到什么地方去。在这种情况下,根本没有必要进行页面重映射,网络接口卡可以提供足够的支持让数据直接存入用户应用程序的缓冲区中去。如果数据接收是异步的,在 read() 系统调用发出之前,操作系统不知道该把数据写到哪里,因为它不知道用户应用程序缓冲区的位置,所以操作系统内核必须要先把数据存放到自己的缓冲区中去。

    局限性

    写时复制技术有可能会导致操作系统的处理开销很大.所有相关的缓冲区都必须要进行页对齐处理,并且使用的 MMU 页面一定要是整数个的。对于发送端来说,这不会造成什么问题。但是对于接收端来说,它需要有能力处理更加复杂的情况。首先,数据包的尺寸大小要合适,大小需要恰到好处能够覆盖一整页的数据,这就限制了那些 MTU 大小大于系统内存页的网络,比如 FDDI 和 ATM。其次,为了在没有任何中断的情况下将页面重映射到数据包的流,数据包中的数据部分必须占用整数个页面。对于异步接收数据的情况来说,为了将数据高效地移动到用户地址空间中去,可以使用这样一种方法:利用网络接口卡的支持,传来的数据包可以被分割成包头和数据两部分,数据被存放在一个单独的缓冲区内,虚拟存储系统然后就会将数据映射到用户地址空间缓冲区去。使用这种方法需要满足两个先决条件,也就是上面提到过的:一是应用程序缓冲区必须是页对齐的,并且在虚拟存储上是连续的;二是传来的数据有一页大小的时候才可以对数据包进行分割。事实上,这两个先决条件是很难满足的。如果应用程序缓冲区不是页对齐的,或者数据包的大小超过一个页,那么数据就需要被拷贝。对于数据发送端来说,就算数据在传输的过程中对于应用程序来说是写保护的,应用程序仍然需要避免使用这些忙缓冲区,这是因为写时拷贝操作所带来的开销是很大的。如果没有端到端这一级别的通知,那么应用程序很难会知道某缓冲区是否已经被释放还是仍然在被占用。

    这种零拷贝技术比较适用于那种写时复制事件发生比较少的情况,因为写时复制事件所产生的开销要远远高于一次 CPU 拷贝所产生的开销。实际情况中,大多数应用程序通常都会多次重复使用相同的缓冲区,所以,一次使用完数据之后,不要从操作系统地址空间解除页面的映射,这样会提高效率。考虑到同样的页面可能会被再次访问,所以保留页面的映射可以节省管理开销,但是,这种映射保留不会减少由于页表往返移动和 TLB 冲刷所带来的开销,这是因为每次页面由于写时复制而进行加锁或者解锁的时候,页面的只读标志都要被更改。

    缓冲区共享

    还有另外一种利用预先映射机制的共享缓冲区的方法也可以在应用程序地址空间和操作系统内核之间快速传输数据。采用缓冲区共享这种思想的架构最先在 Solaris 上实现,该架构使用了“ fbufs ”这个概念。这种方法需要修改 API。应用程序地址空间和操作系统内核地址空间之间的数据传递需要严格按照 fbufs 体系结构来实现,操作系统内核之间的通信也是严格按照 fbufs 体系结构来完成的。每一个应用程序都有一个缓冲区池,这个缓冲区池被同时映射到用户地址空间和内核地址空间,也可以在必要的时候才创建它们。通过完成一次虚拟存储操作来创建缓冲区,fbufs 可以有效地减少由存储一致性维护所引起的大多数性能问题。该技术在 Linux 中还停留在实验阶段。

    为什么要扩展 Linux I/O API

    传统的 Linux 输入输出接口,比如读和写系统调用,都是基于拷贝的,也就是说,数据需要在操作系统内核和应用程序定义的缓冲区之间进行拷贝。对于读系统调用来说,用户应用程序呈现给操作系统内核一个预先分配好的缓冲区,内核必须把读进来的数据放到这个缓冲区内。对于写系统调用来说,只要系统调用返回,用户应用程序就可以自由重新利用数据缓冲区。

    为了支持上面这种机制,Linux 需要能够为每一个操作都进行建立和删除虚拟存储映射。这种页面重映射的机制依赖于机器配置、cache 体系结构、TLB 未命中处理所带来的开销以及处理器是单处理器还是多处理器等多种因素。如果能够避免处理 I/O 请求的时候虚拟存储 / TLB 操作所产生的开销,则会极大地提高 I/O 的性能。fbufs 就是这样一种机制。使用 fbufs 体系结构就可以避免虚拟存储操作。由数据显示,fbufs 这种结构在 DECStation™ 5000/200 这个单处理器工作站上会取得比上面提到的页面重映射方法好得多的性能。如果要使用 fbufs 这种体系结构,必须要扩展 Linux API,从而实现一种有效而且全面的零拷贝技术。

    快速缓冲区( Fast Buffers )原理介绍

    I/O 数据存放在一些被称作 fbufs 的缓冲区内,每一个这样的缓冲区都包含一个或者多个连续的虚拟存储页。应用程序访问 fbuf 是通过保护域来实现的,有如下这两种方式:

    • 如果应用程序分配了 fbuf,那么应用程序就有访问该 fbuf 的权限
    • 如果应用程序通过 IPC 接收到了 fbuf,那么应用程序对这个 fbuf 也有访问的权限

    对于第一种情况来说,这个保护域被称作是 fbuf 的“ originator ”;对于后一种情况来说,这个保护域被称作是 fbuf 的“ receiver ”。

    传统的 Linux I/O 接口支持数据在应用程序地址空间和操作系统内核之间交换,这种交换操作导致所有的数据都需要进行拷贝。如果采用 fbufs 这种方法,需要交换的是包含数据的缓冲区,这样就消除了多余的拷贝操作。应用程序将 fbuf 传递给操作系统内核,这样就能减少传统的 write 系统调用所产生的数据拷贝开销。同样的,应用程序通过 fbuf 来接收数据,这样也可以减少传统 read 系统调用所产生的数据拷贝开销。如下图所示:

    图 5. Linux I/O API

    I/O 子系统或者应用程序都可以通过 fbufs 管理器来分配 fbufs。一旦分配了 fbufs,这些 fbufs 就可以从程序传递到 I/O 子系统,或者从 I/O 子系统传递到程序。使用完后,这些 fbufs 会被释放回 fbufs 缓冲区池。

    fbufs 在实现上有如下这些特性,如图 9 所示:

    • fbuf 需要从 fbufs 缓冲区池里分配。每一个 fbuf 都存在一个所属对象,要么是应用程序,要么是操作系统内核。fbuf 可以在应用程序和操作系统之间进行传递,fbuf 使用完之后需要被释放回特定的 fbufs 缓冲区池,在 fbuf 传递的过程中它们需要携带关于 fbufs 缓冲区池的相关信息。
    • 每一个 fbufs 缓冲区池都会和一个应用程序相关联,一个应用程序最多只能与一个 fbufs 缓冲区池相关联。应用程序只有资格访问它自己的缓冲区池。
    • fbufs 不需要虚拟地址重映射,这是因为对于每个应用程序来说,它们可以重新使用相同的缓冲区集合。这样,虚拟存储转换的信息就可以被缓存起来,虚拟存储子系统方面的开销就可以消除。
    • I/O 子系统(设备驱动程序,文件系统等)可以分配 fbufs,并将到达的数据直接放到这些 fbuf 里边。这样,缓冲区之间的拷贝操作就可以避免。

    图 6. fbufs 体系结构

    前面提到,这种方法需要修改 API,如果要使用 fbufs 体系结构,应用程序和 Linux 操作系统内核驱动程序都需要使用新的 API,如果应用程序要发送数据,那么它就要从缓冲区池里获取一个 fbuf,将数据填充进去,然后通过文件描述符将数据发送出去。接收到的 fbufs 可以被应用程序保留一段时间,之后,应用程序可以使用它继续发送其他的数据,或者还给缓冲区池。但是,在某些情况下,需要对数据包内的数据进行重新组装,那么通过 fbuf 接收到数据的应用程序就需要将数据拷贝到另外一个缓冲区内。再者,应用程序不能对当前正在被内核处理的数据进行修改,基于这一点,fbufs 体系结构引入了强制锁的概念以保证其实现。对于应用程序来说,如果 fbufs 已经被发送给操作系统内核,那么应用程序就不会再处理这些 fbufs。

    fbufs 存在的一些问题

    管理共享缓冲区池需要应用程序、网络软件、以及设备驱动程序之间的紧密合作。对于数据接收端来说,网络硬件必须要能够将到达的数据包利用 DMA 传输到由接收端分配的正确的存储缓冲区池中去。而且,应用程序稍微不注意就会更改之前发到共享存储中的数据的内容,从而导致数据被破坏,但是这种问题在应用程序端是很难调试的。同时,共享存储这种模型很难与其他类型的存储对象关联使用,但是应用程序、网络软件以及设备驱动程序之间的紧密合作是需要其他存储管理器的支持的。对于共享缓冲区这种技术来说,虽然这种技术看起来前景光明,但是这种技术不但需要对 API 进行更改,而且需要对驱动程序也进行更改,并且这种技术本身也存在一些未解决的问题,这就使得这种技术目前还只是出于试验阶段。在测试系统中,这种技术在性能上有很大的改进,不过这种新的架构的整体安装目前看起来还是不可行的。这种预先分配共享缓冲区的机制有时也因为粒度问题需要将数据拷贝到另外一个缓冲区中去。

    总结

    本系列文章介绍了 Linux 中的零拷贝技术,本文是其中的第二部分。本文对第一部分文章中提出的 Linux 操作系统上出现的几种零拷贝技术进行了更详细的介绍,主要描述了它们各自的优点,缺点以及适用场景。对于网络数据传输来说,零拷贝技术的应用受到了很多体系结构方面因素的阻碍,包括虚拟存储体系结构以及网络协议体系结构等。所以,零拷贝技术仍然只是在某些很特殊的情况中才可以应用,比如文件服务或者使用某种特殊的协议进行高带宽的通信等。但是,零拷贝技术在磁盘操作中的应用的可行性就高得多了,这很可能是因为磁盘操作具有同步的特点,以及数据传输单元是按照页的粒度来进行的。

    针对 Linux 操作系统平台提出并实现了很多种零拷贝技术,但是并不是所有这些零拷贝技术都被广泛应用于现实中的操作系统中的。比如,fbufs 体系结构,它在很多方面看起来都很吸引人,但是使用它需要更改 API 以及驱动程序,它还存在其他一些实现上的困难,这就使得 fbufs 还只是停留在实验的阶段。动态地址重映射技术只是需要对操作系统做少量修改,虽然不需要修改用户软件,但是当前的虚拟存储体系结构并不能很好地支持频繁的虚拟地址重映射操作。而且为了保证存储的一致性,重映射之后还必须对 TLB 和一级缓存进行刷新。事实上,利用地址重映射实现的零拷贝技术适用的范围是很小的,这是因为虚拟存储操作所带来的开销往往要比 CPU 拷贝所产生的开销还要大。此外,为了完全消除 CPU 访问存储,通常都需要额外的硬件来支持,而这种硬件的支持并不是很普及,同时也是非常昂贵的。

    本系列文章的目的是想帮助读者理清这些出现在 Linux 操作系统中的零拷贝技术都是从何种角度来帮助改善数据传输过程中遇到的性能问题的。关于各种零拷贝技术的具体实现细节,本系列文章没有做详细描述。同时,零拷贝技术一直是在不断地发展和完善当中的,本系列文章并没有涵盖 Linux 上出现的所有零拷贝技术。

    Linux 中的直接 I/O

    如果应用程序可以直接访问网络接口存储,那么在应用程序访问数据之前存储总线就不需要被遍历,数据传输所引起的开销将会是最小的。应用程序或者运行在用户模式下的库函数可以直接访问硬件设备的存储,操作系统内核除了进行必要的虚拟存储配置工作之外,不参与数据传输过程中的其它任何事情。直接 I/O 使得数据可以直接在应用程序和外围设备之间进行传输,完全不需要操作系统内核页缓存的支持。关于直接 I/O 技术的具体实现细节可以参看 developerWorks 上的另一篇文章”Linux 中直接 I/O 机制的介绍” ,本文不做过多描述。

    图 1. 使用直接 I/O 的数据传输

    针对数据传输不需要经过应用程序地址空间的零拷贝技术

    利用 mmap()

    在 Linux 中,减少拷贝次数的一种方法是调用 mmap() 来代替调用 read,比如:

    	 tmp_buf = mmap(file, len); 
    	 write(socket, tmp_buf, len);

    首先,应用程序调用了 mmap() 之后,数据会先通过 DMA 拷贝到操作系统内核的缓冲区中去。接着,应用程序跟操作系统共享这个缓冲区,这样,操作系统内核和应用程序存储空间就不需要再进行任何的数据拷贝操作。应用程序调用了 write() 之后,操作系统内核将数据从原来的内核缓冲区中拷贝到与 socket 相关的内核缓冲区中。接下来,数据从内核 socket 缓冲区拷贝到协议引擎中去,这是第三次数据拷贝操作。

    图 2. 利用 mmap() 代替 read()

    通过使用 mmap() 来代替 read(), 已经可以减半操作系统需要进行数据拷贝的次数。当大量数据需要传输的时候,这样做就会有一个比较好的效率。但是,这种改进也是需要代价的,使用 mma()p 其实是存在潜在的问题的。当对文件进行了内存映射,然后调用 write() 系统调用,如果此时其他的进程截断了这个文件,那么 write() 系统调用将会被总线错误信号 SIGBUS 中断,因为此时正在执行的是一个错误的存储访问。这个信号将会导致进程被杀死,解决这个问题可以通过以下这两种方法:

    1. 为 SIGBUS 安装一个新的信号处理器,这样,write() 系统调用在它被中断之前就返回已经写入的字节数目,errno 会被设置成 success。但是这种方法也有其缺点,它不能反映出产生这个问题的根源所在,因为 BIGBUS 信号只是显示某进程发生了一些很严重的错误。
    2. 第二种方法是通过文件租借锁来解决这个问题的,这种方法相对来说更好一些。我们可以通过内核对文件加读或者写的租借锁,当另外一个进程尝试对用户正在进行传输的文件进行截断的时候,内核会发送给用户一个实时信号:RT_SIGNAL_LEASE 信号,这个信号会告诉用户内核破坏了用户加在那个文件上的写或者读租借锁,那么 write() 系统调用则会被中断,并且进程会被 SIGBUS 信号杀死,返回值则是中断前写的字节数,errno 也会被设置为 success。文件租借锁需要在对文件进行内存映射之前设置。

    使用 mmap 是 POSIX 兼容的,但是使用 mmap 并不一定能获得理想的数据传输性能。数据传输的过程中仍然需要一次 CPU 拷贝操作,而且映射操作也是一个开销很大的虚拟存储操作,这种操作需要通过更改页表以及冲刷 TLB (使得 TLB 的内容无效)来维持存储的一致性。但是,因为映射通常适用于较大范围,所以对于相同长度的数据来说,映射所带来的开销远远低于 CPU 拷贝所带来的开销。

    sendfile()

    为了简化用户接口,同时还要继续保留 mmap()/write() 技术的优点:减少 CPU 的拷贝次数,Linux 在版本 2.1 中引入了 sendfile() 这个系统调用。

    sendfile() 不仅减少了数据拷贝操作,它也减少了上下文切换。首先:sendfile() 系统调用利用 DMA 引擎将文件中的数据拷贝到操作系统内核缓冲区中,然后数据被拷贝到与 socket 相关的内核缓冲区中去。接下来,DMA 引擎将数据从内核 socket 缓冲区中拷贝到协议引擎中去。如果在用户调用 sendfile () 系统调用进行数据传输的过程中有其他进程截断了该文件,那么 sendfile () 系统调用会简单地返回给用户应用程序中断前所传输的字节数,errno 会被设置为 success。如果在调用 sendfile() 之前操作系统对文件加上了租借锁,那么 sendfile() 的操作和返回状态将会和 mmap()/write () 一样。

    图 3. 利用 sendfile () 进行数据传输

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

    • sendfile() 局限于基于文件服务的网络应用程序,比如 web 服务器。据说,在 Linux 内核中实现 sendfile() 只是为了在其他平台上使用 sendfile() 的 Apache 程序。
    • 由于网络传输具有异步性,很难在 sendfile () 系统调用的接收端进行配对的实现方式,所以数据传输的接收端一般没有用到这种技术。
    • 基于性能的考虑来说,sendfile () 仍然需要有一次从文件到 socket 缓冲区的 CPU 拷贝操作,这就导致页缓存有可能会被传输的数据所污染。

    带有 DMA 收集拷贝功能的 sendfile()

    上小节介绍的 sendfile() 技术在进行数据传输仍然还需要一次多余的数据拷贝操作,通过引入一点硬件上的帮助,这仅有的一次数据拷贝操作也可以避免。为了避免操作系统内核造成的数据副本,需要用到一个支持收集操作的网络接口,这也就是说,待传输的数据可以分散在存储的不同位置上,而不需要在连续存储中存放。这样一来,从文件中读出的数据就根本不需要被拷贝到 socket 缓冲区中去,而只是需要将缓冲区描述符传到网络协议栈中去,之后其在缓冲区中建立起数据包的相关结构,然后通过 DMA 收集拷贝功能将所有的数据结合成一个网络数据包。网卡的 DMA 引擎会在一次操作中从多个位置读取包头和数据。Linux 2.4 版本中的 socket 缓冲区就可以满足这种条件,这也就是用于 Linux 中的众所周知的零拷贝技术,这种方法不但减少了因为多次上下文切换所带来开销,同时也减少了处理器造成的数据副本的个数。对于用户应用程序来说,代码没有任何改变。首先,sendfile() 系统调用利用 DMA 引擎将文件内容拷贝到内核缓冲区去;然后,将带有文件位置和长度信息的缓冲区描述符添加到 socket 缓冲区中去,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,DMA 引擎会将数据直接从内核缓冲区拷贝到协议引擎中去,这样就避免了最后一次数据拷贝。

    引言

    传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为如果所请求的数据已经存放在操作系统的高速缓冲存储器中,那么就不需要再进行实际的物理磁盘 I/O 操作。但是数据传输过程中的数据拷贝操作却导致了极大的 CPU 开销,限制了操作系统有效进行数据传输操作的能力。

    零拷贝( zero-copy )这种技术可以有效地改善数据传输的性能,在内核驱动程序(比如网络堆栈或者磁盘存储驱动程序)处理 I/O 数据的时候,零拷贝技术可以在某种程度上减少甚至完全避免不必要 CPU 数据拷贝操作。现代的 CPU 和存储体系结构提供了很多特征可以有效地实现零拷贝技术,但是因为存储体系结构非常复杂,而且网络协议栈有时需要对数据进行必要的处理,所以零拷贝技术有可能会产生很多负面的影响,甚至会导致零拷贝技术自身的优点完全丧失。

    展开全文
  • linux网络拷贝方式

    2014-03-31 09:06:04
    ftp ftp 命令使用文件传输协议(File Transfer Protocol, FTP)在本地主机和远程主机之间或者在两个远程主机之间进行文件传输。 FTP 协议允许数据在不同文件系统的主机之间传输。尽管这个协议在传输数据上提供了高...

    ftp

    ftp 命令使用文件传输协议(File Transfer Protocol, FTP)在本地主机和远程主机之间或者在两个远程主机之间进行文件传输。

    FTP 协议允许数据在不同文件系统的主机之间传输。尽管这个协议在传输数据上提供了高适应性,但是它并没有尝试去保留一个特定文件系统上的文件属性(例如一个文件的保护模式或者修改次数)。而且 FTP 协议很少对一个文件系统的整体结构作假定,也不提供这样的功能,比如递归的拷贝子目录。在使用 ftp 命令时,需要注意 FTP 协议的这些特性。当需要保留文件属性或者需要递归的拷贝子目录时,可以使用 rcp/scp 等命令。

    基本语法

    ftp 命令的一般格式如下:

    $ ftp 主机名 /IP

    其中“主机名 /IP ”是所要连接的远程机的主机名或 IP 地址。在命令行中,主机名属于可选项,如果指定主机名,ftp 将试图与远程机的 ftp 服务程序进行连接;如果没有指定主机名,ftp 将给出提示符,等待用户输入命令:

    $ ftp 
     ftp >

    此时在 ftp> 提示符后面输入 open 子命令加主机名或 IP 地址,将试图连接指定的主机。不管使用哪一种方法,如果连接成功,需要在远程机上登录。用户如果在远程机上有帐号,就可以通过 ftp 使用这一帐号并需要提供口令。在远程机上的用户帐号的读写权限决定该用户在远程机上能下载什么文件和能将上载文件放到哪个目录中。在远程站点上登录成功后,在“ ftp> ”提示符下可以自由使用 ftp 提供的各种子命令,最常用的子命令如下表所示。


    表 1. ftp 子命令
    命令 描述
    ls 列出远程机的当前目录
    cd 在远程机上改变工作目录
    lcd 在本地机上改变工作目录
    ascii 设置文件传输方式为 ASCII 模式
    binary 设置文件传输方式为二进制模式
    close 终止当前的 ftp 会话
    get (mget) 从远程机传送指定文件到本地机
    put (mput) 从本地机传送指定文件到远程机
    open 连接远程 ftp 站点
    quit 断开与远程机的连接并退出 ftp
    ? 显示本地帮助信息
    ! 转到 Shell 中
    prompt 1 关闭交互模式

    使用实例:

    利用编写 ftp 脚本可以自动完成文件传输任务。具体方法是使用 ftp 命令的 -in 选项,并重定向 ftp 命令的输入。现在我们来编写一个利用 ftp 登录到远程服务器,并以 bin 的文件格式,在 /home 目录下,下载 file1.log 以及 file2.sh 至本机 /opt/ibm/,并从本地 /opt 目录上传文件 file3.jave 至远程服务器 /home 的自动化脚本。

    ftp -ni <<+ 
    	 open $IP 
    	 user $USERNAME $PASSWD 
    	 bin 
    	 cd /home 
    	 lcd /opt/ibm 
    	 mget file1.log file2.sh 
    	 lcd /opt 
    	 mput file3.jave 
    	 ls 
    	 bye

    rcp

    rcp 意为“ remote file copy ”(远程文件拷贝)。该命令用于计算机之间进行文件拷贝。其有两种格式。第一种格式用于文件到文件的拷贝;第二种格式用于把文件或目录拷贝到另一个目录中。

    基本语法

    rcp [-px] [-k realm] file1 file2 
     rcp [-px] [-r] [-k realm] file directory

    每个文件或目录参数既可以是远程文件名也可以是本地文件名。远程文件名具有如下形式:rname@rhost:path,其中 rname 为远程用户名,rhost 为远程计算机名,path 为该文件的路径。下表说明了 rcp 命令各个参数的含义。


    表 2. rcp 命令的命令行参数
    选项 描述
    -r 递归地将源目录中的所有内容拷贝到目的目录中。若使用该选项,目的须为一个目录。
    -p 试图保留源文件的修改时间和模式,忽略 umask 。
    -k 请求 rcp 获得在指定区域内的远程主机的 Kerberos 许可,而不是获得由 krb_relmofhost(3)确定的远程主机区域内的远程主机的 Kerberos 许可。
    -x 为传送的所有数据进行 DES 加密。这会影响响应时间和 CPU 利用率,但是可以提高安全性。

    如果在文件名中指定的路径不是完整的路径名,则该路径将被解释为相对远程机上同名用户的主目录。若没有给出远程用户名,则使用当前用户名。如果远程机上的路径包含特殊 shell 字符,需要使用反斜线(\)、双引号(”)或单引号(’)将其括起来,使所有的 shell 元字符都能被远程地解释。需要说明的是,rcp 不提示输入口令,它通过 rsh(remote shell)命令来执行拷贝。

    使用实例:

    • 将本地文件复制到远程登录目录中

      rcp <source> <remoteDir>

    • 将多个本地文件复制到远程登录目录的子目录中

      rcp <source1> <source2> <source3> <subdirectory in remote system>

    • 将多个文件从多个远程源复制到使用不同用户名的远程目标中

      rcp <host1.user1:source1> <host2.user2:source2> <dest.destuser:directory>

    scp

    scp 命令在网络上的主机之间拷贝文件,它是安全拷贝(secure copy)的缩写。 scp 命令使用 ssh 来传输数据,并使用与 ssh 相同的认证模式,提供同样的安全保障。 scp 命令的用法和 rcp 命令非常类似,这里就不做过多介绍了。一般推荐使用 scp 命令,因为它比 rcp 更安全。

    我们可以通过配置 ssh,使得在两台机器间拷贝文件时不需要每次都输入用户名和密码。

    基本语法

    scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file] 
             [-l limit] [-o ssh_option] [-P port] [-S program] 
             [[user@]host1:]file1 [...] [[user@]host2:]file2

    使用 scp 命令,需要输入密码,如果不想每次都输入,可参考下面的方法。

    首先生成密钥对

    $ ssh-keygen -t rsa 
     Generating public/private rsa key pair. 
     Enter file in which to save the key (/home/user/.ssh/id_rsa): 
     Created directory '/home/user/.ssh'. 
     Enter passphrase (empty for no passphrase): 
     Enter same passphrase again: 
     Your identification has been saved in /home/user/.ssh/id_rsa. 
     Your public key has been saved in /home/user/.ssh/id_rsa.pub. 
     The key fingerprint is: 
     10:66:da:38:85:8a:8c:bd:db:9c:6e:eb:ee:bd:7d:15 user@somehost

    在这里,我们指定了生成 rsa 类型的密钥。在提示密钥的保存路径和密码时,可以直接回车使用默认路径和空密码。这样,生成的公共密钥保存在 $HOME/.ssh/id_rsa.pub,私有密钥保存在 $HOME/.ssh/id_rsa 。然后把这个密钥对中的公共密钥的内容复制到要访问的机器上的 $HOME/.ssh/authorized_keys 文件中。这样,下次再访问那台机器时,就不用输入密码了。

    使用实例:

    • Copy 本地文件 /etc/eva.log, 到远程机器 sysB, 用户 user 的家目录下

      scp /etc/eva.log user@sysB:/home/user

    • copy 远程机器 sysB 上的文件 /home/uesr/eva.log, 到本地的 /etc 目录下 , 并保持文件属性不变

      scp -p user@sysB:/home/uesr/eva.log /etc

    • copy sysB 上的目录 /home/user, 到本地 /home/user/tmp, <new dir,/home/user/tmp/user>

      scp -r user@sysB:/home/user /home/user/tmp

    wget

    wget 是一个经由 GPL 许可的可从网络上自动获取文件的自由软件包。它是一个非交互式的命令行工具。支持 HTTP,HTTPS 和 FTP 协议,支持代理服务器以及断点续传功能。 wget 可实现递归下载,即可跟踪 HTML 页面上的链接依次下载来创建远程服务器的本地版本,完全重建原始站点的目录结构,实现远程网站的镜像。在递归下载时,wget 将页面中的超级链接转换成指向本地文件,方便离线浏览。由于非交互特性,wget 支持后台运行,用户在退出系统后,仍可继续运行。功能强大,设置方便简单。

    基本语法

    wget [options] [URL-list]

    wget 有很多不同的参数以用于远程站点信息的获取,常用参数如下,更多参数请参照 wget 帮助手册 http://www.gnu.org/software/wget/manual/wget.html


    表 3. wget 工具常用参数
    选项 描述
    -r 递归下载服务器上所有的目录和文件。由 -l 选项来指定递归深度。
    -b 后台下载
    -m 制作站点镜像
    -c 指定断点续传功能。该功能要求服务器支持断点续传。
    -I 指定下载目录列表,可实现批量下载
    -A/-R 指定接受/拒绝下载列表,实现选择性地下载
    --proxy=on/off 指定是否利用代理服务器进行下载
    -t, --tries=NUMBER 最大尝试链接次数 (0 表示无限制,默认为 20 次 )
    -nc, --no-clobber 不覆盖已存在的文件
    -N, --timestamping 只下载比本地新的文件
    -nd --no-directories 不进行目录结构创建
    -x, --force-directories 强制创建目录结构
    -nH, --no-host-directories 不继承主机目录结构
    -P, --directory-prefix=PREFIX 设置目录前缀

    使用实例:

    • 递归下载 http://www.ibm.com.cn 站点的信息。下载所有显示完整网页所以需要的文件,如图片等。在下载不进行上层目录搜索并将绝对链接转换为相对链接。
      wget -r -p -np -k http://www.ibm.com.cn
    • 将在本地硬盘建立 http://www.ibm.com.cn 的镜像,镜像文件存入当前目录下一个名为 www.ibm.com.cn 的子目录中(也可以使用 -nH 参数指定不建立该子目录,而直接在当前目录下建立镜像的目录结构),递归深度为 4,重试次数为无穷(若连接出现问题,wget 将永远重试下去,直至任务完成)
      wget -m -l4 -t0 http://www.ibm.com.c
    • 使用代理进行下载,并实现断点续传。代理可以在环境变量 PROXY 或 wgetrc 文件中设定。 -c 选项要求服务支持断点续传。
      wget -Y on -c http://www.ibm.com.cn

    curl

    另一个可以用来进行文件传输的工具是 curl,它是对 libcurl 库的一个命令行工具包装。 libcurl 库中提供了相应功能的 API,可以在程序中调用。对于 libcurl 库的使用方法介绍超出了本文的讨论范围。 curl 使用 URL 的语法来传输文件,它支持 FTP, FTPS, HTTP, HTTPS, TFTP, SFTP, TELNET 等多种协议。 curl 功能强大,它提供了包括代理支持,用户认证,FTP 上载,HTTP post,SSL 连接,文件续传等许多特性。

    基本语法

    curl [options … ] <url>

    其中下载参数大约有 80 多个,curl 的各个功能完全依靠这些参数来完成。下面举例说明 curl 的一些基本用法。

    使用实例:

    • 获取 GNU 的主页

      curl http://www.gnu.org

    • 获取 GNU 的 FTP 服务器上根目录下的 README 文件

      curl ftp://ftp.gnu.org/README

    • 从一个字典中获取 curl 的定义

      curl dict://dict.org/m:curl

    • 如果需要指定用户名和密码的话,可以在 url 中指定,或者使用 -u 参数

      curl ftp://name:passwd@machine.domain:port/full/path/to/file

      curl -u name:passwd ftp://machine.domain:port/full/path/to/file

    • curl 会将从指定 url 处获取的内容打印到标准输出上。如果需要保存在本地文件中,可以使用 -o,或使用 -O 参数指定使用远程主机上的文件名(如果 url 中没有给出文件名的部分,则此操作将会失败)

      curl – o gnu.html http://www.gnu.org

      curl – O http://www.gnu.org/index.html

    • 使用 -x 选项来使用代理进行连接

      curl -x my-proxy:port ftp://ftp.somesite.com/README

    • 通过使用 curl 的 -T 选项来进行上载

      curl -T - ftp://ftp.upload.com/upfile

    • 此命令从标准输入读取数据,并上载至远程 FTP 服务器上的 upfile 文件中。也可以指定上载一个本地文件

      curl -T localfile -a ftp://ftp.upload.com/upfile

      其中 -a 参数表示以添加方式将 localfile 中的内容附加到 upfile 的末尾。

    总的来说,curl 适合用来进行自动的文件传输或操作序列,它是一个很好的模拟用户在网页浏览器上的行为的工具。尤其当需要在程序中调用时,libcurl 是个很好的选择。

    rsync

    rsync 是一款高效的远程数据备份和镜象工具,可快速地同步多台主机间的文件,其具有如下特性:

    • 支持链接、所有者、组信息以及权限信息的拷贝;
    • 通过远程 shell(ssh, rsh)进行传输;
    • 无须特殊权限即可安装使用;
    • 流水线式文件传输模式,文件传输效率高;
    • 支持匿名操作;

    需要提及的是 rsync 以其优越的性能优势区别于其它几种 Linux 文件传输方法,其同步文件的速度相当快,这主要归功于 rsync 所使用的传输算法。简而言之 rsync 算法能在相当短的时间内计算出需要备份的数据,只对源文件与目标文件的不同之处进行传输,从而降低网络中传输的数据量,以此达到快速备份镜像的目的。下面通过一典型应用场景来描述 rsync 算法的基本原理:主机 A 与主机 B 均有对同一文件的拷贝,用户对主机 A 上的拷贝进行更新,主机 B 通过 rsync 算法对更新后的文件进行同步。以下是该算法的实现步骤:

    1. 主机 B 将原始拷贝划分成大小为 N 的不重合的若干块(文件末尾部分分块大小可能不足 N),并对这些数据块进行两种不同方式的校验:32 位的滚动弱校验、128 位的 MD4 强校验。弱校验较之强校验计算速度快。
    2. 主机 B 将每个数据块的弱校验、强校验结果发送给主机 A 。
    3. 主机 A 对更新后的文件拷贝中的每个长度为 N 的数据块进行弱校验并与从 B 接收到的弱校验值进行匹配,若相同再进行强校验匹配。由于弱校验的滚动特性可以快速地筛选出需要进行同步的数据块。该算法的运算量主要集中在主机 A 上。
    4. 通过上述计算,主机 A 将文件的不同部分发送给 B,B 接收到两个拷贝之间的不同之处,从而同步得到更新后的文件。

    通过如上方式,rsync 避免了对相同数据的传输,减少了网络带宽的浪费。在时间上整个过程中需一个往返,从某种程度上也保证了 rsync 的优越性能。

    用户可从官方网站 http://rsync.samba.org/ 上下载安装 rsync 的最新版本。使用时需将 rsync 分别安装于服务端和客户端,服务端和客户端使用同一个 rsync 软件包来实现远程镜像和定期同步更新。需要说明的是一个 rsync 服务端可同时备份多个客户端的数据;多个服务端备份一个客户端的数据。 rsync 默认端口为 873,服务器在该端口接收客户的匿名或者认证方式的备份请求。

    rsync 服务端在使用之前需要进行必要的配置,其配置文件为 /etc/rsyncd.conf,进行认证、访问、日志记录等控制。配置文件包括全局参数、模块参数的设置。 rsyncd.conf 文件中 [module] 之前的所有参数为全局参数,也可以在全局参数部分定义模块参数,在这种情况下该参数的值就是所有模块的默认值。全局参数设置程序使用的端口号,指定消息文件、日志文件 pid 文件以及发送日志消息的级别。模块参数主要定义服务端哪个目录需要被同步。用户可根据不同的需要指定多个模块,每个模块对应需要备份的一个目录树,即若有 N 个需要备份的目录树,则需要 N 个模块与之对应。模块中可以定义许多参数,常见参数如下。


    表 4. rsyncd.conf 配置文件常见模块参数列表
    选项 描述
    Comment 模块信息描述,该描述连同模块名在客户连接得到模块列表时显示给客户。默认没有描述定义。
    Path 指定供备份的目录路径,必须指定该参数。
    max connections 指定最大并发连接数以保护服务器,超过限制的连接请求将被告知随后再试。默认值为 0,即没有限制。
    log file 指定日志文件
    read only 设定是否允许客户上载文件。若为 true 任何上载请求均会失败,若为 false 且客户端拥有服务器目录读写权限则可以上载。默认值为 true 。
    write only 设定是否允许客户下载文件。若为 true 任何下载请求均会失败,默认值为 false 。
    List 设定当客户请求可以使用的模块列表时,是否列出该模块。若为 false,则创建隐藏的模块。默认值为 true 。
    fake super 允许文件享有所有权限,而无需后台服务以 root 权限进行操作。
    Filter 设置过滤列表以决定哪些文件可由客户端访问。
    hosts allow 指定允许客户连接的 IP 地址。可以为单个 IP 地址或整个网段。多个 IP 或网段需要以空格隔开。默认是允许所有主机连接。
    dont compress 指定不进行压缩处理即可传输的文件,默认值是 *.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz
    pre-xfer exec, post-xfer exec 设置可在文件传输前/后执行的命令。若在文件传输前执行的命令失败,则取消本次传输操作。

    基本语法

    • 通过远程 shell 方式:
      • rsync [OPTION] [USER@]HOST:SRC DEST
      • 使用远程 shell(如 ssh, rsh)实现将远程机器的内容拷贝到本地机器。 SRC 地址路径中以单个冒号 ":" 进行分隔。
      • rsync [OPTION] SRC [USER@]HOST:DEST
      • 使用远程 shell(如 rsh、ssh)实现本地机器的内容拷贝到远程机器。 DEST 地址路径中以单个冒号 ":" 进行分隔。
    • 通过 rsync daemon 方式:
      • rsync [OPTION] [USER@]HOST::SRC DEST 或
      • rsync [OPTION] rsync://[USER@]HOST[:PORT]/SRC [DEST]
      • 从远程 rsync 服务器中拷贝文件到本地机。 SRC 地址路径中以双冒号 "::" 进行分隔。
      • rsync [OPTION] SRC [USER@]HOST::DEST 或
      • rsync [OPTION] SRC rsync://[USER@]HOST[:PORT]/DEST
      • 从本地机器拷贝文件到远程 rsync 服务器中。 DEST 地址路径中以双冒号 "::" 进行分隔。

    如果 rsync 命令中只指定 SRC 参数而不指定 DEST 参数,则意为显示源文件列表而非进行同步拷贝。 rsync 有许多功能选项,常用的选项如下:


    表 5. rsync 常用参数
    选项 描述
    -a, --archive 归档模式,保持所有文件属性,等同于 -rlptgoD
    -v, --verbose 详细信息输出
    -r, --recursive 对子目录进行递归处理
    -R, --relative 使用相对路径信息
    -b, --backup 创建备份
    -z, --compress 对备份的文件在传输时进行压缩处理
    --delete 用于同步目录,从 DEST 中将 SRC 不存在的文件进行删除
    --progress 显示备份过程

    使用实例:

    • 查看服务端文件及列表
      • # rsync 9.186.110.53::

        查看服务端可用的模块列表以及注释信息

      • # rsync ibmuser@9.186.110.53::www/

        查看服务端 www 模块中的目录及文件列表(使用 rsyncd 用户认证方式)

      • # rsync ibmuser@9.186.110.53:/var/www/html/

        查看服务端 /var/www/html 目录中的内容(使用服务端的系统用户进行验证,如 ibmuser)

    • 保持客户端与服务端的数据同步
      • # rsync -avz ibmuser@9.186.110.53::www/ /backup1/

        使用后台服务方式将服务端 www 模块下的内容备份到本地 /backup1 目录中,备份时保留原有权限、属性、属主及符号连接等,并使用压缩方式加快数据传输。

      • # rsync – avz ibmuser@9.186.110.53:/var/www/html /backup2/

        使用 ssh 方式将远程的 /var/www/html 目录备份到本地 /backup2/ 目录下

      • # rsync -avz --delete ibmuser@9.186.110.53::www/ /backup3/

        将远程 www 模块备份到本地 /backup3/ 目录中,同时进行同步目录,删除本地目录中多余的文件。

    当服务端的数据出现问题时,需要通过客户端的数据对服务端进行恢复,只要客户端有服务端的写入权限,即可通过调换 rsync 命令的 SRC、DEST 参数进行恢复。

    结尾与总结

    综上所述,各种文件传输方式的特征表现各有千秋,我们从以下几个方面综合对比,更深入地了解它们各自的特性。

    • 传输性能

    wget 通过支持后台执行及断点续传提高文件传输效率 ; rsync 则以其高效的传输及压缩算法达到快传输的目的。

    • 配置难度

    rcp 只需进行简单的配置,创建 .rhost 文件以及设置 /etc/hosts 文件中主机名与 IP 地址列表; wget 设置设置方便简单,只需在客户端指定参数执行命令即可; rsync 在使用前需要对服务端 /etc/rsyncd.conf 进行参数设定,配置内容相对复杂。

    • 安全性能

    ftp、rcp 不保证传输的安全性,scp、rsync 则均可基于 ssh 认证进行传输,提供了较强的安全保障。 wget 也可通过指定安全协议做到安全传输。

    通过上述的对比不难发现,每种文件传输方法基于其自身的特点与优势均有其典型的适用场景:

    • ftp 作为最常用的入门式的文件传输方法,使用简单,易于理解,并且可以实现脚本自动化;
    • rcp 相对于 ftp 可以保留文件属性并可递归的拷贝子目录;
    • scp 利用 ssh 传输数据,并使用与 ssh 相同的认证模式,相对于 rcp 提供更强的安全保障;
    • wget,实现递归下载,可跟踪 HTML 页面上的链接依次下载来创建远程服务器的本地版本,完全重建原始站点的目录结构,适合实现远程网站的镜像;
    • curl 则适合用来进行自动的文件传输或操作序列,是一个很好的模拟用户在网页浏览器上的行为的工具;
    • rsync 更适用于大数据量的每日同步,拷贝的速度很快,相对 wget 来说速度快且安全高效。

      

    读者可在不同的场合根据实际需要,选择适合的文件传输方法。


    参考资料

    展开全文
  • 限制IO速度

    2013-04-22 23:02:44
    scp -l 来限制拷贝带宽。 用cgroup来隔离资源,而到io层面,常用的就是iothrottle。
  • linux拷贝

    2017-05-22 09:54:47
    传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输,这样做最大的好处是可以减少实际的物理磁盘 I/O ...
  • Linux-scp安全拷贝协议

    2017-01-15 19:31:48
    scp 是安全拷贝协议 Secure Copy Protocol的缩写,和众多 Linux/Unix 使用者所熟知的拷贝(cp)命令一样。scp 的使用方式类似于 cp 命令,cp 命令将一个文件或文件夹从本地操作系统的一个位置(源)拷贝到目标位置...
  • # scp 拷贝本地文件filename 到远程机器 192.168.188.188 服务器的/data/tmp目录下 scp -P 61204 -l 40000 filenameusername@192.168.188.188:/data/tmp/-P port Specifies the port to connect to ...
  • 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为...
  • 比如有一个/usr/tmp/ 下面有几个目录 dira, dirb,dirc, 现在要想把 /usr/tmp 拷贝到 /home/xx/ 下面,但又不想要dirc 有几种方法可以实现 1) 使用rsync --exclude cd /usr rsync -avP --exclude=dirc/ tmp...
  • SMB带宽限制

    2019-01-10 02:36:47
    1.安装SMB带宽限制功能  Add-WindowsFeature FS-SMBBW  2.通过PowerShell命令限制SMB带宽  #限制非Hyper-V over SMB or Live Migration使用带宽为10MB   Set-SmbBandwidthLimit -Category Default -Bytes...
  • 概述 黄 晓晨, 软件工程师, IBM ...本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关背景知识,简要概述了 Linux 为什么需要零拷贝技术以及 Linux 中都有哪几种零拷贝技术。 引言 传统的
  • 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为...
  • 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为...
1 2 3 4 5 ... 20
收藏数 11,300
精华内容 4,520
关键字:

linux限制拷贝带宽