mips64高精度时钟引起ktime_get时间不准,导致饿狗故障原因分析【转】

简介:
重点关注关中断的情况。临时做了一个版本,在CPU 0上监控所有非0 CPU的时钟中断计数,检查他们在100ms内是否增加。如果否,则认为此
CPU关中断。另外,在高精度时钟中断处理函数hrtimer_interrupt以及时钟中断总入口打点,记录最长耗时。
关中断检测代码如下:
void check_timer_start(void)
{
int i = 1;
for(;i<32;++i)
cpu_timer_tick[i]=kstat_irqs_cpu(7, i);
}


void check_timer_end(void)
{
int i = 1;
int save_console_level = console_loglevel;
for(;i<32;++i){
/*should I dump only once?*/
if(cpu_flag_dump[i]==1)
continue;
if((kstat_irqs_cpu(7, i)>MIN_TICK_START)&&
(cpu_timer_tick[i]==kstat_irqs_cpu(7, i))){
 
console_loglevel = 4;
mips_send_nmi_single(i);
printk("send nmi to cpu[%d]\n",i);
cpu_flag_dump[i] = 1;
system_is_down = 1;
dump_max_data(); 
}
}
console_loglevel = save_console_level;
}
void check_dead_cpu(void)
{
if(system_is_down)
return;
if(time_after64(jiffies_64, last_tick_cpu0+sysctl_dead_time)){
        last_tick_cpu0=jiffies_64;
if(cpu0_checking){
cpu0_checking = 0;
check_timer_end();
}else{
   cpu0_checking = 1;
check_timer_start();
}
}
}
通常情况下,上述代码正常运行的前提是,CPU 0的时钟中断运作正常。实际上,如果CPU 0关中断超过一定时间,这种检测是否还可靠?首先
,CPU 0负责更新jiffies。根据关中断检测代码的逻辑,每隔100ms为一个周期,如果这段时间某非0的CPU的时钟中断计数不变,则认为此CPU
关中断。假设CPU 0的时钟中断关闭了一段时间,则这段时间内jiffies可能未更新,在下一次CPU 0的时钟中断到来时,会进行jiffies补偿。
那么是否可能存在时钟只过去了一个tick,而jiffies却跳变了100 tick的情况?也就是说,jiffies一次补偿达到了100 ticks?根据
tick_do_update_jiffies64的实现,jiffies进行补偿的本次中断距离上次中断之间,一定是发生了问题,不可能发生时间只经历了1个tick,
而却补偿超过1个tick。(注意32bit中需要用get_jiffies64()获取jiffies64)
当检测到有CPU关中断后,则往此CPU发送nmi中断,打印其回溯。
通过上述检测方式,抓到的典型现场如下:
一个core内的4个子线程全部挂死。其中一个死在中断入口,另外三个在用户态。


CPU 20:
errepc:0x806011a0
epc:0x773160
eirr:0x88
status:6000fff7 KX SX UX USER ERL EXL IE


CPU 21:
errepc:0x7719e4
epc:0x7585b4
eirr:0x0
status:6000fff5 KX SX UX USER ERL IE


CPU 22:
errepc:0xf64f6cc
epc:0x7585b4
eirr:0x0
status:6000fff5 KX SX UX USER ERL IE


CPU 23:
errepc:0x4cb474
epc:0x7585b4
eirr:0x0
status:6000fff5 KX SX UX USER ERL IE

CPU 20被nmi中断打断的地方,位于外部中断入口handle_int。
虽然跟故障无关,这里也顺带讲一下,xlp的handle_int是经过rollback_handle_int包装的。
这个是什么意思? xlp的idle是这么设计的:
LEAF(r4k_wait)
/* start of rollback region */
LONG_L  t0, TI_FLAGS($28)
nop
andi t0, _TIF_NEED_RESCHED
bnez t0, 1f
nop
wait
/* end of rollback region (the region size must be power of two) */
.set pop
1:
jr ra
END(r4k_wait)
考虑这种情况,如果cpu在wait前被中断,返回现场后,将继续在wait处等待下一个时钟周期到来,才可能再次有调度点检查。为了增强实时性
,在handle_int前包装一个rollback的检测:
rollback_handle_int
MFC0 k0, CP0_EPC
PTR_LA  k1, r4k_wait
ori k0, 0x1f/* 32 byte rollback region */
xori k0, 0x1f
bne k0, k1, 9f
MTC0 k0, CP0_EPC
9:
    NESTED(handle_int, PT_SIZE, sp)
