UNIX 6th システムプログラミング - init その2

はじめに

前回のエントリでinitの仕様を明らかにしたので、今回からはいよいよinitのコードを読んでいきます。

コンソールスイッチ

と、本題に入る前に、前回のエントリで「コンソールスイッチって何?」と書いたところ、まごろくさんからコメントを頂きました。


そこで、検索してみたところ、PDP-11/70ですが、このようなものを見つけました。


パチパチっとスイッチを操作して、システム起動時の初期設定などを行っていたようです。ありがとうございます。

init

さて、ようやくinitのコードに入ります。

/etc/rcの実行
main()
{
	register i;
	register struct tab *p, *q;
	int reset();

	/*
	 * if not single user,
	 * run shell sequence
	 */

	if(getcsw() != single) {
		i = fork();
		if(i == 0) {
			open("/", 0);
			dup(0);
			dup(0);
			execl(shell, shell, runc, 0);
			exit();
		}

最初にコンソールスイッチの値を見て、single userモードかどうかを確認しています。singleは以下のように定義されています。この数字は単に仕様に従っているだけです。

#define	single	0173030

getcsw()はcswシステムコール(#38)を実行します。cswシステムコールはコンソールスイッチの値を取得します。

NAME
     csw - read	console	switches

SYNOPSIS
     (csw = 38.; not in	assembler)
     sys     csw

     getcsw( )

DESCRIPTION
     The setting of the	console	switches is returned (in r0).
.globl	_getcsw

_getcsw:
	mov	r5,-(sp)
	mov	sp,r5
	sys	38.
	mov	(sp)+,r5
	rts	pc

single userモードでなかった場合、ルートディレクトリをstdin, stdout, stderrとしてオープンしてから、子プロセスを生成し、/etc/rcを実行させます。後で述べますが、さらにその子プロセスが/etc/updateを永遠に実行し続けます。

char	runc[]	"/etc/rc";

この、ルートディレクトリのオープン処理、そしてすぐ後にでてくるクローズ処理が何をしたいのかはまだ把握できていません。プロセスはu.u_ofile[0, 1, 2]にstdin, stderr, stdout相当のものを持っていけないという仕様に合わせているだけでしょうか? それを前提としているライブラリなどへの考慮のような気がします。

ここで実行されるシェルプログラムは/bin/shです。

char	shell[]	"/bin/sh";

/etc/rcの中を覗いてみます。

rm -f /etc/mtab
/etc/update

たった二つのことしか実行していません。

/etc/mtabはマウントされたディスクの情報が格納されるファイルのようです。


/etc/mount、/etc/umountが/etc/mtabの管理をしているようです。


/etc/updateは、マニュアルによれば30秒に1回syncシステムコールを実行するプログラムのようです。syncシステムコールはメモリに蓄えられた、ブロックデバイスバッファやinode情報のキャッシュをディスクにフラッシュし、メモリ内の情報とディスクの情報の同期を取るシステムコールです。


せっかくなのでupdate.sを覗いてみます。

	sys	fork
		br 1f
	sys	exit
1:
	clr	r0
	sys	close
	mov	$1,r0
	sys	close
	mov	$2,r0
	sys	close
1:
	sys	sync
	mov	$30.,r0
	sys	sleep
	br	1b
sleep = 35.
sync = 36.

親プロセスは子プロセスを作って、すぐにexitシステムコールを発行しています。子プロセスは親プロセスに先立たれて宙ぶらりんになるので、カーネルのexit()の内容を思い出すに、proc[1]に養子に出されるのだと思います。

子プロセスは、initの中で(親プロセスが)オープンしたu.u_ofile[0, 1, 2]をcloseしています。先も述べましたが、この、オープンしたけれど、すぐクローズしている処理が何を意味しているのかはまだ把握できていません。

そして子プロセスは30秒に1回syncシステムコールを実行するループに入ります。

utmp, wtmpファイルへの出力

initのmain()の処理の続きを見ていきます。utmpファイルとwtmpファイルの操作をしています。

char	utmp[]	"/etc/utmp";
char	wtmpf[]	"/usr/adm/wtmp";
		while(wait() != i);
		close(creat(utmp, 0644));
		if ((i = open(wtmpf, 1)) >= 0) {
			seek(i, 0, 2);
			wtmp.tty = '~';
			time(wtmp.time);
			write(i, &wtmp, 16);
			close(i);
		}
	}

最初にutmpファイルを初期化しています。utmpファイルは前回のエントリで紹介した通り、システムを使用中のユーザの情報を管理します。


マニュアルによると、以下の表のように16Bytes/1entryで情報を扱っているようです。

Byte 意味
15-14 unused
13-10 login time
9 unused?
8 端末(のスペシャルファイル)の最後の文字
7-0 user name


続いてwtmpの末尾に情報を追加します。wtmpファイルは前回のエントリで紹介したとおり、ユーザのログイン・ログアウト情報を扱います。


フォーマットはutmpと同じらしく、wtmpファイル操作用のwtmp構造体を見ても、それが確認できます。

struct {
	char	name[8];
	char	tty;
	char	fill;
	int	time[2];
	int	wfill;
} wtmp;

wtmpファイルの最後尾にエントリを追加します。そのエントリの「端末の最後の文字」を表すフィールドには'~'をセットします。'~'はマニュアルによると、rebootされたことを表すようです。

setexit()の実行

その後の処理を見ていきます。

	setexit();
	signal(1, reset);

setexit()って何? とマニュアルを調べたところ、r5, r6を退避する関数であることがわかりました。reset()を使って、setexit()を実行した個所に戻るようです。


set"exit"()という名前から、exitシステムコール関連を想像したのですが、文字通りexit(出口)を設定する関数のようです。

実際にsetexit()、reset()のコードを見てもそれが確認できます。(spではなく、spcなのはなぜ?)

/ C library -- reset, setexit

/	reset()
/ will generate a "return" from
/ the last call to
/	setexit()
/ by restoring sp, r5
/ and doing a return.
/
/ useful for going back to the main loop
/ after a horrible error in a lowlevel
/ routine.

.globl	_setexit
.globl	_reset
.globl	csv, cret

_setexit:
	jsr	r5,csv
	mov	r5,sr5
	mov	2(r5),spc
	jmp	cret

_reset:
	mov	sr5,r5
	mov	spc,2(r5)
	jmp	cret

.bss
sr5:	.=.+2
spc:	.=.+2

setexit()の次の行で、signal(1, reset)を実行しています。マニュアルでsignalの種類を確認すると、signal#1はhangupに対応していることがわかります。hangupが引き起こされた場合、reset()が実行され、setexit()実行個所に戻ってくるようです。

今回のまとめ

長くなってきたので、ここでいったん区切ります。今回見てきたことをまとめると、以下のようになります。

  • single user modeでなければ、shellプログラムに/etc/rcを実行させる
  • single user modeでなければ、utmpとwtmpファイルにユーザ情報を書き込む
    • utmpはシステムを実行中のユーザ情報を扱う
    • wtmpはシステムを実行したユーザの履歴を扱う
  • setexit()、reset(1, reset)を実行し、hangupが起きたら、init処理(の途中)から再開されるようにしている

終わりに

次回もinitのコードの続きを見ていきます。起動時、そして、hangupが起きた時にどういう処理が行われるのかが見られると思われます。