backup探求

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レジスターをシミュレートする」という部分が目につく。 Lions本では2-5に「PDP11/40にはメモリ管理状態レジスタがふたつある」という説明があって、 あとのほうの「SR2はそれぞれの命令のフェッチ開始時に16bit仮想アドレスをロードする」と説明されている。 「11/40にこのレジスタはいったいあるのかないのか?」といった疑問がのこるが、 Lions本の説明は簡単すぎてよくわからない。

なんのこっちゃ

v6はメモリの使用量を極力減らす戦略をとっている。 そのひとつとして、ユーザーのスタック領域はあらかじめすべてを物理メモリに割り当てるのではなく、必要が生じた時のみ確保して割り当てる方式をとっている。 すなわち、実行中のユーザープログラムがスタックをアクセスしたが割り当てられていなかった場合、実行を中断してtrapルーチンに制御が移る。 trapルーチンがこの状況を検出するとメモリを割り当てをおこない、ユーザープログラムの実行を再開する。 PDP-11/45の場合は実行の再開に必要な情報をマシンが保持しており、backup()ルーチンによりこの情報を取得できる。 ところが、PDP-11/40の場合はかならずしもすべてのデータが揃わない。 そこで、さまざまな情報から推測しよう、というのがm40.sのbackup()の趣旨である。

難しくないほうから

このへんの理解に必要な情報のうちLions本に欠けている部分についてはメモリ管理機構の項に説明をおぎなった。 これを参照しながら、てはじめに、PDP-11/45用のm45.sを見ることにしよう。 こちらはわずか26行におさまっており、「難しい部分」が存在しない。

.globl        _backup
.globl        _regloc
_backup:
        mov     2(sp),r0
        movb    ssr+2,r1
        jsr     pc,1f
        movb    ssr+3,r1
        jsr     pc,1f
        movb    _regloc+7,r1
        asl     r1
        add     r0,r1
        mov     ssr+4,(r1)
        clr     r0
2:
        rts     pc
1:
        mov     r1,-(sp)
        asr     (sp)
        asr     (sp)
        asr     (sp)
        bic     $!7,r1
        movb     _regloc(r1),r1
        asl     r1
        add     r0,r1
        sub     (sp)+,(r1)
        rts     pc

これはm40.sの_backup先頭の7命令をのぞいたものとおなじである。 1:は簡単なサブルーチンなのでこれから見ていこう。

reglocはsystm.h(0237)で定義されており、実体はtrap.c(2677)にある。 その中身はreg.hで定義する配列で、コンテキスト中に目当てのレジスタが保存されているオフセットが入っている。 適当なレジスタについてreglocの値を引いて2倍して(ワードなので)r0の保存されているアドレスに加算すると、目的のアドレスが得られる。

引数として使われているr1の下3bitがレジスタ番号である。 目当てのレジスタの保存内容からr1の上5bitの値を引いて処理は終了する。 r0は不変である。

もういちど先頭から見直してみると、 uに保存されているレジスタについて以下の操作をおこなっていることがわかる。

要するにssr+2とssr+3には、レジスタ補正用のデータがあるということになる。

ここまでわかったところで、m40の_backup先頭の共通しない部分をみてみる。 r2をpush/popしているのは_backupの返り値につかわれているからである。 backupはr2を返すが、これはssr+2とssr+3に代入される。 ただし、jflgが非0ならあとの処理はスキップする。 すなわち、この場合、補正はおこなわれない。

1008 /* --------------------------        */
1009 .globl        _backup
1010 /* --------------------------        */
1011 .globl        _regloc
1012 _backup:
1013         mov     2(sp),ssr+2
1014         mov     r2,-(sp)
1015         jsr     pc,backup
1016         mov     r2,ssr+2
1017         mov     (sp)+,r2
1018         movb    jflg,r0
1019         bne     2f
注意したいのはbackup()の引数をssr+2に保存しているところである。 単に使えるメモリアドレスというだけのことで、ssr+2を使う必然性はない。

ここで補正につかわれているssrについてみてみよう。 m40.sではSSR0がssrに、SSR2がssr+4に保存されている。 m45.sではさらにSSR1がssr+2に保存されている。 ということは、PDP-11/45に存在してPDP-11/40に存在しないのはSSR1で、 backupはSSR1に相当するデータを計算しているのである。 というわけで、コメント

1044 / hard part
1045 / simulate the ssr2 register missing on 11/40
のssr2はssr1の誤りである。 これでようやく話の辻褄があった。

m40.sではSSR1相当の部分にデータを格納している。 ということはそこには普通のメモリー(に見えるもの)がある、ということでよいだろう。

難しいほう

「難しい部分」は実行が失敗した命令のオペコードを解析して、 適切な補正値を推測する。 これを理解するには、PDP-11のオペコードの体系を頭に入れておく必要がある。 PDP-11/40のハンドブックの付録に"NUMERICAL OP CODE LIST"というのがあって、そちらを見るとよりわかりやすい。