这段代码检查如果cpu是在idle里被中断,则恢复现场时直接将pc指向r4k_wait的入口,进行调度点检测,这样可以避免之前说的wait延时。
再回到故障信息,status显示在用户态。这就有点说不过去了。按道理,既然是在外部中断被nmi打断的,那么status自然应该是在内核态而不
是用户态。打印出USER的条件是status的KSU为10,至于KSU是硬件还是内核代码我们这里暂且不管,继续往下分析。根据内核的中断流程:
a) SAVE_ALL保存现场
b) 置status寄存器的CU0位表示可以访问协处理器0。
在出问题时的status现场,CU0还是0,说明可能在SAVE_ALL过程中或者之前就被nmi打断了。
于是做了一个实验,写了一个用户态死循环程序,不断的给此进程发nmi ipi中断。回溯发现,系统有一定几率会出现被在handle_int里被nmi
打断的情况,并且此时status显示KSU为10,即用户态。由此推测,可能出现这种情况:
程序在用户态死循环
来了一个中断,刚进入口,系统来不及更新status的KSU为内核态
被nmi打断,显示此时在用户态

当然,上述的分析只是推测,对故障定位本身没有影响,只是提出在此以备将来参考。
其实抓到的现场,除了在handle_int入口外,还有在tlb refill入口处的现场。
因此问题就变成了,为什么会在handle_int停住,或者是否发生了中断嵌套?
于是在handle_int以及tlb refill添加统计计数,看cpu此时到底在做什么。

4.4 在中断入口打点

用一个每cpu变量来记录各个cpu的handle_int计数。
uasm_i_dmfc0(p, K0 C0_CONTEXT);
uasm_i_dsrl(p, K0, K0, 23);
UASM_i_LA(p, K1, cpu_int_count);
uasm_i_daddu(p, K1, K0, K1);
uasm_i_ld(p, K0, 0, K1);
uasm_i_daddiu(p, K0, K0, 1);
uasm_i_sd(p, K0, 0, K1);
实际上xlp有一个scratch寄存器可以保存每个cpu的tlb miss次数。
当故障复现时,发现handle_int次数没有变化。说明不是外部中断嵌套。
说明一下xlp的寻址流程。
在开启pagewalker和extent tlb时,不考虑cache,当给定一个虚拟地址vaddr,cpu首先会同时检查所有的tlb,如果没有匹配的条目,则引发
一个tlb miss,pagewalker将对应页表项填入random的一个tlb条目。通常情况下,在linux设计框架里,这个过程是不会发生二次异常的。如
果填入的是一个无效的条目,再次访址引发tlb load/modify/store异常,走page fault执行写时复制,按需分配等。
根据故障现场时的epc,显示是在用户态,那么有怀疑是用户态正在执行指令,发生tlb miss进入了pagewalker流程,但在pagewalker重填过程
中流水线halt住,导致不响应中断。从我们实验的结果来看,当系统出问题时,我们给此cpu发送一个nmi,此cpu的handle_int计数就开始增加
。因此pagewalker的硬件设计值得怀疑,与boardcom交流后,fae承认工程部已发现pagewalker的问题,并正在调试。
由于pagewalker对我们来说是一个黑盒,因此下面我们关闭pagewalker进行测试。

4.5 关中断超过100ms

