« 阳朔归来 | 返回首页 | 脏矩形演示 demo »

不太精准的时钟

目前,我们的游戏的同步是依赖机器时钟做预测的。这种做法的前提是,client 的机器和 server 的机器的时钟走速必须相同。这样,当我们把时刻校对了以后,可以不通过网络就可以知道时间信息。即,每个通过网络包传送过来的事件已经发生了多久。这只需要发送方为事件打上时间戳,然后接收方以自己的时候为准求出时间差即可。

我原以为,在 client 不作弊的情况下,只需要登陆校对一次时间即可。而我们的服务器组每台机器也只需要在互相握手的时候校对一次时间,然后各自依靠自己机器上的时钟就可以了。今天发生的事情让我对 PC 的时钟准确度产生了怀疑。

我们以一台 freebsd 系统的机器为基准对时服务器,其他机器连上去获取时间。对时服务的程序用 clock_gettime 获取纳秒级时间,折算成 0.05s 的最小单位供其他程序使用。今天实际测试,发现,这台对时服务器的时钟比我用的 PC (windows xp 平台) 大约每 20 分钟会快上 0.1s 。这误差也太大了点吧。

那一天岂不是会快上 6,7 秒?看来不能忽略时钟不一致的情况,保持半小时一次的对时是有必要的。

ps. 另外我怀疑是 freebsd 的问题,打算周一再仔细研究一下。


5月22日 补 周一来上班,跟同事讨论了一下。首先可能是自己的问题。我的 windows client 测试时间的时候用的函数是 clock() 。我错误理解了这个函数的含义。按标准 clock() 应该返回的是程序运行时间量,即当前进程占用系统时间的量。所以应该换成 windows 的 api GetTickCount 。不过实际测试,没有差别,或许 Windows 下定义不太一样。

而后,换了一台 freebsd 的服务器,时间基本正常了。

另外,Windows 下 QueryPerformanceCounter 是不可用的,这个根据 MSDN 的文档猜测,有可能是用 TSC 实现的。在多核和变频时代,TSC 几乎不可能取到准确的时间。

freebsd 上,使用 TSC 获取时间的优先级最低,现在一般使用 ACPI 获取时钟。在 Windows 上没有找到对应的手段。

TrackBack

链入链接:不太精准的时钟:

» [Timer]PC上8253计时器芯片精确频率到底是多少? from zyl910
很多书上说PC机的8253的计时器#0的输出频率是每秒18.2次(每隔55ms触发一次),但都说这个只是约值,精确值有很长一串小数。计算机应该是靠整数运算的,那些小数值应该只是换算成现实时间的结果。所以我想知道精确的频率,于是查找了大量的资料,结果发现都有一点出入: 1.许多书上所说的(计数器#0)精确值: [Read More]

Comments

RDTSC 读取不准确这个应该算是CPU的BUG,由TSC的计数方式,多核等影响。多核TSC的问题目前MS和AMD 通过软件补丁的方式来解决了。而SpeedStep以及CPU频率变化的影响Intel已经对TSC进行修正了。
RDTSC 毕竟是比较快速的读取方法,使用ACPI PM Timer 需要保护模式切换也会变成一个问题。
TSC 的计数方式。
(I)variant TSC 该方式下TSC是由CPU的频率决定,每个CPU Clock TSC 加1 Pentium M处理器 (family [06H], models [09H, 0DH]); Pentium 4以及志强处理器中(family [0FH], models [00H, 01H, or 02H]);和P6家族的处理器,TSC增加的频率都是会随着处理器的时钟变化而变化的

(II)Invariant TSC Pentium 4和志强处理器(family [0FH], models [03H and higher]); Core Solo and Core Duo处理器 (family [06H], model[0EH]); 志强处理器5100系列和Core 2 Duo处理器(family [06H], model [0FH]),TSC都不会随着SpeedStep的频率变化而变化

