欢迎光临
我们一直在努力

Linux 到底能不能做实时?TSC 抖动测试10纳秒!

长期被认为"天生不适合做实时"的 Linux,在合理调试配置与核心隔离之后,也能获得接近裸机级的确定性。本文用一套可复现的 TSC 抖动测试,把这场吵了很多年的争论变成一条可以精确测量的频谱:在 AMD Ryzen 9 9950 的完全隔离核心上,1 小时、数千亿次采样中,最大抖动仅约 10 纳秒,超过 100 个时钟周期(约 20 纳秒)的抖动事件一次都没有。

一、这场争论本身,可能就问错了

实时系统圈子里,"Linux 到底能不能做实时操作系统",是一个吵了很多年的问题。

反对者的理由看起来相当充分:Linux 本质上是为吞吐量而生的通用操作系统,它拥有庞大的调度子系统、复杂的锁机制、深度的电源管理栈,以及无处不在的内核线程和延迟工作队列。即便打上 PREEMPT_RT 补丁、把内核中大部分临界区转化为可抢占代码,也常被看作"在一个天生不具备确定性的系统之上,强行施加插队机制"。

支持者同样有理有据:PREEMPT_RT 早已合入主线内核,约八成代码就位;高频交易、工业控制、机器人等领域已有大量成功部署,调度延迟稳定在几十微秒以内。

但这场争论,可能本身就问错了——实时性的本质,从来不是"能不能"的二选一

二、实时性不是"开关",而是一条"频谱"

任何操作系统,只要它管理并发、仲裁资源、响应中断,就必然会引入时间上的不确定性。Linux 的复杂度高于传统 RTOS,意味着同等条件下它的抖动上界确实更高——但这不等于"不能做实时",而是意味着"需要更多工程手段来逼近确定性"。

反过来看,传统 RTOS 一旦迁移到多核平台,同样要面对核间中断、全局锁、缓存一致性协议、核间负载均衡等复杂机制,其复杂度并不亚于一个精简配置的 Linux。把传统 RTOS 等同于"确定性的保证",其实是一种幸存者偏差。

世界上唯一能做到"绝对确定"的执行环境,只有裸机:没有调度器、没有中断框架、没有锁,代码就是指令序列本身,执行路径完全可预测。

所以,正确的提问方式不是"哪个系统能做实时",而是:经过特定的工程配置后,你的系统抖动能逼近裸机到什么程度?用什么样的方法,才能把这段距离精确地测出来?

三、从 nohz_full 到"完全隔离":把核心"隔离到绝"

Linux 社区其实早就给出过一个答案——nohz_full 核心隔离:当隔离核心上只有一个可运行任务时,内核停止向它发送周期性的时钟中断(tick),从而消除最大的周期性噪声源。