继续用之前的检测代码,在关闭pagewalker的情况下测试。大约拷机进行了3天左右,系统检测到有个CPU关中断超过100ms。往此CPU发nmi时,
发现挂死的位置不定,有可能在update_process_times,double_rq_lock,hrtimer_interrupts里的红黑树操作等。
这几个现场基本都和hrtimer_interrupts相关,而且表象是hrtimer_interrupt耗时,因此我们在hrtimer_interrupts,7号中断里,添加计时
代码。
下面是抓到的现场:
NMI triggered on CPU 6
errepc : 0xffffffffc0699cfc
epc   : ffffffffc0699cfc rcu_check_callbacks+0x4/0x190
ra    : ffffffffc066b320 update_process_times+0x68/0x140
[<ffffffffc0699cfc>] rcu_check_callbacks+0x4/0x190 
[<ffffffffc066b320>] update_process_times+0x68/0x140 
[<ffffffffc0684a84>] tick_sched_timer+0x6c/0x198 
[<ffffffffc067bd1c>] __run_hrtimer.clone.33+0x7c/0x220 
[<ffffffffc067c648>] hrtimer_interrupt+0xf8/0x248 
[<ffffffffc06310f0>] c0_compare_interrupt+0x60/0xa8 
[<ffffffffc069475c>] handle_IRQ_event+0x94/0x238 
[<ffffffffc06949a8>] __do_IRQ+0xa8/0x230 
[<ffffffffc0629c10>] do_IRQ+0x48/0x50 
[<ffffffffc06197ac>] nlm_common_timer_interrupt+0x44/0xb8 
[<ffffffffc0600f00>] ret_from_irq+0x0/0x4 
[<ffffffffc060ea0c>] _spin_unlock_irq+0x24/0x58 
[<ffffffffc066f1a8>] SyS_rt_sigtimedwait+0x190/0x280 
[<ffffffffc06035a4>] handle_sys64+0x44/0x60


cpu[2]'s irq7 max time:8ms 
cpu[2]'s hrtimer_func callback max time:3413ms 
cpu[2]'s callback func:0xffffffffc067b8b8


cpu[7]'s irq7 max time:8ms 
cpu[7]'s hrtimer_func callback max time:3413ms 
cpu[7]'s callback func:0xffffffffc067b8b8


cpu[9]'s irq7 max time:17592186041002ms 
cpu[9]'s hrtimer_func callback max time:0ms 
cpu[9]'s callback func:0xffffffffc06771e0


cpu[10]'s irq7 max time:7ms 
cpu[10]'s hrtimer_func callback max time:3413ms 
cpu[10]'s callback func:0xffffffffc067b8b8


cpu[11]'s irq7 max time:3412ms 
cpu[11]'s hrtimer_func callback max time:0ms 
cpu[11]'s callback func:0xffffffffc06771e0
 
cpu[12]'s irq7 max time:6ms 
cpu[12]'s hrtimer_func callback max time:17592186041002ms 
cpu[12]'s callback func:0xffffffffc067b8b8


上面的irq7代表的是7号中断耗时,hrtimer_func callback代表的是单次hrtimer回调的执行耗时,callback func即此回调的函数地址。
打点记录耗时的函数用的是ktime_get,为什么会出现3413ms和17592186041002ms?我们在第4.7节分析。
在继续之前,先简单介绍一下高精度时钟的计时原理。

4.6 高精度时钟回顾

既然说到高精度时钟,就顺便整理一下高精度时钟从初始化到正常运行的流程,熟悉的同学可以跳过这一节。首先,在2.6.32内核里,高精度
时钟是放在硬中断里做的,这就带来一个问题,提高实时性的同时也导致中断时间过长,一把双刃剑。
先说明两个概念,clock event 和 clock source。前者用来触发下一次中断,不管是否配置CONFIG_HIGH_RES_TIMERS此框架都会在代码里存在
,一般是用cpu自带的counter和compare实现;后者是要求系统提供一个64位累加计数,具体的实现可能有多种,对于mips64而言,要么用
jiffies,要么用32位的counter,要么用64位的pic timer。

4.6.1 高精度模式的切换

系统刚初始化时,clock_device dev的 event_handler是一个空函数,后面被替换成tick_handle_periodic,接着主动触发一个时钟中断,在
tick_handle_periodic里临时完成一次时钟中断的工作,当运行到时钟软中断时,尝试进行真正的高精度模式切换。不管切换是否成功,都会
走原来的wheel定时器处理流程。作者注释说这种方法不好,每次都会在软中断里尝试切换到高精度时钟。要改进的话也有方案,例如
那么就可能有以下疑问:
1)满足什么条件时,系统会尝试切换到高精度模式?
2)为什么要在软中断里不停的尝试切换?
当clock source或者clock event变化(注册)时,将会给系统置位,并尝试在下一个时钟软中断里切换至高精度时钟,如果切换失败,则置位
等待下一次clock source或者clock event的变化。需要说明的是,clock event的变化是针对某个cpu的,clock source的变化则是针对所有
cpu。也就是说,如果某个cpu的clock event发生了变化,则只在此cpu的软中断里切换到高精度模式,反之clock source发生改变后,所有的
cpu都必须切换到高精度模式。如果确实有clock event或者clock source发生了变化(很有可能是模块注册了新的clock source),则进行下
一步检查,要求系统未使能no_hz模式,也就是说,不允许从no_hz模式切换到高精度模式。接下来是核心的判断,如果目前使用的clock 
source支持高精度模式,则可以切换到高精度模式。
关于高精度模式的切换我们就说到这里,现在假设系统已运行在高精度模式下。

