既然本书中的大多数的例子都需要测量一个时间间隔,我们需要更仔细地介绍一下当前Unix系统所采用的记录时间的方法。下面的描述适用于本书中例子所使用的系统,也适用于大多数的Unix系统。[Leffler et al.1989]的3.4节和3.5节给出了另外的细节。
硬件按照一定的频率产生一个时钟中断。对于Sun SPARC和Intel 80386,时钟中断每10 ms产生一次。
应该注意到大多数的计算机使用一种无补偿的晶体振荡器来生成这些时钟中断。正如RFC 1305 [Mills 1992]的表7指出的,你不要想知道这种振荡器一天的偏差有多少。这就意味着几乎没有计算机能维持精确的时间(即,中断并不是精确地每10 ms发生一次)。一个0.01%的误差就会产生一个每天8.64秒的差错。为了得到更好的时间测量需要:(1)一个更好的振荡器;(2)一个外部的更精确的时间资源(如,全球定位卫星提供的时间资源);或者(3)通过因特网访问一个具有更精确时钟的系统。后者通过RFC 1305定义的网络时间协议实现,对该协议的描述超出了本书的范围。
Unix系统中引起时间差错的另一个公共的原因是10 ms的中断只是引起内核给一个记录时间的变量增1。如果内核丢失了一个中断(也就是说两个连续中断之间间隔10 ms对于内核来说太快了),时钟将失去10 ms。丢失这种类型的中断经常引起Unix系统丢失时间。
尽管时间中断近似于每10 ms到达一次,更新的系统,如SPARC,提供了一个更高精度的定时器来测量时间差异。通过NIT驱动程序,tcpdump(在附录A中描述)已经访问了这个更高精度的定时器。在SPARC上,这个定时器提供了微秒级的精度。用户进程也可以通过gettimeofday(2)函数来访问这个更高精度的定时器。
作者做了下面的试验。我们运行了一个程序,这个程序在一个循环里调用了10 000次gettimeofday函数,并将每次的返回值保存在一个数组中。在循环结束后,打印了9999个时间差。对于一个SPARC ELC,时间差的分布如图B-1所示。
图B-1 在SPARC ELC上调用gettimeofday 函数10 000次所需要的时间分布
在一个空闲的系统中,运行这个程序所花的时钟时间为0.38秒。根据这一点,我们可以说进程调用gettimeofday所花的时间大约37微秒。既然ELC的速度是21 MIPS(MIPS表示每秒100万指令),37微秒相应于大约800个指令。这些指令对于内核处理一个用户进程的调用、执行系统调用、复制8个字节的结果及返回给用户进程看起来是合理的(MIPS速度是不可靠的,很难测量当前系统的指令时间。我们试图做的只是得到一个粗略的估计来评价一下上面的值是否有意义)。
从这个简单的试验,我们可以说gettimeofday返回的值确实包含了微秒级的精度。
如果我们在SVR4/386上进行类似的测试,结果是不同的。这是因为很多386 Unix系统,如SVR4,只计数10 ms的时钟中断,而没有提供更高的精度。图B-2是运行在25 MHz 80386上的SVR4中9999个时间差的分布。
图B-2 在SVR4/386上调用gettimeofday函数 10000次所需要的时间
这些值是无意义的,因为时间差一般小于10 ms,都被认为是0了。在这些系统中,我们所能做的就是在一个空闲的系统上测量时钟时间,除以循环的次数。这个结果提供了一个上界,因为它包含了调用printf函数9999次的时间和将结果写入一个文件的时间(在SPARC的情况,图B-1,时间差没有包括printf的时间,因为所有10 000个值都是首先获得的,然后才打印结果)。在SVR4的时钟时间为3.15秒,每个系统调用消耗了315微秒。这个大约比SPARC慢8.5倍的系统调用时间看来是正确的。
BSD/386 1.0版提供了类似于SPARC的微秒级的精度。它读8253时钟寄存器,计算从上次时钟中断以来的微秒次数。调用gettimeofday的进程和内核模块,如BSD分组过滤器,可以使用这个精度。
和tcpdump联系起来,这些数字意味着我们可以相信在SPA RC和BSD/386系统上打印的毫秒和亚毫秒(submillisecond)的值。而在SVR4/386上,tcpdump打印的值总是10 ms的倍数。对于其他打印往返时间的程序,如ping(第7章)和traceroute(第8章),在SPARC和BSD/386系统上,我们可以相信它们输出的毫秒值,但在SVR4/386上,打印的值总是10的倍数。为了在LAN上测量某个ping的时间,在第7章中我们显示的时间是3ms,所以需要在SPARC和BSD/386系统上运行ping程序。
本书中的一些例子是运行在BSD/386 0.9.4版上,它类似于SVR4,只提供了10 ms的时钟精度。在我们显示这个系统的tcpdump输出时,只显示到小数点后面两位,因为这就是所提供的精度。