StackThreads はスレッドがヒープにコンテキストを 保存し, 現在のフレーム(そのスレッドの現在の親)のすぐ下の フレームを再開する方法を提供する. 現在の親はそのスレッドが初めてブロックしたときの もともとの caller(creator), もしくはスレッドが一度 ブロックしたあとに, そのスレッドを再開する任意のスレッドである. StackThreads では, スレッドがブロックしたときすぐに現在の親を再開することを 言語のランタイムシステムに対して決して強制していないことに注意せよ. それはしばらくスピンするかもしれないし, ローカルまたはネットワークから他の仕事を探そうとするかもしれないし, 適当ならばごみ集めを起動するかもしれない. 親を再開する 以外の全ての行動はまったくライブラリのサポートを必要とせず, これが StackThreads がライブラリ中で親の再開をサポートしている 唯一の理由である.
スレッド P が f を fork し, 現在 f は P を再開したいと
思っているとしよう.
それはライブラリ関数の alloc_ctxt
を呼ことによって
適当なサイズのヒープフレームを確保し, その後, 実際のコンテキストスイッチを
起動するために, 確保されたコンテキストとともに, switch_to_parent
を
呼ぶ. switch_to_parent
は
ヒープフレームを f のコンテキストで満たし, f の現在の親を再開する.
f が後に再開されるときには, まるで switch_to_parent
が
正常に戻ってきたかのように制御が f に戻る.
コンテキストスイッチのコードはコンテキストに対し,
言語に特定の行動(例えばそのコンテキストへのポインタを
あとで再開するためにどこかへ保存しておくこと)を行なう必要が
あるため, 領域確保と実際のコンテキストスイッチは分離されている.
典型的なコンテキストスイッチの一連の流れで行なわれることは,
まずヒープフレームを確保し, そのフレームへのポインタを
言語コンストラクトを実装するためにどこかに保存し,
そして switch_to_parent
を呼び出す. このインタフェースは
言語実装者が, 一つの手続きの中で複数の中断があるときに,
コンテキストセーブのために同じメモリを再利用することも許している.
図3.2: P が f を fork し, f が自分をブロックするために switch_to_parent を呼び出したときの制御フローおよび スタックフレームの配置. 制御は the current position で 示される点にあり, 今我々は P にいる. switch_to_parent! の 中で局所変数, テンポラリ, f のパラメタをヒープにコピーし, 全ての callee-save register を保存する特別なハンドラの 中で callee-save register を獲得する. 点線は P を 再開する時の制御パスを示す.
さしあたり, 簡単のために(あとで緩めるが), f が
直接 switch_to_parent
を直接呼び出し, ゆえに
switch_to_parent
はブロックしたスレッドの
フレームが現在のフレームのすぐ下にあることを知っていると仮定しよう.
switch_to_parent
が f によって呼ばれた直後の
スタックフレームとコントロールフローが図3.2に図解されている.
太線は手続きの prologue と epilogue のコード列を示す.
後でまるでswitch_to_parent
が f にリターンしたかのように
f を 再開するためには, 我々は (1) f が switch_to_parent
を
呼んだ点(図中では )での f の状態を獲得, 保存し,
(2) P が f を呼び出した点(図中では )におけるスタック/フレーム
ポインタ, callee-save register の値とともに f の
リターンアドレス(図中では )に制御を移動しなければならない.
f の状態はフレーム内の局所変数と f の入力パラメタ,
callee-save register からなる. 局所変数を保存することは,
フレーム f 内の局所変数保存領域の最高と最低のアドレスを
必要とし, 入力パラメタについてはそのサイズを必要とする(その
オフセットはすべての手続きを通じて定数である). callee-save registerを
セーブすることはより複雑である. どのcallee-save registerを
switch_to_parent
が
破壊するのかがわからないため
,
において callee-save register を獲得する唯一の実行可能な
方法は実際に switch_to_parent
の epilogue コードを走らせ,
そこで callee-save register を獲得することである.
これは, switch_to_parent
の
epilogue を走り終わったあとに特別なハンドラルーチンに
ジャンプするように switch_to_parent
のリターンアドレスを
修正することで実現している. 特別なハンドラルーチンは
レジスタ使用慣例で定められた
全ての callee-save register を保存し, f の
epilogue コードにジャンプする.
その後 f の epilogue コードは f が使用した callee-save register を
リターンし, P に戻る. f のための callee-save register を
セーブし, Pを再開する制御パスは図3.2の点線によって
示されている. 制御パスは, f の手続きのボディの残りの部分が
スキップされていることを除けば
通常の制御パスと同等であることに注意せよ.
我々はこれまで switch_to_parent
が f から直接
呼ばれることを仮定してきた. この仮定を緩め,
switch_to_parent
が f から呼ばれている手続きから
間接的に呼び出されているような一般的な場合を考えてみよう.
StackThreads は言語実装者のために switch_to_parent
が
間接的に呼ばれることを許している.
もしそれがスレッドそのもののトップレベルからのみ呼びだし
可能であるならば, スレッドは常にスレッドが継続するかブロック
するかを決定するコード列を埋め込まなくてはならない.
そのような埋め込まれたコード列はときに不快なほどに
長くなることがあるため, 我々はスレッドが小さいオーバヘッドで
継続する頻繁に起こるケースのみを埋め込みにし,
異なるケースは分離した手続きにしたい
.
図3.3: F の現在の親を再開するときの制御パス(一般的なケース). f の直接の子(図ではb)を除くすべての親のリターンアドレスは 親の epilogue コードに再方向づけされる. b のリターンアドレスは 全ての callee-save register を保存する特別なハンドラへ 再方向づけされる.一般化した場合を図3.3に示す. P が f を fork したとし, f は関数 b を呼んだとし, b は最終的に f がブロック しなければならないという決定をしたとする. 後にまるで b が f へリターンしたかのように f を再開し,
switch_to_parent
から b までの計算をabortする. b 以外の全てのフレームはその caller の epilogue コードに 再方向づけされ, b はすべての callee-save register をセーブする ハンドラへ再方向づけされる.switch_to_parent
から P へのコントロールフローは図3.3の点線に示すように 呼び出しの連鎖の全ての epilogue コード列の連なりである.
Mitsubishi Research Institute,Inc.
Mon Feb 24 19:27:36 JST 1997