4.6.2 ktime_get和hrtimer_interrupt

有两点需要注意:
A)时间获取用的是ktime_get,这是高精度定时器超时比较的基准;
B)判断红黑树上高精度timer是否超时的基准时间,是第一次进入到hrtimer_interrupt时,取的当前时刻。

先看ktime_get。 ktime_get设计的目的是获取是系统从启动到现在的时间。大体实现原理是根据墙上时钟xtime,根据wall_to_mono转换成单
调递增时间。
ktime_get在mips64 xlp上的实现,靠的是每cpu的32位counter寄存器。实现的原理是,每个cpu上counter寄存器,与打点时间做差算出偏移,
然后根据cpu频率换算出具体的纳秒值。
static inline s64 timekeeping_get_ns(void)
{
cycle_t cycle_now, cycle_delta;
struct clocksource *clock;


/* read clocksource: */
clock = timekeeper.clock;
cycle_now = clock->read(clock);


/* calculate the delta since the last update_wall_time: */
cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;


/* return delta convert to nanoseconds using ntp adjusted mult. */
return clocksource_cyc2ns(cycle_delta, timekeeper.mult,
 timekeeper.shift);
}
核心代码即上面的红字部分。cycle_now是调用ktime_get的cpu的counter值,clock->cycle_last是cpu 0负责每个tick更新。通过这个差值,
可以算出每个cpu上的当前时刻。这里存在的问题是什么?(分析在4.7)


再分析hrtimer_interrupt函数的实现,这里参考的内核版本是2.6.32.41。hrtimer_interrupt在某些时候,会打印hrtimer: interrupt took 
3456 ns的告警,我们来看看这是为什么。
在每次进入hrtimer_interrupt时,用ktime_get获取当前时间,摘下当前红黑树的最左节点,判断该节点的到期时间是否小于当前时刻,如果
是,则认为需要执行定时器回调,并取下一个最左节点进行超时比较;如果否,则退出遍历流程。要强调的是,刚进入hrtimer_interrupt时取
得的时间点,是判断所有timer是否超时的基准。假设有如下场景:
1)tick 4来了一个时钟中断,红黑树上最早到期的timer 1超时时间为tick 1,于是执行此timer的回调函数,执行长度为4个tick。
2)timer 1的回调结束后取下timer 2,发现超时时间为tick 3,小于tick 4,于是继续执行timer 2的回调,timer 2耗时5个tick。
3)timer 3的回调结束后取下timer 3,发现超时时间为tick 18,大于tick 4,因此退出循环。
4)取当前时间,得到tick 13,则设置下一次超时时间为距离当前(18-13)=5 tick后触发。示意图如下:
 







5)如果第3步的timer 3,超时时间为tick 9,则设置超时时间时会出错,如果连续出现这种状况3次,则打印告警。之所以会出现这种现象,
是因为timer过多,并且每个回调比较耗时。hrtimer_interrupt告警示意图如下:
 




4.7 超时数据分析与复现

