arm64では/proc/cpuinfoからCPUのモデル名を取得できない
Linuxにはシステムの様子を知ることができる仮想ファイルがいくつかあります。そのうちの一つが/proc/cpuinfoで、その名の通りCPUの情報が以下のように閲覧できます。
$ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 63 model name : Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz stepping : 2 microcode : 0x1 cpu MHz : 2294.686 cache size : 4096 KB physical id : 0 siblings : 1 core id : 0 cpu cores : 1 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon rep_good nopl cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data bogomips : 4589.37 clflush size : 64 cache_alignment : 64 address sizes : 46 bits physical, 48 bits virtual power management: (略、以下論理コアごと同じような出力が並ぶ)
「Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz」のようなCPUの名前を見たい場合にはmodel nameの行を見れば良いわけです。しかし、このmodel nameは常にあるとは限りません。先ほどの実行例はx86_64のものでしたが、arm64での実行例を見ていきましょう。
$ cat /proc/cpuinfo processor : 0 BogoMIPS : 108.00 Features : fp asimd evtstrm crc32 cpuid CPU implementer : 0x41 CPU architecture: 8 CPU variant : 0x0 CPU part : 0xd08 CPU revision : 3 (略、以下論理コアごと同じような出力が並ぶ)
先ほどと比べて情報量がかなり少なくなっており、model nameの行もないためCPUのモデル名が取得できなくなっています。
なぜこういった仕様になっているかは、Linuxカーネルのコード(下記参照)やパッチのディスカッションを見ることでわかります。
if (compat) seq_printf(m, "model name\t: ARMv8 Processor rev %d (%s)\n", MIDR_REVISION(midr), COMPAT_ELF_PLATFORM);
https://github.com/torvalds/linux/arch/arm64/kernel/cpuinfo.c#L193-L195
まとめると概ね以下の通りです:
- 32bitではarmでもmodel nameが取得できたが64bitではできなくなっている(そういうカーネルのコードになっている)
- arm64ではハードウェアのAPIからmodel nameを取得する術がないため上記実装になっている
lscpuではなぜarm64でもmodel nameが取得できるのか
lscpuというコマンドがあります。Ubuntuにはデフォルトで入っていそうですが、入っていないディストリビューションの場合にはutil-linuxをaptやらyumやらpacmanやらでインストールすると使えるはずです。
こちらのコマンドでもcat /proc/cpuinfo
同様にCPUの情報を閲覧することができます。ここでは本題のModel nameにgrepで絞った結果をお見せします。
$ lscpu | grep "Model name" Model name: Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
上記はx86_64での実行結果ですが、なんとlscpuではarm64の場合でもモデル名を取得することができます。
$ lscpu | grep "Model name" Model name: Cortex-A72
これもソースコードから紐解いていきましょう。
lscpu_context_init_paths(cxt); lscpu_read_cpulists(cxt); lscpu_read_cpuinfo(cxt); cxt->arch = lscpu_read_architecture(cxt); lscpu_read_archext(cxt); lscpu_read_vulnerabilities(cxt); lscpu_read_numas(cxt); lscpu_read_topology(cxt); lscpu_decode_arm(cxt);
https://github.com/util-linux/util-linux/sys-utils/lscpu.c#L1359-L1370
lscpu_read_cpuinfoという関数がある通り、lscpuでも基本は/proc/cpuinfoファイルの内容を参照しているようです。しかし、一番下のlscpu_decode_arm関数の中身が気になりますね。
part = parse_model_id(ct); if (part <= 0) goto done; for (j = 0; parts[j].id != -1; j++) { if (parts[j].id == part) { free(ct->modelname); ct->modelname = xstrdup(parts[j].name); break; } }
https://github.com/util-linux/util-linux/sys-utils/lscpu-arm.c#L366-L376
armの場合はlscpu_decode_arm関数から呼び出される上記の処理で、あらかじめなんらかの手段で得たモデルIDをモデル名に変換しているようです。以下のように変換テーブルを自前で管理していて味わい深いですね。
static const struct id_part arm_part[] = { { 0x810, "ARM810" }, { 0x920, "ARM920" }, { 0x922, "ARM922" }, { 0x926, "ARM926" }, ... { 0xd08, "Cortex-A72" }, ...
https://github.com/util-linux/util-linux/sys-utils/lscpu-arm.c#L25-L97
そして、変換元のModel IDはどこから取得しているかというと、これは/proc/cpuinfoにちゃんと含まれていました。「CPU part」というカラムが対応するそうです。冒頭に挙げたarm64の出力例では「0xd08」という値になっているので、変換テーブルを引いた結果、モデル名として「Cortex-A72」が得られるという仕組みです。
Goのプログラムで/proc/cpuinfoを解析してモデル名を取得している際にどうするか
さて、お手持ちのGo製のアプリケーションで、/proc/cpuinfoを解析してCPUのモデル名を取得しているプログラムはありませんか?私はあります。
先ほどご覧いただいたlscpuの実装をgoでも真似すればarm64の対応ができるのですが、あの大量のidとmodel nameの対応マップを自前で用意したり場合分けしたりするのは大変ですよね。ここは巨人の肩に乗っかっていきたいところです。
shirou/gopsutilという、システムの情報を取得する際に便利なgoのライブラリがあり、この中にCPU情報を取得できる関数cpu.Info()
があります。
この実装を読むと、lscpu同様にテーブルを用意し、armの場合にはモデルIDをモデル名に変換しているようです。
case "model", "CPU part": c.Model = value // if CPU is arm based, model name is found via model number. refer to: arch/arm64/kernel/cpuinfo.c if c.VendorID == "ARM" { if v, err := strconv.ParseUint(c.Model, 0, 16); err == nil { modelName, exist := armModelToModelName[v] if exist { c.ModelName = modelName } else { c.ModelName = "Undefined" } } }
https://github.com/shirou/gopsutil/cpu/cpu_linux.go#L247-L259
var armModelToModelName = map[uint64]string{ 0x810: "ARM810", 0x920: "ARM920", 0x922: "ARM922", 0x926: "ARM926", ...
https://github.com/shirou/gopsutil/cpu/cpu_linux.go#L21-L84
ついでにWindowsやFreeBSDなどの様々なOSの対応もできて嬉しいので、自前の実装をこういったメンテされているライブラリに移し替えるのを検討すると良いと思います。