カーネルを読んでみる

UNIXカーネルC言語に書き換えられたのは3rd editionであり1973年のことなので、このバージョンのソースコードPDP-11のフルアセンブリである。しかし、4221行とコンパクトだし、コメントも満載なので、アセンブリに対するアレルギーがなければ、pre K&R Cの文法に惑わされなくてよい分、V6カーネルよりも読みやすかったりして。どちらにしろPDP-11アーキテクチャの理解がある程度必要だけど。Gordon BellのページにPDP-11 handbookのコピーがあったので、興味がある人はそちらもどうぞ。

まずはシステムコール呼出しから。次のコードは、cat.sのopenシステムコールを呼び出す部分の抜粋だ。

2:
	mov	r0,0f
	sys	open; 0:..; 0

システムコールの実現には、sys命令によるトラップを利用する。この際にパラメータをどのようにユーザからカーネルに渡すのかという問題がある。Linux/x86ではレジスタ渡しするけど、UNIX/PDP-11ではsys命令に続くメモリ上にパラメータを配置し、カーネルはトラップアドレスからの相対でそこにアクセスする。ちなみに関数呼出し(jsr命令)では、スタック渡しもできるが、sys命令のように配置したパラメータに、リンケージレジスタを使ってアクセスする方法もある。基本的にグルーバルシンボルとして定義された関数は前者のスタック経由のパラメータ渡しを使うようで、カーネルは後者しか使ってないのかな。

閑話休題。上の例では、レジスタr0にファイル名のアドレスが格納されている。mov命令でsys命令の次のアドレス(ラベル0:)にその値を書き込んでいる。続く0はmodeがread onlyだということを示している。

あとちょっと気になったのが、openというシンボルがいつシステムコール番号(5)に解決されるかだけど、asの中で変換している気がする。sys命令というのはUNIX asのニーモニックで、DEC的にはtrap命令になる。trap命令の下位8ビットは自由に使えるらしいので、そこにシステムコール番号を埋め込んでいる感じがする。

unkni:、sysent:(u1.s)がカーネルのエントリになっている。PCとPSは自動的にスタックに退避されるので、残りのユーザモードのレジスタをu領域へ退避した後、トラップが起こったPCからシステムコールハンドラを表引きして、sysopenルーチンにジャンプする。

ユーザモードに復帰するときは、sysretルーチンが呼ばれ、後始末をいろいろした後で、sysreleルーチンでスタックを戻して、rti命令を実行する。

この辺はV6カーネルと比較しても、アセンブリとCを行き来するコードが追加されるぐらいであまり変わらない。

(追記)システムコールの引数渡しにはr0レジスタを使うこともあるようだ。

(追記: 2009-08-20)sys命令のことは「UNIX Assembler Reference Manual」の8.8、9.2あたりにちゃんと書いてあるな。

Lions’ Commentary on UNIX (Ascii books)

Lions’ Commentary on UNIX (Ascii books)