使用sendfile()让数据传输得到最优化,TCP_CORK、TCP_DEFER_ACCEPT和TCP_QUICKACK优化网络
当今国互联网的飞速发展让人们获益匪浅,同时人们对于互联网的期望值也变得越来越高。这就形成
了一个矛盾,虽然互联网的发展已经是相当迅猛的了,但是人们还是期望从服务器到客户终端的文件传输
的速度能够比现在再快一些,这种要求(当然是合理的要求)好像从来也满足不了。在向人们询问“一种
什么样的速度对于数据传输来说才是最理想的”问题时,几乎每一次你都会得到一种不同的答案:有的人
认为数据传输的速率越快越好,有的人则认为数据传输的速率只要在能够容忍的限度之内就可以了,而另
一人则认为一种数据传输时不会有数据损失的最快速率才是他们真正需要的,当然,这里还有许多其它的
回答,我就不一一赘述了。
在现实中,对于这个有关数据传输的问题是没有一个统一的答案的。绝大多数的人在评价数据传输的
快慢时,还是会使用“每秒钟传输的兆字节数”来作为一种衡量的标准。但是,数据传输快慢的真正衡量
标准却是CPU对于每一个传输的兆字节所花费占用的时间。对于实时应用软件,尤其是那些传输视频或者
音频数据流的软件,最不想出现的一种状况就是所谓的“延迟”。CPU执行任务的时候如果没有任何的效
率,那么,要实现对协议层(protocol-level)的负载平衡(load balancing)以及将主机的IP名私人化
的支持(一种叫做Virtuozzo的操作系统虚拟化技术)都是不太可能的。一个加装了Virtuozzo的主机内能
够装得下数以千计的网络站点,所以,尽可能的减少用来处理数据传输占据的CPU时间是非常重要的。
Sendfile()是一种崭新的操作系统核心,这种新核心能够帮助人们解决上边所描述的那些问题。而且
,这种新内核对于UNIX,Linux, Solaris 8操作系统来说都是适用的。从技术角度来看,sendfile()是磁
盘和传输控制协议(TCP)之间的一种系统呼叫,但是sendfile()还能够用来在两个文件夹之间移动数据
。在各种不同的操作系统上实现sendfile()都会有所不同,当然这种不同只是极为细微的差别。通常来说
,我们会假定所使用的操作系统是Linux核心2.4版本。
系统呼叫的原型有如下几种:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset,size_t count)
in_fd是一种用来读文件的文件描述符。
out_fd是一种用来写文件的描述符。
Offset是一种指向被输入文件变量位置的指针,sendfile()将会从它所指向的位置开始数据的读取。
Count表示的是两个文件描述符之间数据拷贝的字节数。
sendfile()的威力在于,它为大家提供了一种访问当前不断膨胀的Linux网络堆栈的机制。这种机制叫
做“零拷贝(zero-copy)”,这种机制可以把“传输控制协议(TCP)”框架直接的从主机存储器中传送到
网卡的缓存块(network card buffers)中去。
为了更好的理解“零拷贝(zero-copy)”以及sendfile(),让我们回忆一下以前我们在传送文件时所
需要执行的那些步骤。首先,一块在用户机器存储器内用于数据缓冲的位置先被确定了下来。然后,我们
必须使用read()这条系统呼叫来把数据从文件中拷贝到前边已经准备好的那个缓冲区中去。(在通常的情
况下,这个操做会把数据从磁盘上拷贝到操作系统的高速缓冲存储器中去,然后才会把数据从高速缓冲存
储器中拷贝至用户空间中去,这种过程就是所谓的“上下文切换”。)
在完成了上述的那些步骤之后,我们得使用write()系统呼叫来将缓冲区中的内容发送到网络上去,
程序段如下所示:
intout_fd, intin_fd;
char buffer[BUFLEN];
…
/* unsubstantial code skipped for clarity */
…
read(in_fd, buffer, BUFLEN); /* syscall, make context switch */
write(out_fd, buffer, BUFLEN); /* syscall, make context switch */
操作系统核心不得不把所有的数据至少都拷贝两次:先是从核心空间到用户空间的拷贝,然后还得再
从用户空间拷贝回核心空间。每一次操做都需要上下文切换(context-switch)的这个步骤,其中包含了
许多复杂的高度占用CPU的操作。系统自带的工具vmstat能够用来在绝大多数UNIX以及与其类似的操作系
统上显示当前的“上下文切换(context-switch)”速率。请看叫做“CS”的那一栏,有相当一部分的上
下文切换是发生在取样期间的。用不同类型的方式进行装载可以让使用者清楚的看到使用这些参数进行装
载时的不同效果。
关于切换过程的一些具体细节:
让我们向着有关上下文切换过程的更深层次挖掘,这样做能够让我们更好的理解有关切换的一些问题。这里有许多种有关从用户空间呼叫系统的操作的方法。举个例子来说,将虚拟内存页面从用户空间中切换到核心,然后再切换回去的这种操作是必不可少的。这种操作过程需要的系统花销是相当大的(尤其是在CPU周期的占用方面)。这种操做是通过使用叫做全局描述符台面(Global Descriptor Table)以及局部描述符台面(LocalDescriptor Table)的存储器控制台来实现的。另外的一种结构被称之为TSS (Task Status Segment任务状态段)的工具也需要大家给予足够的重视。
此外,还有一些隐藏的非常“昂贵”的操作没有被上下文切换程序呼叫出来。我们能够通过虚拟内存需要虚拟物理地址翻译操作的支持才能实现的例子来说明这些隐藏操作的存在。这种翻译所需要的数据也是存储于存储器中的,所以,CPU每一次对这些数据存储位置的请求都需要对主存储器进行一次或多次的访问(这是为了读取翻译台入口),这是除了需要获取的那些数据外还要进行的一些操作。现在的CPU通常都包含了一个翻译缓存,其缩写为TLB。TLB是作为页面入口来工作的,其中存储了最近访问过的对象。(这是对TLB最为简单的解释,其具体的解释应为:OLE库文件,其中存放了OLE自动化对象的数据类型、模块和接口定义,自动化服务器通过TLB文件就能了解自动化对象的使用方法。)TLB高速缓冲存储器拥有巨大的潜在花费,其中包括了几个存储器的访问操作以及页面错误处理器的实行操作。在拷贝大量数据的时候,会对TLB高速缓冲存储器产生大量的消耗。在这个时候,TLB高速缓冲存储器里边会被要拷贝的页面数据占据的容不下任何别的其它数据。
在有了sendfile()零拷贝(zero-copy)之后,如果可能的话,通过使用直接存储器访问(Direct Memory Access)的硬件设备,数据从磁盘读取到操作系统高速缓冲存储器中会变得非常之迅速。而TLB高速缓冲存储器则被完整无缺的放在那里,没有充斥任何有关数据传输的文件。应用软件在使用sendfile()
primitive的时候会有很高的性能表现,这是因为系统呼叫没有直接的指向存储器,因此,就提高了传输数据的性能。通常来说,要被传输的数据都是从系统缓冲存储器中直接读取的,其间并没有进行上下文切换的操作,也没有垃圾数据占据高速缓冲存储器。因此,在服务器应用程序中使用sendfile()能够显著的减少对CPU的占用。
在我们的这个例子中取代带有mmap() 的read()不会让事情有什么显著的变化。然而,mmap系统呼叫的请求是从文件中(或者从其它的一些对象中)生成一些连接信息,这些都是从在虚拟内存中的文件描述符中指定的。试图从虚拟内存中读取数据会产生一些磁盘操作。因为系统会把含有映射内容的存储器直接的写入而不会调用read()以及缓存定位,所以我们能够消除磁盘读取操作。然而,这种操作会导致TLB高速缓冲存储器的过热,所以CPU在装载每个字节的传输时的负荷会稍微加大一些。
零拷贝的方法以及应用软件的开发
只要有可能,在任何时候,对性能要求非常苛刻的客户服务器应用程序的开发都会使用到零拷贝方法(The zero-copy approach)。设想一下,当我们需要在一个单独的服务器上运行超过一千个分散的拥有私人IP地址的Apache网络服务器时,就可以使用到Virtuozzo技术了。为了达到这个目的,我们每一秒钟都不得不在传输控制协议的层面上处理数以千计的请求来列出客户的请求并且把主机名页开列出来。如果没有经过最优化的处理和零拷贝(zero-copy)支持的话,这对于处理器来说将是一项繁重而且复杂的任务,甚至都超过了网络对它的负荷。零拷贝sendfile()的实现是基于9K-http-requests-per-second速率的,甚至在速度相对较慢的Pentium II350 MHz的处理器上也是一样。
然而,零拷贝(zero-copy) sendfile()并不是能解决所有问题的“万能药”。部分的,减少网络操作的数量,sendfile()系统呼叫应该和TCP/IP中的TCP_CORK选项在一起共同的使用。在我们以后的文章中将会讨论应用软件从此选项中获得的好处,以及讨论其它的一些相关问题。
阅读(2099) | 评论(0) | 转发(0) |