http://msdn2.microsoft.com/en-us/library/bb173458(VS.85).aspx
GetPerformanceCounter是基于TSC实现,不过操作系统做了额外的同步处理,可以很好处理多核和变频。使用的时候需要用SetThreadAffinityMask避免计时线程在cpu间切换。
ps.看来微软是打算把GetPerformance* API做为毫秒级别的计时器。

PC开机时无论CPU时钟还是RTC,都是所想到同一个晶振的,关机的BIOS时钟这里不讨论。

PC晶振的的固有频率误差一般小于500PPM(这是NTPD的假定值),我遇到的典型值有10、30、130PPM这样的。PC晶振频率还受机箱内温度影响而漂移,具体值尚不清楚,我估计为每摄氏度0.7PPM。知道了固有频率偏差和漂移就可以预测了时间偏差了。至于短期的OSC频率抖动,应该在一般网络对时的分辨率之下;晶振的老化则变化很慢,远低于温度带来的漂移。

我们的程序直接去读 rtc 是没有太多意义的,因为相比较而言用网络对时更方便且精确。

计算机(包括pc server和很多嵌入系统)一般都有一个rtc(real time clock) 可以读出比较精确的时间 一年也就差1两秒那种 但是那一般是一个外设 通过比较慢的总线访问 比如我搞的一个嵌入系统 rtc通过i2c总线访问 平均要1ms才能读一次数据 pc的我没具体测试过 但是在linux下输入hwclock就知道了 延时是人能感觉到的 所以系统日常记时一般是用中断源 OS都有定时的中断 每次中断都把内存里的系统时钟值做累加(也有的cpu有专门记时的寄存器 每若干时钟周期自动加一) 这种时钟的特点就是精度做短期记时还行 万分之几的样子 长时间积累就很够呛了 基本上rtc就是在开机的时候读一下 同步到系统时钟(linux下就是用hwclock --hctosys)
在一个一般的分时OS里 即使没有smp 想达到精确到ms的记时也是很困难的 rtc读取的时间延迟就很难控制 而且就算这个问题能解决得很完美 system call返回到进程被唤醒之间的时间 是由调度器决定的 在一个非实时OS里 这个时间是非常不确定的 如果这个系统里有多个线程而且都比较忙 或者正好发生了别的硬件中断 就可能产生一个远远长于1ms的延迟 所以就算时钟很精确 系统调用的结果也是不可依赖的 除非用dos 或者vxworks :-)

我曾用gettimeofday(redhat advanced server下),windows下timeGetTime(),结果出现很怪异的情况,客户端收到的两个包的时间间隔竟然下于Server下的两个包的发送时间.怀疑是和smp有关.但一直没找到证据!

利用 TSC 获取时间肯定会有问题的,因为 TSC 原本设计就不是为了提供时钟。

不过我觉得理论上从 i8254 获取时间,至少不会受 CPU 的影响,应该是准的了。

恩,这问题做MMO 基于物理仿真REALTIME同步时候就更厉害,能挣死人.
最后是的答案是这样: 想依靠timeGetTime, GetTickCount,GetPerformanceCount,rtsc 得到准确时钟方法是不存在的.
因为超线程CPU受负荷影响,多核,BIOS BUG等一系列因素.导致没有正常途径可以得到全世界PC都相同的时钟.某些情况下(HT),GetPerformanceCount错得更厉害.
不少论坛有讨论.

得到所有PC都准确得毫秒级别时钟是可能的. 但是驱动级的编程.NT CORE下 非administrator动态加载核心驱动也是不可能的.
所以,只能靠一系列补偿算法和一个能得到比较准确时钟的SERVER.

可能是Windows的问题
我的笔记本原来是Windows的系统,(长开)每天都要慢上几分钟。现在是Linux,ntpdate(&ntp)服务关掉了,每天都不会慢。FreeBSD我没试过,不知道情况会不会一样。我猜这可能是Windows对某种型号的主板的driver的兼容性问题。

那一天岂不是会快上 6,7 秒?

我强烈怀疑是算法搞错了

Post a comment

非这个主题相关的留言请到:留言本