bflg
内部用のフラグ。バイト命令なら1、ワード命令なら0
jflg
backupが失敗したら非0
1237 .bss
1238 bflg:    .=.+1
1239 jflg:    .=.+1

r2はSSR1に相当する補正値を蓄積するのに使われる。それぞれのbyteについて、 下3bitがレジスター番号、上5bitが補正値であることに留意しよう。

下請けのsetreg(1197)は引数r0からr2(の下8bit)を計算する。 その際、r0のbit0-2をレジスター、bit3-5をアドレッシングモードとみなす。
モード補正値
自動加算1
間接自動加算2
自動減算-1
間接自動減算-2
ただし、spおよびpcの自動加減の場合は補正値を倍にする。 このへんはbit1もbit2も0でなければ、という間接的な処理になっていてわかりにくい。 アセンブラーでこういう条件判断を記述するのは無理があるかもしれない。

1196 setreg:
1197         mov     r0,-(sp)
1198         bic     $!7,r0
1199         bis     r0,r2
1200         mov     (sp)+,r0
1201         ash     $-3,r0
1202         bic     $!7,r0
1203         movb    0f(r0),r0
1204         tstb    bflg
1205         beq     1f
1206         bit     $2,r2
1207         beq     2f
1208         bit     $4,r2
1209         beq     2f
1210 1:
1211         cmp     r0,$20
1212         beq     2f
1213         cmp     r0,$-20
1214         beq     2f
1215         asl     r0
1216 2:
1217         bisb    r0,r2
1218         rts     pc
1219 
1220 0:     .byte    0,0,10,20,-10,-20,0,0

下請けのfetchは引数および結果がr0である。 ユーザー空間の(r0)にあるワードを読んでr0に返す。 失敗した場合はr2をクリアしてr0に-1を返す。

1222 fetch:
1223         bic     $1,r0
1224         mov     nofault,-(sp)
1225         mov     $1f,nofault
1226         mfpi    (r0)
1227         mov     (sp)+,r0
1228         mov     (sp)+,nofault
1229         rts     pc
1230 
1231 1:
1232         mov     (sp)+,nofault
1233         clrb    r2                        / clear out dest on fault
1234         mov     $-1,r0
1235         rts     pc

fetchを呼ぶときのr0にはSSR2から読み出した値が入っている。 すなわち、実行が失敗した命令の(ユーザー空間での)アドレスである。 この命令のオペコードの8進数表記XXxxxxのXXによりt00からt17に分岐する。

1047 backup:
1048         clr     r2                / backup register ssr1
1049         mov     $1,bflg                / clrs jflg
1050         mov     ssr+4,r0
1051         jsr     pc,fetch
1052         mov     r0,r1
1053         ash     $-11.,r0
1054         bic     $!36,r0
1055         jmp     *0f(r0)
1056 0:              t00; t01; t02; t03; t04; t05; t06; t07
1057                 t10; t11; t12; t13; t14; t15; t16; t17
1058 

さらに、オペコードが(8進数で)00Xxxxもしくは10Xxxxの場合は、 Xの値によりu0からu7に分岐する。

1059 t00:
1060         clrb    bflg
1061
1062 t10:
1063         mov     r1,r0
1064         swab    r0
1065         bic     $!16,r0
1066         jmp     *0f(r0)
1067 0:              u0; u1; u2; u3; u4; u5; u6; u7

命令を仕分けして処理に振り分ける。 さいわい、コメントでおおよその見当がつく。 1099行目の
    br        setreg
    jsr       pc,setreg
    rts       pc
の略である。

1069 u6:     / single op, m[tf]pi, sxt, illegal
1070         bit     $400,r1
1071         beq     u5              / all but m[tf], sxt
1072         bit     $200,r1
1073         beq     1f              / mfpi
1074         bit     $100,r1
1075         bne     u5              / sxt
1076
1077 / simulate mtpi with double (sp)+,dd
1078         bic     $4000,r1        / turn instr into (sp)+
1079         br      t01
1080 
1081 / simulate mfpi with double ss,-(sp)
1082 1:
1083         ash     $6,r1
1084         bis     $46,r1          / -(sp)
1085         br      t01
1086 
ror:0060xx, rol:0061xx, asr:0062xx, asl:0063xx。 rorb:1060xx, rolb:1061xx, asrb:1062xx, aslb:1063xx。 sxt:0067xx。 ここまではu5に飛ばされる。 mfpi:0065xx, mtpi:0066xx。 mfpd:1065xx, mtpd:1066xx。106[47]xxは未使用。 mark:0064xxが無視されているのは要検討。 1078行目では命令を0026xxに書き換えて、ダブルオペランド処理をおこなうmovの処理ルーチンに渡す。 ここの26は(sp)+に相当。命令を変換して処理をまとめるのは賢い方法かもしれないがずいぶんな落とし穴。 1083行目も同様で、命令を65xx46に書き換えて、mov処理ルーチンに渡す。 46は-(sp)相当。
1087 u4:     / jsr
1088         mov     r1,r0
1089         jsr     pc,setreg       / assume no fault
1090         bis     $173000,r2      / -2 from sp
1091         rts     pc
0004xxxはjsrで、10x4[0-3]xxはemt、10x4[4-7]xxはtrapである。
1093 t07:    / EIS
1094         clrb    bflg
07xxxxにある拡張命令。 075040-076777は未使用。
1096 u0:     / jmp, swab
1097 u5:     / single op
1098         mov     r1,r0
1099         br      setreg
000xxx: JMP, RTS, SPL, NOP, SWAB, BR等 100xxx: BPL, BMI 005xxx: 未定義 105xxx: CLRB, COMB, ...

