UNIX 6th code reading - 6章まとめ

初めての方はこちらを参照 http://d.hatena.ne.jp/takahirox/20101024/1287923014

はじめに

第6章のまとめです。起動時の流れを絵にしてみました。また、6章の範囲外ですが、savu&retuに関してもまとめてみました。

/etc/init実行までの流れ

/etc/init実行までの流れを絵にしてみました。


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章に移ります。

今回のエントリは眠いときに書いたので、内容や文章表現のあやしい箇所がいくつかあります。
後日こっそり直す予定です。