但这并不彻底。根据 Linux 内核官方文档(cpu-isolation.rst)与社区实践,nohz_full 隔离的核心仍会遭受至少五类"残留干扰":

  • 1Hz 残余 tick:housekeeping 核心仍会代表隔离核心执行每秒一次的远程调度统计,某些情况下仍会落在隔离核心上;
  • 硬件中断未隔离干净:网卡、NVMe 等设备中断默认由 irqbalance 分配,即便设置了 irq affinity,部分 managed IRQ 仍可能落在隔离核心上;
  • 核间中断(IPI):TLB 刷新、函数调用 IPI(smp_call_function)、调度迁移等机制会无条件中断目标核心——这是 nohz_full 从未解决的盲区;
  • RCU 回调噪声:RCU 状态机仍可能在隔离核心上产生干扰;
  • SMI(系统管理中断):来自 BIOS 层的"隐形"中断,对操作系统完全透明,任何内核参数都无法控制它。
  • 一句话概括:这就像给运动员清好了跑道,却仍有陌生人随时闯进来。

    望获OS 的做法,是把隔离"做绝":

  • 禁用系统中断:不再只是"尽量不分配",而是从根本上禁止隔离核心响应任何系统中断;
  • 禁用核间中断 IPI:阻断来自其它核心的 TLB flush、函数调用 IPI 等一切核间干扰;
  • 彻底清除残余 tick:消除 1Hz 远程调度 tick 的干扰;
  • BIOS 层面规避 SMI:通过配置限制深 C-state,降低 SMI 发生频率。
  • 处理之后,隔离核心上不再存在任何操作系统层面的异步干扰源——它表现得像裸机一样确定。这是一句可以验证的工程声明,而验证它,需要一把比传统工具更硬的"尺子"。

    四、为什么 cyclictest 不够?

    cyclictest 是 Linux 实时性评测的事实标准:让一个高优先级线程按固定间隔唤醒,再测量实际唤醒时间与预期时间的偏差,从而统计调度延迟。它简单、易用,但也有几个结构性局限:

  • 测的是调度行为的确定性,而非系统抖动的确定性。cyclictest 的核心路径是"定时器到期→硬中断→唤醒线程→调度器调度→线程执行测量",得到的是调度层面的延迟。而"调度延迟低"不等于"CPU 执行指令时不会被 SMI、IPI、缓存迁移等异步事件打断"——后者才是指令流的真实抖动,cyclictest 对此完全"无感"。
  • 结果偏乐观。cyclictest 使用 clock_nanosleep 唤醒,定时器到期路径直接在硬中断上下文执行;而真实应用通常经过线程化中断上下文,存在额外间接开销,因此测得的是"下界"而非真实延迟。
  • 黑盒测量,无法定位根因。它只告诉你延迟是多少,不告诉你为什么。借助 ftrace 等工具排查时,tracing 本身的开销又会掩盖真正的延迟源。
  • 周期性唤醒会漏测非周期性干扰。如果 SMI、stop_machine、模块加载恰好发生在两次唤醒之间,其影响可能被完全错过。
  • 五、TSC 抖动测试:用一把硬件级的"尺子"直接量抖动

    如果目标是评估"隔离核心上的执行环境是否像裸机一样确定",就要直接在指令执行层面测量时间不确定性。我们选择的基准,是 x86 处理器的 TSC(Time Stamp Counter,时间戳计数器)。

    TSC 是处理器内部的 64 位计数器,每个时钟周期自增一次,通过 RDTSC 指令读取。它天然具备四个非常适合当"标尺"的特性:

  • 恒定频率(Invariant TSC):不随 CPU 频率缩放和电源状态变化;
  • 跨核同步:多核处理器上,所有核心的 TSC 保持一致;
  • 极低读取开销:RDTSC 指令本身仅需几十个时钟周期;
  • 不可被软件干扰:计数过程是纯硬件的,不存在软件层面的干扰。
  • 方法本身非常朴素:在一个紧凑循环里,背靠背连续执行两次 RDTSC,计算差值。 两次读取之间的指令序列是固定的,如果执行环境绝对确定,这个差值应当纹丝不动;任何异步干扰——中断、IPI、SMI、调度抢占——都会让差值"跳"一下。于是,差值的波动,就成了环境抖动的直接度量。

    为了让这把"尺子"足够灵敏,测试代码做了几个刻意设计:

  • 循环内全部使用寄存器操作、不访问内存:消除内存访问延迟波动对测量的污染;
  • 不插入任何串行化指令(lfence/cpuid/rdtscp):传统实践会在两次读取间插入串行化指令以保证顺序,这里反其道而行——故意让 CPU 的乱序执行引擎把两次 RDTSC 尽量重叠执行,把差值压向硬件极限;
  • 只比较低 32 位:减少指令数量,缩短两次读取之间的测量窗口;
  • 连续运行 1 小时:覆盖足够大的统计样本量,捕捉罕见的长尾抖动;
  • 单独统计超过 100 个时钟周期的事件数:量化"异常抖动"的频率,而不只是看极值。
  • 测试代码还通过 CPUID 0x15 硬件接口读取 TSC 频率(处理器不支持时回退到 clock_gettime 标定法),保证结果能以纳秒为单位、跨机器可比。代价是两次 RDTSC 之间无数据依赖、架构上不保证读取顺序,极端情况下差值可能回绕成一个较大的值——但 1 小时内数千亿次采样中,这种事件的实际概率低到可以忽略。

    六、测试代码解读

    以下是我们的TSC抖动测试代码任何x86平台均可编译运行。

  • /* 
  •  * tsc.c – x86 TSC (Time Stamp Counter) 读取开销与抖动测量工具 
  •  * 
  •  * 功能: 
  •  *   在指定时长(RUN_SECONDS)内,反复执行"背靠背两次 rdtsc", 
  •  *   统计两次读取之间差值(diff,单位:TSC 周期数)的: 
  •  *     – 最小值 min  :理论上代表 rdtsc 指令本身的最小执行开销 
  •  *     – 最大值 max  :代表测量期间出现过的最坏抖动(中断、SMI、 
  •  *                     频率切换、缓存/流水线停顿等都可能导致) 
  •  *     – 大抖动计数 big:diff > 100 周期的次数,用于衡量系统实时性 
  •  * 
  •  * 用途:评估 x86 平台(尤其是工控/实时场景)的时钟读取开销与 
  •  *       系统抖动水平,常用于实时性(latency)摸底测试。 
  •  */  
  •   
  • #include <stdio.h>  
  • #include <stdint.h>  
  • #include <inttypes.h>  
  • #include <time.h>  
  •   
  • #define VERSION "2.5"          /* 程序版本号,打印时输出便于追溯 */  
  • #define RUN_SECONDS 3600       /* 测量总时长:3600 秒 = 1 小时 */  
  •   
  • /* 
  •  * rdtsc – 读取 CPU 的 TSC 寄存器(64 位时间戳计数器) 
  •  * 
  •  * RDTSC 指令把 TSC 的低 32 位放入 EAX,高 32 位放入 EDX。 
  •  * 这里用 "=a"/"=d" 约束直接绑定到 rax/rdx 寄存器, 
  •  * 再拼成完整的 64 位返回值。 
  •  * 
  •  * 注意:RDTSC 不是串行化指令,乱序引擎可能让它提前/延后执行。 
  •  * 本程序恰恰利用这一点来测"最小开销",详见 main() 中的注释。 
  •  */  
  • static inline uint64_t rdtsc(void)  
  • {  
  •     uint32_t lo, hi;  
  •     __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));  
  •     return ((uint64_t)hi << 32) | lo;  
  • }  
  •   
  • /* 
  •  * now_sec – 获取 CLOCK_MONOTONIC 单调时钟,转换为秒(double) 
  •  * 
  •  * 只用于 tsc_freq() 的回退测量路径(用墙上时间标定 TSC 频率), 
  •  * 不参与核心测量循环,因此精度要求不高。 
  •  */  
  • static double now_sec(void)  
  • {  
  •     struct timespec ts;  
  •     clock_gettime(CLOCK_MONOTONIC, &ts);  
  •     return ts.tv_sec + ts.tv_nsec / 1e9;  
  • }  
  •   
  • /* 
  •  * tsc_freq – 获取 TSC 频率(单位 Hz) 
  •  * 
  •  * 优先路径:CPUID.15H(Time Stamp Counter and Nominal Core 
  •  *   Crystal Clock Information Leaf) 
  •  *   – EAX = 分母 denominator 
  •  *   – EBX = 分子 numerator 
  •  *   – ECX = 晶体时钟频率(Hz) 
  •  *   TSC 频率 = ECX * EBX / EAX 
  •  *   这是 Intel SDM 规定的标准方法,Skylake 及以后的 CPU 支持。 
  •  *   若任一返回值为 0,说明该 CPU 不支持此叶子,走回退路径。 
  •  * 
  •  * 回退路径:软件测量法 
  •  *   记录起始 TSC 值 c0 和起始单调时钟 t0,忙等约 0.1 秒, 
  •  *   用 (TSC 增量) / (时间增量) 估算频率。 
  •  *   0.1 秒的窗口足以把计时误差压到可接受范围(<0.1%)。 
  •  */  
  • static double tsc_freq(void)  
  • {  
  •     uint32_t a, b, c, d;  
  •     /* "c"(0) 表示 ECX 输入为 0(选择子叶 0) */  
  •     __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d)  
  •              : "a"(0x15), "c"(0));  
  •     if (a && b && c)  
  •         return (double)c * b / a;  
  •   
  •     double t0 = now_sec();  
  •     uint64_t c0 = rdtsc();  
  •     double t1;  
  •     do {  
  •         t1 = now_sec();  
  •     } while (t1 – t0 < 0.1);  
  •     return (rdtsc() – c0) / (t1 – t0);  
  • }  
  •   
  • int main(void)  
  • {  
  •     double freq = tsc_freq();      /* TSC 频率 (Hz),用于把周期数换算成时间 */  
  •   
  •     /* 统计变量,全部强制驻留寄存器(见下方汇编约束 "+r"): 
  •      *   max:观测到的最大 diff(最坏抖动) 
  •      *   min:观测到的最小 diff(rdtsc 最小开销),初值取最大便于比较 
  •      *   big:diff > 100 周期的事件计数 
  •      *   end:结束时刻的 TSC 值 = 当前 TSC + 频率 * 秒数 */  
  •     uint64_t max = 0, big = 0, min = UINT64_MAX;  
  •     uint64_t end = rdtsc() + (uint64_t)(freq * RUN_SECONDS);  
  •   
  •     printf("TSC freq: %.3f MHz [v%s]\\n", freq / 1e6, VERSION);  
  •   
  •     /* 
  •      * ============ 核心测量循环(手写内联汇编) ============ 
  •      * 
  •      * 设计目标:测出"背靠背两次 rdtsc"的最小间隔,以此逼近 
  •      * rdtsc 指令的硬件极限开销。为此做出如下权衡: 
  •      * 
  •      * 1) 不插入任何串行化指令(lfence / cpuid / rdtscp)。 
  •      *    串行化会强制排空流水线,两次 rdtsc 之间至少隔几十 
  •      *    个周期,测到的是"串行化开销"而非 rdtsc 本身开销。 
  •      *    不加串行化时,乱序引擎可以把两次读取重叠发射, 
  •      *    diff 可压到接近硬件下限(现代 Intel 通常 20~30 周期)。 
  •      * 
  •      * 2) 全部统计量(max/min/big)和循环控制都在寄存器中 
  •      *    完成,循环体内零内存访问,避免缓存 miss / 栈访问 
  •      *    引入额外抖动,保证测到的 diff 纯粹反映 rdtsc 行为。 
  •      * 
  •      * 代价与风险: 
  •      *    两次 rdtsc 之间没有数据依赖,架构上不保证执行先后。 
  •      *    若乱序引擎让第二次 rdtsc 先于第一次执行,减法结果 
  •      *    会回绕成接近 2^32 的大正数,污染 max/big 统计。 
  •      *    实际硬件上 rdtsc 基本按程序序退休,发生概率极低, 
  •      *    且这种污染一眼就能从 max 值(巨大)识别出来。 
  •      * 
  •      * 寄存器分配(由编译器保证,约束见下方): 
  •      *   %[max] %[min] %[big] %[end] —— 通用寄存器("+r"/"r") 
  •      *   eax/edx —— rdtsc 的固定输出(低/高 32 位) 
  •      *   esi/ecx —— 暂存第一次/第二次 rdtsc 的低 32 位 
  •      *   r11     —— 临时计算 big 增量(0 或 1) 
  •      * 
  •      * 为什么比较低 32 位就够? 
  •      *   两次背靠背读取的间隔远小于 2^32 个周期(在 GHz 级 
  •      *   TSC 下也对应数秒),diff 不可能溢出 32 位,因此只 
  •      *   比较低 32 位即可,省去 64 位拼接,进一步压缩循环体。 
  •      */  
  •     __asm__ volatile(  
  •         "1:\\n\\t"                     /* 循环顶标签(局部标签,用 1b 回跳) */  
  •         "rdtsc\\n\\t"                  /* 第一次读 TSC:eax=低32位, edx=高32位 */  
  •         "movl %%eax, %%esi\\n\\t"      /* 保存第一次的低 32 位到 esi */  
  •         "rdtsc\\n\\t"                  /* 第二次读 TSC(背靠背,无串行化) */  
  •         "movl %%eax, %%ecx\\n\\t"      /* 保存第二次的低 32 位到 ecx */  
  •         "subl %%esi, %%ecx\\n\\t"      /* ecx = 第二次 – 第一次 = diff(32位) */  
  •   
  •         /* — 更新 max:无分支,用 cmov 避免条件跳转引起的流水线气泡 — */  
  •         "cmpq %[max], %%rcx\\n\\t"     /* 比较 diff 与当前 max */  
  •         "cmovaq %%rcx, %[max]\\n\\t"   /* diff > max 时更新 max(a = above,无符号) */  
  •   
  •         /* — 更新 min:同理,无分支 — */  
  •         "cmpq %[min], %%rcx\\n\\t"     /* 比较 diff 与当前 min */  
  •         "cmovbq %%rcx, %[min]\\n\\t"   /* diff < min 时更新 min(b = below,无符号) */  
  •   
  •         /* — 统计大抖动(diff > 100 周期):setcc 无分支实现 — 
  •          * 等价于:big += (diff > 100) ? 1 : 0 */  
  •         "xorl %%r11d, %%r11d\\n\\t"    /* r11 = 0 */  
  •         "cmpl $100, %%ecx\\n\\t"       /* 比较 diff 与阈值 100 */  
  •         "seta %%r11b\\n\\t"            /* diff > 100 则 r11b = 1,否则 0 */  
  •         "addq %%r11, %[big]\\n\\t"     /* 累加到 big */  
  •   
  •         /* — 循环终止判断:用第二次 rdtsc 的完整 64 位值 — 
  •          * 拼接 rdx:rax(高32:低32)得到完整 TSC,与结束值比较。 
  •          * 用完整 64 位是防止 1 小时测量内低 32 位回绕导致提前退出。 */  
  •         "shlq $32, %%rdx\\n\\t"        /* rdx 高 32 位左移到位 */  
  •         "orq %%rax, %%rdx\\n\\t"       /* 拼入低 32 位,rdx = 完整 TSC */  
  •         "cmpq %[end], %%rdx\\n\\t"     /* 与结束时刻比较 */  
  •         "jb 1b\\n\\t"                  /* 未到结束时间则跳回循环顶 */  
  •   
  •         /* 输出/输入约束:"+r" 表示读写(统计量),"r" 表示只读(end) */  
  •         : [max] "+r" (max), [min] "+r" (min), [big] "+r" (big)  
  •         : [end] "r" (end)  
  •         /* clobber 列表:告诉编译器这些寄存器被破坏,不要在里面存值 */  
  •         : "rax""rcx""rdx""rsi""r11""cc");  
  •   
  •     /* — 结果换算与输出:周期数 / 频率 = 秒,再换算成 ns/us — */  
  •     double ns = max / freq * 1e9;  
  •     double ns_min = min / freq * 1e9;  
  •     printf("min diff: %" PRIu64 " cycles (%.2f ns)\\n", min, ns_min);  
  •     printf("max diff: %" PRIu64 " cycles (%.2f ns, %.3f us)\\n",  
  •            max, ns, ns / 1e3);  
  •     printf("big jitter (>100 cycles): %" PRIu64 " events\\n", big);  
  •     return 0;  
  • }  
  • 代码解读

    1TSC频率获取(tsc_freq 函数):

    优先使用CPUID.15H叶节点直接读取处理器报告的TSC频率。这是Intel/AMD在较新处理器上提供的硬件接口,能给出精确的TSC频率比值。如果处理器不支持,则回退到标定法:用clock_gettime测量一段已知时间(0.1秒)内的TSC增量来计算频率。

    2核心测量循环(内联汇编):

    循环体由以下指令组成:

  •     "rdtsc\\n\\t"                 /* 第一次读 TSC:eax=低32位, edx=高32位 */  
  •     "movl %%eax, %%esi\\n\\t"     /* 保存第一次的低 32 位到 esi */  
  •     "rdtsc\\n\\t"                 /* 第二次读 TSC(背靠背,无串行化) */  
  •     "movl %%eax, %%ecx\\n\\t"     /* 保存第二次的低 32 位到 ecx */  
  •     "subl %%esi, %%ecx\\n\\t"     /* ecx = 第二次 – 第一次 = diff(32位) */  
  • 随后用cmp / cmov指令组更新maxmin统计,并检查diff是否超过100个cycle(若超过则big 计数器+1,用于统计"大抖动事件"次数)。最后用第二次rdtsc的完整64位值判断是否到达测试时长。

    3关键设计点:

  •      * 设计目标:测出"背靠背两次 rdtsc"的最小间隔,以此逼近 
  •      * rdtsc 指令的硬件极限开销。为此做出如下权衡: 
  •      * 
  •      * 1) 不插入任何串行化指令(lfence / cpuid / rdtscp)。 
  •      *    串行化会强制排空流水线,两次 rdtsc 之间至少隔几十 
  •      *    个周期,测到的是"串行化开销"而非 rdtsc 本身开销。 
  •      *    不加串行化时,乱序引擎可以把两次读取重叠发射, 
  •      *    diff 可压到接近硬件下限(现代 Intel 通常 20~30 周期)。 
  •      * 
  •      * 2) 全部统计量(max/min/big)和循环控制都在寄存器中 
  •      *    完成,循环体内零内存访问,避免缓存 miss / 栈访问 
  •      *    引入额外抖动,保证测到的 diff 纯粹反映 rdtsc 行为。 
  •      * 
  •      * 代价与风险: 
  •      *    两次 rdtsc 之间没有数据依赖,架构上不保证执行先后。 
  •      *    若乱序引擎让第二次 rdtsc 先于第一次执行,减法结果 
  •      *    会回绕成接近 2^32 的大正数,污染 max/big 统计。 
  •      *    实际硬件上 rdtsc 基本按程序序退休,发生概率极低, 
  •      *    且这种污染一眼就能从 max 值(巨大)识别出来。 
  • 、实测:1 小时、数千亿次采样,最大抖动约 10 纳秒

    测试环境为 AMD Ryzen 9 9950(TSC 频率约 5 GHz),在望获OS 完全隔离的核心上连续运行 1 小时:

  • 最大差值约 43 个时钟周期(约 10 纳秒)——1 小时内最大抖动;
  • 超过 100 个时钟周期(约 20 纳秒)的"大抖动"事件:0 次。
  • 操作系统的确定性不是"有和无"的问题,而是一条连续的频谱——你的隔离做到什么程度,抖动就降到什么程度。

    、10 纳秒的确定性,意味着什么?

    对绝大多数实时控制场景来说,10 纳秒级的确定性几乎是"奢侈"的:微秒级的伺服控制环、高频交易的下单链路、精密测量仪器,它们的控制周期通常在微秒到毫秒级,抖动裕量高出需求两到三个数量级。

    更关键的是,这套 TSC 抖动测试方法是操作系统无关的——只要是 x86 平台,无论 Linux、RT-Linux、传统 RTOS 还是裸机,都能运行同一套代码、产出完全可比的指标。这也让它成为横评"软件确定性"的一把客观标尺。

    、结语:确定性,是工程能力

    回到开头那个吵了很多年的问题:Linux 能不能做实时操作系统?答案是——。但前提是:你的隔离做到了什么程度,你的测试测到了什么层面。

    望获OS 的实践证明:通过完全隔离一个核心、禁用其系统中断与核间中断,完全可以在操作系统环境下获得接近裸机级的确定性。我们想做的,不只是做出一款"能实时"的操作系统,更是让"国产芯片 + 国产实时操作系统"这条路线,也能把确定性做到极致。

    相关镜像和测试代码即将在望获OS v3.0 版本中发布,欢迎关注我们,后续还会分享更多的技术干货

    附:面向大众的"术语小课堂"

  • 实时操作系统(RTOS):必须在限定时间内完成响应的操作系统,常见于工业控制、医疗设备、航空航天等对时间敏感的领域。
  • PREEMPT_RT:Linux 内核的实时化补丁,已随主线内核发布,通过让内核临界区可抢占来改善调度延迟。
  • TSC(时间戳计数器):处理器内部的时间戳计数器,每个时钟周期自增一次,是 x86 平台上最准的"秒表"之一。
  • tick:操作系统的心跳节拍,内核靠它来指挥调度;心跳越频繁,对隔离核心的干扰也越多。
  • nohz_full:Linux 提供的一种核心隔离机制,让隔离核心停止周期性心跳。
  • IPI(核间中断):一颗核心请求另一颗核心"搭把手"的信号,是隔离核心最难防的干扰源之一。
  • SMI(系统管理中断):来自 BIOS 层的隐蔽中断,操作系统看不见也管不了,几乎无法用软件消除。
  • cyclictest:Linux 实时性评测的"行业标准"工具,但测的是调度延迟,对指令执行层面的抖动并不敏感。
  • https://www.bilibili.com/video/BV1QwtP6vEZp/?spm_id_from=333.1387.homepage.video_card.click

    赞(0)
    未经允许不得转载:171主机测评 » Linux 到底能不能做实时?TSC 抖动测试10纳秒!
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址