続・timecounter “TSC”

追記:2020年5月頃からTSCまわりのコードが大幅に改善され,ここで示している cpu_counter_serializing() などのコードは取り除かれています。

timecounter “TSC” の続き

CPUの周波数つまり struct cpu_info *ci における ci->ci_data.cpu_cc_freq がどこで設定されるかというと (src/sys/arch/)x86/x86/cpu.c の cpu_get_tsc_freq()

これは、x86_delay(100000) の前後で cpu_counter_serializing() を呼んで値の差をとって10倍している。

cpu_counter_serializing()は、x86/x86/tsc.c の一番最後にあって、

uint64_t
cpu_counter_serializing(void)
{
        if (tsc_good)
                return rdmsr(MSR_TSC);
        else
                return cpu_counter();
}

この時点では tsc_good は 0 (偽)なので cpu_counter() が呼ばれている。

cpu_counter() は amd64/amd64/cpufunc.S にあって、

ENTRY(cpu_counter)
        xorq    %rax, %rax
        rdtsc
        shlq    $32, %rdx
        orq     %rdx, %rax
        addq    CPUVAR(CC_SKEW), %rax
        ret
END(cpu_counter)

x86のアセンブラは良く知らないが、rdtsc命令はx86の伝統的な Time Stamp Counter (TSC) を取り出す命令そのものらしく、(互換性の関係上)32ビットずつに分かれてレジスタに収まっているのをシフトとORで64ビットの値にしているように見える。何故 CPUVAR(CC_SKEW) (→ %gs:CPU_INFO_CC_SKEW%gs:offsetof(struct cpu_info, ci_data.cpu_cc_skew) ?)を足してるのかはよくわからないが、cpuを跨ぐ場合以外は関係なさそう?

しかし、x86_delay(100000) の前後で取り出された具体的な値を表示させたところ、16,120,975,782 と 16,664,790,833 で、差は 543,815,051。10倍すると 5,438,150,510 となり、推定クロックおよそ 5.4GHz ということになる。もちろんおかしい

rdtsc 命令は(ハードの問題でないなら)これ以上疑いようがないので、あとは x86_delay(100000) が想定より長い(0.1秒のつもりが0.5秒以上かかっている)可能性くらいか。

x86_delay() の実体は x86/isa/clock.c の i8254_delay() のようだ。
(hyperv の場合は置き換わったりするようだが、今回は関係ない。)

つづく(かどうか不明)