メモ: 9front MP Kernel panics on KVM
MP対応な9frontカーネル様がKVM環境でブート時にパニックしたのでメモ。
ここでは手前でコンパイルして配置したpcfカーネルを利用。
pccpufカーネルだと引っかからないらしい。
実機試験はPlan 9の方のみ実施。こちらではpcfカーネルでもきちんと動くようだ (結局別のところでおかしくなったけど)
再現方法 (またはMP対応有効化)
通常、MP対応は無効になっているのでブート時にこれを有効にする必要がある。
例の如くplan9.iniに設定を突っ込めばいい。
9front向けplan9.iniのマニュアルによると以下のオプション行が必要とのこと。
既に記述はあるが、1 (=無効)になっているので0 (=有効)にする必要がある。
*nomp= A multiprocessor machine will enable all processors by default. Setting *nomp restricts the kernel to starting only one processor and using the traditional interrupt con- troller.
ブートコンフィグたるplan9.iniは9fat領域に納められている。
Plan 9ではここをマウントする時には以下のコマンドを使っていた。ところがこれは9frontでは存在しない。
term% 9fat:
そのかわりに9fsコマンドを使って/n/9fat/の位置にマウントする。
term% 9fs 9fat
この状態でKVMで起動すると以下のようにパニックする。
cpu0: 3193MHz Genuine Intel Atom (cpuid: AX 0x106C2 DX 0xFCBFBF9) .... panic: lapic clock 999812570 > cpu clock > 205454592
追っかけ
dumpstackなんてまるっきり読めないので, panicメッセージを手がかりに
落ちている場所をさがしてみる。以下の関数の155行目らしい。
/sys/src/9/pc/apic.c 129 /* 130 * use the i8253 clock to figure out our lapic timer rate. 131 */ 132 static void 133 lapictimerinit(void) 134 { 135 uvlong x, v, hz; 136 137 v = m->cpuhz/1000; 138 lapicw(LapicTDCR, LapicX1); 139 lapicw(LapicTIMER, ApicIMASK|LapicCLKIN|LapicONESHOT|(VectorPIC+IrqTIMER)); 140 141 if(lapictimer.hz == 0ULL){ 142 x = fastticks(&hz); 143 x += hz/10; 144 lapicw(LapicTICR, 0xffffffff); 145 do{ 146 v = fastticks(nil); 147 }while(v < x); 148 149 lapictimer.hz = (0xffffffffUL-lapicr(LapicTCCR))*10; 150 lapictimer.max = lapictimer.hz/HZ; 151 lapictimer.min = lapictimer.hz/(100*HZ); 152 153 if(lapictimer.hz > hz-(hz/10)){ 154 if(lapictimer.hz > hz+(hz/10)) 155 panic("lapic clock %lld > cpu clock > %lld\n", 156 lapictimer.hz, hz); 157 lapictimer.hz = hz; 158 } 159 lapictimer.div = hz/lapictimer.hz; 160 } 161 }
Local APIC Timerのクロックレートを調べているらしい。
手順としては以下の流れになっている。
- lapicタイマの初期値を0xffffffffに設定
- fastticks()を使って100msecの間ループ
- lapicタイマの現在値と初期値との差分計算、lapicタイマのクロックレートの計算
- hz (= cpu clock ? )と比べる
- 落ちる
改めてパニック時に出力されているlapictimer.hzとhzを眺めてみる
lapic clock = lapictimer.hz = 999812570 = 999MHz
cpu clock = hz = 205454592 = 205MHz
あきらかにCPUのクロックレートじゃないよね... 上に3192MHzって出てるし...
それもそのはずで、関数の上のコメントにもあるようにここでfastticks()を用いて
取得できるhzにはi8253、つまりPITのクロックレートが渡されるようだ。
i8253はだいたい200MHzなのでまさにそうなっている模様。
panic()の中身を信じるならばここにはcpuのクロックレートが入っているハズだが....
どうも単純に比較先を間違えているのか、何かクロック周りを間違えているのか。
あるいはKVM環境だとlapic clock周りで特殊な事情があるのでわ!?とも考えられる。
local apicのクロックレートの適正値とかよくわからない。ここらへんは実機で試して見ないとなんとも。
とりあえず比較対象をhzから、m->cpuhz(CPUのクロックレートが入っているはず)に
変えたところ、正常に起動するのを確認。
コードの変更はこんな感じ : https://github.com/enukane/9front-work/commit/79807778262f3f7ec174a4ca97f1c99a5138e010
果たして同じコードで実機動作するのか。
とりあえずこれでMSI周りを叩けるようになりましたとさ。
補足:fastticks() ?
pccpufカーネルだと引っかからないという当たりが腑に落ちないので少し中身を追いかけてみた。対象はfastticks()。
/sys/src/9/pc/devarch.c 1039 /* 1040 * return value and speed of timer set in arch->clockenable 1041 */ 1042 uvlong 1043 fastticks(uvlong *hz) 1044 { 1045 return (*arch->fastclock)(hz); 1046 }
通常、x86アーキテクチャにてfastclockはi8253のクロックレートを参照している。
これはMPカーネルでも同様で、archの中身は以下のようになっている。
59 PCArch archmp = {
60 .id= "_MP_",
61 .ident= identify,
62 .reset= mpshutdown,
63 .intrinit= mpinit,
64 .intrenable= mpintrenable,
65 .intron= lapicintron,
66 .introff= lapicintroff,
67 .fastclock= i8253read,
68 .timerset= lapictimerset,
69 };
i8253read()ではではクロックレートを引数hzのアドレスに突っ込んで返す。
これを用いて先ほどのhz≒200MHzを取得している。これが一般的なフロー。
ところが、場合によってはこの部分が書き換わる。
/sys/src/9/pc/archmp.c 71 static int 72 identify(void) 73 { 74 char *cp; 75 PCMP *pcmp; 76 uchar *p, sum; 77 ulong length; 78 79 if((cp = getconf("*nomp")) != nil && strtol(cp, 0, 0) != 0) 80 return 1; 81 82 /* 83 * Search for an MP configuration table. For now, 84 * don't accept the default configurations (physaddr == 0). 85 * Check for correct signature, calculate the checksum and, 86 * if correct, check the version. 87 * To do: check extended table checksum. 88 */ 89 if((_mp_ = mpsearch()) == 0 || _mp_->physaddr == 0) 90 return 1; 91 92 pcmp = KADDR(_mp_->physaddr); 93 if(memcmp(pcmp, "PCMP", 4)) 94 return 1; 95 96 length = pcmp->length; 97 sum = 0; 98 for(p = (uchar*)pcmp; length; length--) 99 sum += *p++; 100 101 if(sum || (pcmp->version != 1 && pcmp->version != 4)) 102 return 1; 103 104 if(cpuserver && m->havetsc) 105 archmp.fastclock = tscticks; 106 return 0; 107 }
上記104行目で、cpuserverかつTSCが有効な場合にtsticks()とかいう関数に置き換わる。
cpuserverはpccpufカーネルの時有効。
tscticks()は以下に挙げてあるように、CPUのクロックレートそのもので動作する。
/sys/src/9/pc/archmp.c 131 uvlong 132 tscticks(uvlong *hz) 133 { 134 if(hz != nil) 135 *hz = m->cpuhz; 136 137 cycles(&m->tscticks); /* Uses the rdtsc instruction */ 138 return m->tscticks; 139 }
これなら比較されても大丈夫だったね! ってかわざわざcpuserverに限定するのはどうしてなんだ...