ぼっちリーディング(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: