10/25 (土)
[CPU] IA-64/Linux カーネルのスタックレジスタエンジン(RSE)の切り替え
IA-64/Linux カーネルの中からユーザプログラムのスタックレジスタの値を参照したくなって ガリガリとがんばって調査する。 が、いろいろと七面倒だということが分かる。
IA-64 は整数レジスタを 128 本持っていて、 そのうち r32-r128 のレジスタはメモリとの間で自動的にロード・セーブされるレジスタスタックになっている。 書き込まれるメモリはレジスタ・スタック・エンジン(RSE)のバッキング・ストアと呼ばれ、 現在の r32 番のレジスタを書き込むべきバッキングストアの位置が ar.bsp レジスタに記憶され、 バッキングストアに書き出されていない最後のアドレス位置を ar.bspstore レジスタが記録している。 通常は ar.bsp > ar.bspstore で、 全てのスタックレジスタがメモリに書き出されると ar.bsp == ar.bspstore になる。
IA-64/Linux のカーネルはユーザープログラムの中で割り込みが起こった場合、
ユーザスレッドのバッキングストアからカーネルスレッドバッキングストアへ変更する。
これはカーネル内で ar.bspstore レジスタを書き換えることで実現する。
この時、
ユーザランドで使っていたスタックレジスタのうちユーザバッキングストアを全て書き出してから切り替えればよいのだが、
実際はかなりの本数のレジスタスタックが CPU 内に残ったままで ar.bspstore が書き換えられる。
結果としてユーザランドで使用していたレジスタの内容が、
ユーザのバッキングストアとカーネルのバッキングストアに分かれて記録されることになる。
RSE がどこまでスタックレジスタをバッキングストアに書き出していたか(ar.bspstore)は、
非決定的にしか決まらない。
![]() |
もう一つ面倒なことがある。 IA-64 の整数レジスタは 64 ビットだが、おのおのに NaT ビットと呼ばれる「内容無効」を示す特殊ビットを持っている。 そのため概念的には IA-64 の整数レジスタは 65 ビット長なのだ。
65ビット長のスタックレジスタをバッキングストアに書き出す場合、
当然 65 ビット単位で書き出す訳にはいかないので、
スタックレジスタを 63 本メモリに書き出すと、次の1本は NaT ビットを 63 ビット集めた
NaT コレクションと呼ばれる8バイト長のデータを書き出す。
この NaT コレクションがバッキングストアに書き込まれるために、
「バッキングストアに書き出されていないレジスタの本数は (ar.bsp - ar.bspstore)/8
ね」
という単純計算ができなくなっている。
実際に NaT コレクションが挿入される位置はバッキングストアのアドレスによって機械的に決まる。
((bsp / 8) % 64 == 63)
が成立する位置が NaT コレクションがつく。
ところ IA-64/Linux はレジスタスタック・バッキングストアをユーザ側からカーネル側に切り替える際に、
ar.bspstore のカーネル側の用意したバッキングストアの最低位アドレスに合わせてしまう。
そのためユーザー側にいた時と ar.bspstore % (8 * 64)
が一致しない。
そのため Nat コレクションが挟み込まれる位置が切り替え前と切り替え後でずれてしまうことになる。
結局、以下のようなコードで処理できると思われるのだが、 本当にこれでいいかはいまいち自信がない。
struct pt_regs * regs; unsigned long sof, ndirty, *bsp; /* * regs->ar_bspstore は元コンテキストの ar.bspstore を指している。 * ここから regs->>loadrs >> 16 だけ上に ar.bsp が設定されている。 */ ndirty = (regs->loadrs >> 16); bsp = (unsigned long *)(regs->ar_bspstore + ndirty); /* * ただし Linux カーネルの割り込みハンドラ内の ar.bsp は、 * cover 命令を実行して、元コンテキストの現在のスタックフレーム * (alloc 命令で sof 指定) した値が加算されている。 * sof 分減らした値が ar.bsp になる。 */ sof = (regs->cr_ifs) & 0x7f; bsp = (unsigned long)ia64_rse_skip_regs(bsp, -sof); unsigned long *regN_bsp, regN_value; /* * スタックレジスタ N 番は bsp から N - 32 スロット+δ離れた位置を bsp とする */ regN_bsp = ia64_rse_skip_regs(bsp, N - 32); if ((unsigned long)regN_bsp >= regs->ar_bspstore) { /* * ユーザ側のバッキングストアに保存されなかった */ unsigned long diff, *krbs; /* スタックレジスタを強制的にフラッシュする。 */ asm volatile ("flushrs" : : : "memory" ); /* カーネルのバッキングストアの開始位置を計算 (task_struct に続くアドレス) */ krbs = (unsigned long *) current + IA64_RBS_OFFSET/8; /* bspstore と N 番のレジスタの bsp の距離を計算 */ diff = ia64_rse_num_regs((unsigned long*)regs->ar_bspstore, regN_bsp); /* krbs から diff だけ離れた位置に N 番のレジスタが格納されている。*/ regN_value = *ia64_rse_skip_regs(krbs, diff); } else { /* * ユーザ側のバッキングストアに保存されている */ if (copy_from_user(®N_value, regN_bsp, sizeof(unsgined long))) { return -EFAULT; } }
レジスタの値だけではなく NaT ビットまで求めるためには、 処理がさらに複雑に…
追記:11月2日
駄目じゃん。
regs->loadrs
が保持しているのは、
バッキングストア上のワード数ではなくて
スタックレジスタ上のレジスタ数なので
struct pt_regs * regs; unsigned long sof, ndirty, *bsp; /* * regs->ar_bspstore は元コンテキストの ar.bspstore を指している。 * ここから regs->>loadrs >> 16 だけ上に ar.bsp が設定されている。 */ ndirty = (regs->loadrs >> 19); bsp = (unsigned long *)ia64_rse_skip_regs((unsigned long *)regs->ar_bspstore, ndirty);
でないと計算できないよ。
レジスタスタック周りは本当に鬼門だよな…
10/9 (金)
[Prog] 非同期コンパイル
Java の JIT コンパイルや Transit のようにホストマシンとは異なるアーキテクチャを動かす バイナリエミュレータが JIT コンパイラを導入する場合、
- 対象アーキテクチャのバイナリをいったんホスト変換してから実行する。
- 最初は対象アーキテクチャのバイナリをインタプリータで実行するが、 一定の条件が揃うとインタプリータを止めてコンパイルを走らせる。 コンパイル後は翻訳されたホストコードで実行する。
- 2.に近いがコンパイラは別スレッドで走らせ、インタプリータ実行を続行する。
1. と 2. はコンパイルが実行と同期して動いており、 3. はコンパイラが非同期に動作する。
CPUのコアに余裕がある場合、 実行スレッドとコンパイラスレッドを別々のコアに割り当てることができるので 3. の非同期方式はは 2. の同期方式よりも原理的に高速になるはず…。
なのに開発中のJITコンパイラはなぜか 2. > 3. になる。
呪われているのか orz
10/3 (金)
Google のエラー画面を見る
Google のエラー画面には時々遭遇する
(2005年6月11日、
2005年11月17日)が、
こういう確認画面にあったのははじめてナリ。
スパイウェアの有無は不明だが、
再アクセス後に同じメッセージは見ていない。