UNIX 6th code reading - 6章まとめ
初めての方はこちらを参照 http://d.hatena.ne.jp/takahirox/20101024/1287923014
はじめに
第6章のまとめです。起動時の流れを絵にしてみました。また、6章の範囲外ですが、savu&retuに関してもまとめてみました。
savu & retu
6章の内容の理解を深めるために、savu, retuの内容を追ってみました。そのメモを残しておきます。
Lions本ではsavu, retuを第8章で扱っています。
cのソースをコンパイルしたコード
savu, retuを理解するために、Cのコンパイラがどういうアセンブリコードを吐くのかを確認してみました。
以下を参考に、simhによるpdp11 + unix v6のエミュレーション環境を構築しました。
以下のCソースを書いてみて、コンパイルしてアセンブリコードを吐いてみました。
関数呼び出し時にどういうアセンブリコードを吐くのか見るためです。
# cat src .c called( a, b ) { int c, d ; c = a ; d = b ; return c + d ; } calling( ) { int e, f, result ; e = 1 ; f = 2 ; result = called( e, f ) ; return result ; }
calling関数がcalledを呼んでいます。
このコードをコンパイルしたのが以下です。
# cc -S src.c # cat src.s .globl _called .text _called: ~~called: ~a=4 ~b=6 ~c=177770 ~d=177766 jsr r5,csv sub $4,sp mov 4(r5),-10(r5) mov 6(r5),-12(r5) mov -10(r5),r0 add -12(r5),r0 jbr L1 L1:jmp cret .globl _calling .text _calling: ~~calling: ~result=177764 ~e=177770 ~f=177766 jsr r5,csv sub $6,sp mov $1,-10(r5) mov $2,-12(r5) mov -12(r5),(sp) mov -10(r5),-(sp) jsr pc,*$_called tst (sp)+ mov r0,-14(r5) mov -14(r5),r0 jbr L2 L2:jmp cret .globl .data
callingからcalledを呼び出す箇所は以下です。
mov -12(r5),(sp) mov -10(r5),-(sp) jsr pc,*$_called
スタックに引数を積み、その後にjsrを実行してcalledに遷移するとともにpc(calledからのreturn先アドレス)をスタックに積んでいます。
savu
関数呼び出しロジックを頭に起きつつ、savuのロジックを追います。
jsr命令でsavuに飛んだ後のスタックの状態は以下です。
表の上側がアドレスlow方向でスタックの成長方向です。
sp | stack |
---|---|
sp -> | return address(PC) |
u.u_rsav(argument) | |
727-728行で、スタックに積んでおいたreturn addressをr1に, u.u_rsavをr0にコピーします。
以下がスタックとレジスタの状態です。
sp | stack |
---|---|
return address | |
sp -> | u.u_rsav |
register | value |
---|---|
r0 | u.u_rsav |
r1 | return address |
729-730行で、r0を経由してu.u_rsav[0], u.u_rsav[1]にそれぞれ現在の実行プロセスのsp, r5を保存しておきます。
u.u_rsav | value |
---|---|
u.u_rsav[0] | sp |
u.u_rsav[1] | r5 |
732行目のjmp (r1)で、savu呼び出し先に戻ります。
retu
savuと同様に、関数呼び出しロジックを頭に起きつつ、retuのロジックを追います。
jsr命令でretuに飛んだ後のスタックの状態は以下です。
sp | stack |
---|---|
sp -> | return address(PC) |
proc[n].addr(argument) | |
742行目でreturn addressをr1にコピーします。
743行目でretuの引数としてproc[n].addrをKISA6にコピーします。KISA6は7番目のPARを指します。
744行目で$_uの内容をr0にコピーします。$_uは140000で、7番目のPARに設定されたページの先頭を指します。743行目で7番目のPARを設定しなおしたので、proc[n]に対応したu.u_rsavがあります。
以下が、この時点でのスタックとレジスタの内容です。
sp | stack |
---|---|
return address | |
sp -> | proc[n].addr |
register | value |
---|---|
KISA6 | proc[n].addr |
r1 | return address |
r0 | u.u_rsav |
746-747行目でu.u_rsavを指しているr0を経由して、proc[n]に対応したsp, r5を設定します。
u.u_rsav | value |
---|---|
sp | u.u_rsav[0] |
r5 | u.u_rsav[1] |
これで、retuで指定したproc[n]に対応したsp, r5が復帰されました。
returnの先がretuで決まる理由
314Pなどに書かれている「returnは、スタックポインタ(r6)と環境ポインタ(r5)によって設定された値に従う」の理屈は以下です。
上に書いたように、retuでプロセスの切替(r5, r6の切替)が行われます。
また、cソースから吐いたアセンブリコードを確認すると、returnからjmp cretという命令が吐かれています。
jmp cret
cret(1430行-)は、csv(1420行目-, 344-345P参照)で積んでおいたr2-5を復帰させた後に、rts pcという命令でreturn先に戻ります。
rts pc
pdp11 handbookには以下のように書かれています。
"RTS PC" has the effect of returning to the address specified on the top of the stack.
スタックの最上位(spが指している先)に積まれているアドレスに遷移します。
retuで現在のプロセスに対応したsp, r5がロードされているので、この時点でspが指している先は、現在のプロセスが最後に実行した関数の戻り先アドレスが入っています。
終わりに
ようやく6章の内容が終わりました。
続いて7章に移ります。
今回のエントリは眠いときに書いたので、内容や文章表現のあやしい箇所がいくつかあります。
後日こっそり直す予定です。