bit15-12が命令の種類、bit11-6がソース、 bit5-0がデスティネーションの場合(xxSSDD)。 1117、1120行目でsetregを呼び出して、ソース、デスティネーションそれぞれについて 補正値を概算する。

1101 t01:    / mov
1102 t02:    / cmp
1103 t03:    / bit
1104 t04:    / bic
1105 t05:    / bis
1106 t06:    / add
1107 t16:    / sub
1108         clrb    bflg
1109 
1110 t11:    / movb
1111 t12:    / cmpb
1112 t13:    / bitb
1113 t14:    / bicb
1114 t15:    / bisb
1115         mov     r1,r0
1116         ash     $-6,r0
1117         jsr     pc,setreg
1118         swab    r2
1119         mov     r1,r0
1120         jsr     pc,setreg
SSとDDについて補正値を概算した結果がr2に入っている。 r1はもとの命令のまま。
1122 / if delta(dest) is zero,
1123 / no need to fetch source
1124 
1125         bit     $370,r2
1126         beq     1f
ソースの補正値が0ならソースを取ってくる必要がないのでそのまま終了。
 
1128 / if mode(source) is R,
1129 / no fault is possible
1130 
1131         bit     $7000,r1
1132         beq     1f
レジスタモードであればfaultが起きるはずはない。
1134 / if reg(source) is reg(dest),
1135 / too bad.
1136 
1137         mov     r2,-(sp)
1138         bic     $174370,(sp)
1139         cmpb    1(sp),(sp)+
1140         beq     t17
174370は上下バイトの上5bitに対するマスク。 ソースとデスティネーションのレジスタが同じだとまずい。 何でt17なのか、という話もあるが要するにあきらめる。
1142 / start source cycle
source cycleを再現するのは、プロセッサの内部状態を復元するためか。
1143 / pick up value of reg
1144 
1145         mov     r1,r0
1146         ash     $-6,r0
1147         bic     $!7,r0
1148         movb    _regloc(r0),r0
1149         asl     r0
1150         add     ssr+2,r0
1151         mov     (r0),r0
u構造体からソースレジスタの値を取ってくる。 ssr+2を使っているのでびっくりするが、単に仮置きされているだけである。
1153 / if reg has been incremented,
1154 / must decrement it before fetch
1155 
1156         bit     $174000,r2
1157         ble     2f
1158         dec     r0
1159         bit     $10000,r2
1160         beq     2f
1161         dec     r0
補正値がプラスならデクリメント、2のbitが立っていればさらにデクリメント。
1162 2:
1163 
1164 / if mode is 6,7 fetch and add X(R) to R
1165 
1166         bit     $4000,r1
1167         beq     2f
1168         bit     $2000,r1
1169         beq     2f
1170         mov     r0,-(sp)
1171         mov     ssr+4,r0
1172         add     $2,r0
1173         jsr     pc,fetch
1174         add     (sp)+,r0
6はindex mode、7はindex deferred mode。 X(R)のXは命令の次のアドレスに入っている。
1175 2:
1176 
1177 / fetch operand
1178 / if mode is 3,5,7 fetch *
1179 
1180         jsr     pc,fetch
1181         bit     $1000,r1
1182         beq     1f
1183         bit     $6000,r1
1184         bne     fetch
1185 1:
1186         rts     pc
3はauto-increment deferred、5はauto-decrement deferred、7はindex deferred。 間接アドレッシングモードの場合は繰り返し適用してオペランドの値をr0に得る。 ここでせっかく取得した値は捨てられてしまう。 実際にアクセスできることを確認して、無限ループに陥るのを防いでいる、というのが一応の説明だろう。 ではデスティネーションのほうは確認しなくていいのか、という疑問は残る。

不正な命令など、backupによる復元が不可能な場合。

1188 t17:    / illegal
1189 u1:     / br
1190 u2:     / br
1191 u3:     / br
1192 u7:     / illegal
1193         incb    jflg
1194         rts     pc
単純にあきらめる。

陽に処理が記述されていない命令についてもひととおりチェックするとこの項はおしまい。 いくつか残った疑問はいずれそのうち解決することにしよう。
戻る