有了4.6节的基础,再来看故障现场。
故障发生时,有两个现象值得注意:
A)系统曾经打印过hrtimer: interrupt took 3456 ns之类的告警;
B)4.5节的数据,关中断时间出现了3413ms和17592186041002ms。
A现象,说明应该出现过高精度时钟比较频繁,并且单个耗时的现象,观察跑流的单板现场,/proc/timer_list,控制面很多posix timer和
nanosleep timer。写了一个小程序,尽量模拟出高负载的高精度定时器同时处理的场景,即开启poxis timer,以及调用nanosleep。目的是让
其模拟出hrtimer_interrupt耗时过长的告警。最终的测试程序,启动了5000个线程,每个线程启动一个posix timer(RESTART性质,可以加重
hrtimer_interrupt单次处理的负担),以及nanosleep调用,大约3分钟左右就会复现一次故障。
关于B现象的这两个数据,一个是7号时钟中断的最大耗时,一个是高精度时钟的回调函数最大耗时。其中,17592186041002大的有点离谱,怀
疑是溢出。
尝试加一下他们的和:3413+17592186041002 = 0xFFFFFFFFFFF
感觉互相有关联,分析一个就够了。
我们先来分析3413的来历。
4.6.2的我们曾提到,ktime_get的实现最核心的一句为:
cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;
由于cycle_now取得是本地cpu的32 位counter值, clock->cycle_last是CPU 0负责更新,因此就带来同步的风险。
考虑如下状况:
如果CPU 1的counter比CPU 0的counter稍滞后一点。CPU 0的tick来临,并更新cycle_last,此时在CPU 1上调用了ktime_get,由于CPU 1的
counter滞后于CPU 0,此时cycle_now正好小于cycle_last,相减得到一个接近2^32的数值,从而得到的ktime_get值极大。一旦发生这种跳变
,则引起的关中断超时的现象都不足为奇了,因为高精度定时器处理的基准是ktime_get,如果取的时间过大,可能导致处理较多的timer,引
起耗时。另外,counter寄存器的频率大约是1.3G,32bits能表示的最大数为4*10^9,
 约为4*10^9/1.3*10^9 = 3.3s,与报错的数据也大体吻合。
      下图说明了窗口造成ktime_get异常的原因:
 


5 总结

按照4.7分析的结果,在hrtimer_interrupt添加验证代码,进一步说明是因为counter的窗口问题导致ktime_get跳变,引起高精度时钟处理时
间过长,再加上之前说的高精度时钟都在硬中断下完成,导致这个关中断时间过长的问题。
最初想到有如下解决方案:
1) 采用counter 64位寄存器作为时钟源;
1.1) __asm__ __volatile__("DMFUR $16, %0" :"=r"(tmp));
2) 去掉高精度时钟,采用jiffies做时钟源;
2.1) 去除CONFIG_HIGH_RES_TIMERS(会自动去除CONFIG_TICK_ONESHOT);
2.2) 去除CONFIG_CSRC_R4K时钟源,这样会自动选中jiffies时钟源。
3) 去掉高精度时钟,采用pic timer做时钟源。

但实际上,除了3,剩下的两种方案都有缺陷。
如果采用counter64做寄存器,虽然避免了32位相减溢出,但由于counter64是每cpu寄存器,仍然存在窗口风险;
如果只是把高精度时钟关掉,以jiffies为时钟源的话,会出现系统时间比实际时间慢的情况。具体原因我们下面讨论。
首先用户态取时间gettimeofday,而内核函数是do_gettimeofday:
void getnstimeofday(struct timespec *ts)
{
*ts = xtime;
nsecs = timekeeping_get_ns();
timespec_add_ns(ts, nsecs);
}
最终的时间是xtime加上当前时刻距离上次中断的偏移。
xtime的更新速度以HZ为基准,若在两个tick之间调用了gettimeofday,则timekeeping_get_ns可以算出当前时刻距上次中断过了多长时间(精
确到时钟源,而不是时钟tick),再加到xtime里去,得到精确的时间。
如果说用getnstimeofday获取的时间比实际的时间慢,根据上面的代码,有两种可能:
a) xtime比实际的小
b) timekeeping_get_ns比实际的小

a.1) xtime在update_wall_time里更新,
   offset = (clock->read(clock) - clock->cycle_last) & clock->mask;
   while (offset >= timekeeper.cycle_interval) {
       u64 nsecps = (u64)NSEC_PER_SEC << timekeeper.shift; 
       offset -= timekeeper.cycle_interval;
clock->cycle_last += timekeeper.cycle_interval;


       timekeeper.xtime_nsec += timekeeper.xtime_interval;
if (timekeeper.xtime_nsec >= nsecps) {
   timekeeper.xtime_nsec -= nsecps;
   xtime.tv_sec++;
   second_overflow();
}
   }
   xtime.tv_nsec = ((s64) timekeeper.xtime_nsec >> timekeeper.shift) + 1;
