backupbackup探求PDP-11/40用のm40.sもしくはPDP-11/45用のm45.sで定義されているbackupは謎につつまれている。Lions本からピックアップまず、trap.cの2812行目セグメンテーション例外の処理でbackup()を呼び出している。引数はu.u_ar0すなわち、context中に格納されているr0のアドレスのみである。backupはC言語での名前で、アセンブラでは_backupであることに注意しよう。1047行目からのbackupは_backupのサブルーチンである。まぎらわしいのだが、文句を言っても始まらないので先に進もう。Lions本の記述は第12章トラップとシステムコールの「ユーザーモードのトラップ」の項(訳書352ページ)にある。2810行目あたりから見ると「ユーザーモードスタック領域を拡張するためにオペレーティングシステムの手助けが必要である場合を表している。アセンブラルーチンの``backup''はトラップをひきおこした命令が実行される前の状況を再構築するために使われる」とある。直後には「``backup''プロシージャは自明ではなく、その理解のためはPDP11アーキテクチャの多様な側面を慎重に考慮する必要がある。「PDP11/40のためのコメントにあるように、プロセッサーがすべての可能性を解明するのに十分な情報を残してくれないので、``backup''はかならずしもうまくいかないかもしれない」とつづく。backupは、m40.sの長大な領域(Lions本では1008行目から1240行目までの)を占めている。コメントだけひろってみると、「難しい部分」「11/40にないssr2レジスターをシミュレートする」という部分が目につく。あとのほうの「SR2はそれぞれの命令のフェッチ開始時に16bit仮想アドレスをロードする」と説明されている。Lions本の説明は簡単すぎてよくわからない。なんのこっちゃv6はメモリの使用量を極力減らす戦略をとっている。そのひとつとして、ユーザーのスタック領域はあらかじめすべてを物理メモリに割り当てるのではなく、必要が生じた時のみ確保して割り当てる方式をとっている。実行中のユーザープログラムがスタックをアクセスしたが割り当てられていなかった場合、実行を中断してtrapルーチンに制御が移る。trapルーチンがこの状況を検出するとメモリを割り当てをおこない、ユーザープログラムの実行を再開する。PDP-11/45の場合は実行の再開に必要な情報をマシンが保持しており、backup()ルーチンによりこの情報を取得できる。ところが、PDP-11/40の場合はかならずしもすべてのデータが揃わない。そこで、さまざまな情報から推測しよう、というのがm40.sのbackup()の趣旨である。難しくないほうからこのへんの理解に必要な情報のうちLions本に欠けている部分についてはメモリ管理機構の項に説明をおぎなった。これを参照しながら、てはじめに、PDP-11/45用のm45.sを見ることにしよう。こちらはわずか26行におさまっており、「難しい部分」が存在しない。pcこれはm40.sの_backup先頭の7命令をのぞいたものとおなじである。1:は簡単なサブルーチンなのでこれから見ていこう。reglocはsystm.h(0237)で定義されており、実体はtrap.c(2677)にある。その中身はreg.hで定義する配列で、コンテキスト中に目当てのレジスタが保存されているオフセットが入っている。適当なレジスタについてreglocの値を引いて2倍して(ワードなので)r0の保存されているアドレスに加算すると、目的のアドレスが得られる。引数として使われているr1の下3bitがレジスタ番号である。目当てのレジスタの保存内容からr1の上5bitの値を引いて処理は終了する。uに保存されているレジスタについて以下の操作をおこなっていることがわかる。ssr+2でレジスタを補正ssr+3でレジスタを補正ssr+4でpcを上書き要するにssr+2とssr+3には、レジスタ補正用のデータがあるということになる。ここまでわかったところで、m40の_backup先頭の共通しない部分をみてみる。r2をpush/popしているのは_backupの返り値につかわれているからである。backupはr2を返すが、これはssr+2とssr+3に代入される。jflgが非0ならあとの処理はスキップする。この場合、補正はおこなわれない。2f注意したいのはbackup()の引数をssr+2に保存しているところである。使えるメモリアドレスというだけのことで、ssr+2を使う必然性はない。ここで補正につかわれているssrについてみてみよう。m40.sではSSR0がssrに、SSR2がssr+4に保存されている。m45.sではさらにSSR1がssr+2に保存されている。backupはSSR1に相当するデータを計算しているのである。11/40のssr2はssr1の誤りである。これでようやく話の辻褄があった。m40.sではSSR1相当の部分にデータを格納している。いうことはそこには普通のメモリー(に見えるもの)がある、ということでよいだろう。適切な補正値を推測する。これを理解するには、PDP-11のオペコードの体系を頭に入れておく必要がある。LIST"というのがあって、そちらを見るとよりわかりやすい。bflg内部用のフラグ。.=.+1r2はSSR1に相当する補正値を蓄積するのに使われる。下3bitがレジスター番号、上5bitが補正値であることに留意しよう。下請けのsetreg(1197)は引数r0からr2(の下8bit)を計算する。その際、r0のbit0-2をレジスター、bit3-5をアドレッシングモードとみなす。-2ただし、spおよびpcの自動加減の場合は補正値を倍にする。このへんはbit1もbit2も0でなければ、という間接的な処理になっていてわかりにくい。アセンブラーでこういう条件判断を記述するのは無理があるかもしれない。0,0,10,20,-10,-20,0,0下請けのfetchは引数および結果がr0である。ユーザー空間の(r0)にあるワードを読んでr0に返す。失敗した場合はr2をクリアしてr0に-1を返す。pcfetchを呼ぶときのr0にはSSR2から読み出した値が入っている。実行が失敗した命令の(ユーザー空間での)アドレスである。この命令のオペコードの8進数表記XXxxxxのXXによりt00からt17に分岐する。Xの値によりu0からu7に分岐する。u7命令を仕分けして処理に振り分ける。コメントでおおよその見当がつく。asl:0063xx。aslb:1063xx。sxt:0067xx。ここまではu5に飛ばされる。mtpi:0066xx。mtpd:1066xx。106[47]xxは未使用。mark:0064xxが無視されているのは要検討。1078行目では命令を0026xxに書き換えて、ダブルオペランド処理をおこなうmovの処理ルーチンに渡す。ここの26は(sp)+に相当。命令を変換して処理をまとめるのは賢い方法かもしれないがずいぶんな落とし穴。1083行目も同様で、命令を65xx46に書き換えて、mov処理ルーチンに渡す。46は-(sp)相当。pc0004xxxはjsrで、10x4[0-3]xxはemt、10x4[4-7]xxはtrapである。bflg07xxxxにある拡張命令。075040-076777は未使用。bit5-0がデスティネーションの場合(xxSSDD)。pc,setregSSとDDについて補正値を概算した結果がr2に入っている。r1はもとの命令のまま。1fソースの補正値が0ならソースを取ってくる必要がないのでそのまま終了。1fレジスタモードであればfaultが起きるはずはない。t17174370は上下バイトの上5bitに対するマスク。ソースとデスティネーションのレジスタが同じだとまずい。何でt17なのか、という話もあるが要するにあきらめる。cycleを再現するのは、プロセッサの内部状態を復元するためか。(r0),r0u構造体からソースレジスタの値を取ってくる。ssr+2を使っているのでびっくりするが、単に仮置きされているだけである。r0補正値がプラスならデクリメント、2のbitが立っていればさらにデクリメント。X(R)のXは命令の次のアドレスに入っている。間接アドレッシングモードの場合は繰り返し適用してオペランドの値をr0に得る。ここでせっかく取得した値は捨てられてしまう。実際にアクセスできることを確認して、無限ループに陥るのを防いでいる、というのが一応の説明だろう。デスティネーションのほうは確認しなくていいのか、という疑問は残る。不正な命令など、backupによる復元が不可能な場合。pc単純にあきらめる。陽に処理が記述されていない命令についてもひととおりチェックするとこの項はおしまい。いくつか残った疑問はいずれそのうち解決することにしよう。