ぼっちリーディング(1)

カーネル読んでみたいなーと思ったので読んでみる。対象は手元に転がっていたFreeBSD

$uname -a
FreeBSD foo.localdomain 8.0-RELEASE FreeBSD 8.0-RELEASE #0: Sat Nov 21 15:48:17 UTC 2009     root@almeida.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC  i386

まったく経験ないので、とりあえず簡単なお題を設定して読み解くスタイルで進めることにする。お題は、dmesgで表示されるCPU名はどうやってとってきているんだろう?に決定。CPU名ってのはこれのことね。

dmesg | grep -i cpu
CPU: Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz (2388.76-MHz 686-class CPU)
cpu0: <ACPI CPU> on acpi0
acpi_throttle0: <ACPI CPU Throttling> on cpu0

とりあえずmanでもみてみますかね。

$ man dmesg
DESCRIPTION
     The dmesg utility displays the contents of the system message buffer.  If
     the -M option is not specified, the buffer is read from the currently
     running kernel via the sysctl(3) interface. 

あー、sysctl(3)経由で値を取得してるみたいだ。ソースのぞいてみる。FreeBSDの場合、カーネルソースは/usr/src/sys以下にある。sys/sysctl.hをのぞいてみる。つらつらと眺めてみたが、それっぽいのを見つけられなかった。ちなみに実体は、kern/kern_sysctl.cっぽい。しばし考えて、printされてる文字列で検索かければいいんじゃね?と思いつく。

$ grep -R "CPU: " *
    :
dev/mly/mly.c:	                mly_printf(sc, "CPU: %s @ %dMHZ\n", 
i386/i386/identcpu.c:	printf("CPU: ");
ia64/ia64/machdep.c:	printf("CPU: %s (", model_name);
    :

なんかそれっぽいのがひっかかった。i386/i386/identcpu.cをのぞいてみる。printfの該当箇所は以下の関数。

void
printcpuinfo(void)
{
        u_int regs[4], i;
        char *brand;

        cpu_class = i386_cpus[cpu].cpu_class;
        printf("CPU: ");
        strncpy(cpu_model, i386_cpus[cpu].cpu_name, sizeof (cpu_model));

        /* Check for extended CPUID information and a processor name. */
        init_exthigh();
        if (cpu_exthigh >= 0x80000004) {
                brand = cpu_brand;
                for (i = 0x80000002; i < 0x80000005; i++) {
                        do_cpuid(i, regs);
                        memcpy(brand, regs, sizeof(regs));
                        brand += sizeof(regs);
                }
        }
                :

init_exthigh()が気になる感じ。中身はこんなん。

static void
init_exthigh(void)
{
        static int done = 0;
        u_int regs[4];

        if (done == 0) {
                if (cpu_high > 0 &&
                    (cpu_vendor_id == CPU_VENDOR_INTEL ||
                    cpu_vendor_id == CPU_VENDOR_AMD ||
                    cpu_vendor_id == CPU_VENDOR_TRANSMETA ||
                    cpu_vendor_id == CPU_VENDOR_CENTAUR ||
                    cpu_vendor_id == CPU_VENDOR_NSC)) {
                        do_cpuid(0x80000000, regs);
                        if (regs[0] >= 0x80000000)
                                cpu_exthigh = regs[0];
                }

                done = 1;
        }
}

regsはたぶんレジスタを表現してんだろーな。do_cpuidが怪しい感じですね。do_cpuidはsys/i386/include/cpufunc.hで定義されていてこんな感じ。

static __inline void
do_cpuid(u_int ax, u_int *p)
{
        __asm __volatile("cpuid"
                         : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
                         :  "0" (ax));
}

インラインアセンブラですな。cpuid命令とやらをつかっているらしい。簡単にいうとEAXレジスタの特定の値セットして、cpuid命令をcallすればcpuの名前とかがレジスタに格納されるらしい。これを使ってCPU情報とってきてるんだねー。init_exthighの中ではdo_cpuid(0x80000000, regs);の形で呼び出しているのでサポートする最大拡張機能番号の取得を行ってる。最大拡張機能番号っつーのは、EAXに設定できる最大の番号と思えばよい。Wikipediaによると0x80000008がいまのとこ最大。(Intel developers manualで要確認)。これらを踏まえてprintcpuinfoを見直すと、for (i = 0x80000002; i < 0x80000005; i++) { … } の中でEAXの値を0x80000002から0x80000004まで変更しながらcpuid命令を実行しながらプロセッサブランド文字列を取得しているのがわかる。んで、最終的に出力しているのは下記の部分。

        :
        /*
         * Replace cpu_model with cpu_brand minus leading spaces if
         * we have one.
         */
        brand = cpu_brand;
        while (*brand == ' ')
                ++brand;
        if (*brand != '\0')
                strcpy(cpu_model, brand);

        printf("%s (", cpu_model);
        switch(cpu_class) {
        case CPUCLASS_286:
                printf("286");
                break;
        case CPUCLASS_386:
                printf("386");
                break;
#if defined(I486_CPU)
        case CPUCLASS_486:
                printf("486");
                bzero_vector = i486_bzero;
                break;
#endif
#if defined(I586_CPU)
        case CPUCLASS_586:
                hw_clockrate = (tsc_freq + 5000) / 1000000;
                printf("%jd.%02d-MHz ",
                       (intmax_t)(tsc_freq + 4999) / 1000000,
                       (u_int)((tsc_freq + 4999) / 10000) % 100);
                printf("586");
                break;
#endif
#if defined(I686_CPU)  // 今回はこのCPUタイプ
        case CPUCLASS_686:
                hw_clockrate = (tsc_freq + 5000) / 1000000;
                printf("%jd.%02d-MHz ",
                       (intmax_t)(tsc_freq + 4999) / 1000000,
                       (u_int)((tsc_freq + 4999) / 10000) % 100);
                printf("686");
                break;
#endif
        default:
                printf("Unknown");      /* will panic below... */
        }
        printf("-class CPU)\n");
        :

cpu0: on acpi0とかacpi_throttle0: on cpu0はどこから出力してんのかわかんなかった。まー、とりあえずCPU情報取得するにはインラインアセンブラ使ってcpuid命令をcallすればいいというのがわかったのでよしとしよう。あと結局dmesgからどうやって読んでいるのかこれだけだとつながってないな。