原理是根据当前时钟源的计数,减去上次时钟tick到来时的时钟源计数,根据这个差值,
换算成纳秒,然后依次进位累加到xtime的秒,以及剩下的加到纳秒单位上。
场景:
1)如果中断被关了一会,则带来的影响是,jiffies没有及时更新。
由于以jiffies为时钟源,linux不会对jiffies进行补偿,所以关中断对时钟系统来说是透明的。如果jiffies没有及时更新,此时又调用了
getnstimeofday,则xtime滞后了,得到的时间自然要少。
2)以jiffies为时钟源,就算没有关中断,但是每次时钟中断都有一定的耗时,由于tick_handle_periodic设置下一次时钟中断的时间,为相
对当前时刻加上tick_period
所以如果每次时钟中断耗时x, 则n次时钟中断后,将会落后标准时间n*x。
 根源还是jiffies不准确,在GSU上应该也有这个问题(GSU是jiffies),但由于GSU会从主控来同步时间,所以这个问题没有表现出来。


所以,最终的解决方案是,暂时关闭高精度时钟,并采用pic timer做时钟源,具体的修改即关闭CONFIG_HIGH_RES_TIMERS,并注册一个rating
高于r4k时钟源的pic timer时钟源即可。

















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5381859.html ,如需转载请自行联系原作者
相关文章
|
26天前
|
传感器 算法
温度对振弦传感器的影响取决于传感器的类型
**温度影响振弦传感器的精度,尤其是金属材料的传感器因热膨胀导致误差。石英传感器则具较好温度稳定性。补偿措施包括选用温度稳定的材料、实施温度补偿算法和采取隔热安装。**
HMI-40-【节能模式】平均油耗实现
昨天我们把中心仪表自检搞了,今天我们搞个简单的,左侧的平均油耗。
HMI-40-【节能模式】平均油耗实现
|
10月前
频率调优(调频)
频率调优(调频)
72 0
|
算法 异构计算
m通信系统中基于相关峰检测的信号定时同步算法的FPGA实现
m通信系统中基于相关峰检测的信号定时同步算法的FPGA实现
299 0
m通信系统中基于相关峰检测的信号定时同步算法的FPGA实现
|
传感器
智能振弦传感器频率值不稳定
基本概念 振弦传感器:(vibrating wire sensor)是以拉紧的金属钢弦作为敏感元件的谐振式传感器。当弦的长度确定之后,其固有振动频率的变化量即可表征钢弦所受拉力的大小。根据这一特性原理,即可通过一定的物理(机械)结构制作出测量不同种 类物理量的传感器(如:应变传感器、压力传感器、位移传感器等),从而实现被测物理量与频率值之间的一一对应关系,通过测量频率值变化量来计算出被测物理量 的改变量。 
智能振弦传感器频率值不稳定
|
传感器 缓存 算法
基于STM32的心率计(二)动态阈值算法获取心率值
基于STM32的心率计(二)动态阈值算法获取心率值
656 0
基于STM32的心率计(二)动态阈值算法获取心率值
|
传感器 缓存 算法
基于uFUN开发板的心率计(二)动态阈值算法获取心率值
基于uFUN开发板的心率计(二)动态阈值算法获取心率值
131 0
基于uFUN开发板的心率计(二)动态阈值算法获取心率值
【电压?电流?Mos管?cmos?都是什么?最终理解失败。。。】失败笔记(下)
【电压?电流?Mos管?cmos?都是什么?最终理解失败。。。】失败笔记
209 0
【电压?电流?Mos管?cmos?都是什么?最终理解失败。。。】失败笔记(下)
|
传感器 移动开发
使用标准信号检测 VM振弦采集模块测量精度(三)
VM 振弦采集模块自 SF3.51 版本开始,新增加了频率和温度的多项式修正功能。测量、计算完成后的频率值和温度值,经过一个 2 次多项式进行修正,最终更新到频率和温度寄存器。
使用标准信号检测 VM振弦采集模块测量精度(三)
|
传感器 移动开发 芯片
使用标准信号检测 VM振弦采集模块测量精度对比
真值是检测任何设备测量精度的基础条件,真值不能用信号发生器号称的误差来衡量、不能用电阻标称的阻值来衡量。获取真值最可靠的办法是使用比检测精度更高一个数量级的仪表去测量。例如:电阻的值必须要用 6 位半或者更高精度的仪表测量后才能确定真实的电阻到底是多少。信号发生器也必须用一个更高精度的频率测量设备检测后才可以使用。如果“真值” 是不可靠的,那么对任何设备的精度检测工作,都会是徒劳的。
使用标准信号检测 VM振弦采集模块测量精度对比