通常はC言語からlibc経由で呼び出します。UNIX V6とMINIX 2.0.4でlibcからexitシステムコールが呼び出されるまでを追ってみます。
UNIX V6
s4/exit.s
_exit: mov r5,-(sp) mov sp,r5 mov 4(r5),r0 sys exit
絶対に成功するという前提で、システムコール失敗時のエラー処理は書かれていません。
MINIX 2.0.4
マイクロカーネルはユーザープロセスとサーバープロセスの間でメッセージをやり取りするため、モノリシックカーネルよりも複雑です。
1はatexit()で登録された関数などを処理しています。UNIX V6にはatexit()がなかったため、この部分は存在しません。
2はjmpするだけの実装です。syscallディレクトリにあるコードはすべてjmpするだけのコードです。直接_exit.cを呼び出せば良いように思いましたが、恐らく_exit.sをUNIX V6のようなアセンブラで書かれた割り込みコードに置き替えることで、モノリシックカーネルにも対応できるような構造にしたかったのではないでしょうか。
言い換えると、_exit.sの先にマイクロカーネル特有の実装を分離したかったのではないでしょうか。
コードを見ていきます。
ansi/exit.c
void exit(int status) { _calls(); if (_clean) _clean(); _exit(status) ; }
atexit()で登録された関数を呼んで、出力をflushします。
syscall/_exit.s
__exit: jmp ___exit
posix/__exit.c
#define _exit __exit PUBLIC void _exit(status) int status; { message m; m.m1_i1 = status; _syscall(MM, EXIT, &m); }
メッセージを作っています。システムコールで使われる引数を取得するには、この部分を見ればメッセージの構造が分かります。
なぜ名前を直接変えずにマクロを使っているのか不明です。
other/syscall.c
PUBLIC int _syscall(who, syscallnr, msgptr) int who; int syscallnr; register message *msgptr; { int status; msgptr->m_type = syscallnr; status = _sendrec(who, msgptr); if (status != 0) { /* 'sendrec' itself failed. */ /* XXX - strerror doesn't know all the codes */ msgptr->m_type = status; } if (msgptr->m_type < 0) { errno = -msgptr->m_type; return(-1); } return(msgptr->m_type); }
exit以外からも使われる汎用の関数です。_sendrec()を呼び出して、その戻り値で後処理をしています。m_typeにシステムコール番号を入れて呼び出すと、m_typeに結果が入ります。
i86/rts/_sendrec.s
BOTH = 3 SYSVEC = 32 __sendrec: mov cx,*BOTH ! _sendrec(srcdest, ptr) jmp L0 L0: push bp ! save bp mov bp,sp ! can't index off sp mov ax,4(bp) ! ax = dest-src mov bx,6(bp) ! bx = message pointer int SYSVEC ! trap to the kernel pop bp ! restore bp ret ! return
ここでやっと割り込みを掛けてカーネルに処理を移しています。カーネルで処理されるのはメッセージの送受信だけで、システムコールはメッセージを受け取ったサーバーで処理されます。このようにカーネルを呼び出すことをMINIX用語ではカーネルコールと呼んで、システムコールとは区別しています。