読者です 読者をやめる 読者になる 読者になる

【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

MINIXのexitシステムコールを追う

x86-16 MINIX UNIX V6

前回、アセンブラからシステムコールを呼ぶ例を示しました。

通常はC言語からlibc経由で呼び出します。UNIX V6とMINIX 2.0.4でlibcからexitシステムコールが呼び出されるまでを追ってみます。

UNIX V6

exit()はC言語ではなくアセンブラで実装されています。

s4/exit.s
_exit:
	mov	r5,-(sp)
	mov	sp,r5
	mov	4(r5),r0
	sys	exit

絶対に成功するという前提で、システムコール失敗時のエラー処理は書かれていません。

MINIX 2.0.4

マイクロカーネルはユーザープロセスとサーバープロセスの間でメッセージをやり取りするため、モノリシックカーネルよりも複雑です。

  1. ansi/exit.c
  2. syscall/_exit.s
  3. posix/__exit.c
  4. other/syscall.c
  5. i86/rts/_sendrec.s

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用語ではカーネルコールと呼んで、システムコールとは区別しています。