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

はじめに

半年くらい抱えていたとあることが、一応一段落したので、またブログを更新していこうと思っています。UNIX v7 code readingを継続するのと、それに加えてUNIX v6 システムプログラミング(code reading)というものを始めます。他にもいくつかネタはあるのですが、それは追々。

UNIX v6のカーネルは一通り理解できたつもりなので、次はカーネルが提供するサービスを使って、システムを構築・管理するユーザランドシステムプログラムを見ていこうというものです。

カーネルは最低限のサービスを提供するものであり、その内容を真に理解するためには、サービスを使用する側の理解も必要だと考えています。これはカーネルだけでなく、ハードウェアとソフトウェア間や、ネットワークのレイヤ間などでも同様のことが言えるでしょう。

/etc/init

起動処理のおさらい

今回は/etc/init(以下init)を見ていきます。最初に、システムの起動時に実行されるカーネルのmain()の処理のうち、initに関連するものをおさらいします。

  • main()はproc[0]をシステムプロセス(カーネルプロセス)として生成する
    • proc[0]はswtch()とsched()の処理のみを行うスケジューリングプロセス
  • proc[0]はnewproc()を実行してproc[1]を生成する
    • proc[1]はinitを実行する
    • 実行直前に自身のモードをユーザモードにする


これでカーネルとしての起動処理は完了し、ここからカーネルの処理の中心は、クロック割り込みなどを除き、基本的にはユーザプロセスからの要求を対処することになります。

initの仕様

init仕様を見る前に、上に挙げたことからいくつかのことが推測できます。

  • initはシステムのユーザがシステムを使える環境を構築しているはず
    • proc[0]はスケジューリングプロセスなので、initが環境を構築しないと、誰もシステムを使える状況にならない
    • ユーザはシェルからシステムを操作する。そして端末を使ってシステムをやり取りする
    • これらから推測すると、initは端末を開き、ログインプログラムとシェルプログラムを実行するはず
    • 複数端末が使える環境にある場合は、それぞれの端末のオープン処理とログイン・シェルプログラム実行を行うはず


というわけで、マニュアルを見てinitの仕様を実際に確認します。

http://man.cat-v.org/unix-6th/8/init

カーネルの中身を追っていたときは、マニュアルの2, 4, 5章あたりを中心に見ていましたが、システムプログラムを追うときには1, 5, 8章あたりが重要になってきそうです。

Descriptionの部分を引用します。

DESCRIPTION
     Init is invoked inside UNIX as the	last step in the boot
     procedure.	 Generally its role is to create a process for
     each typewriter on	which a	user may log in.

     First, init checks	to see if the console switches contain
     173030.  (This number is likely to	vary between systems.)
     If	so, the	console	typewriter /dev/tty8 is	opened for read-
     ing and writing and the Shell is invoked immediately.  This
     feature is	used to	bring up a single-user system.	When the
     system is brought up in this way, the getty and login rou-
     tines mentioned below and described elsewhere are not used.
     If	the Shell terminates, init starts over looking for the
     console switch setting.

     Otherwise,	init invokes a Shell, with input taken from the
     file /etc/rc.  This command file performs housekeeping like
     removing temporary	files, mounting	file systems, and start-
     ing daemons.

     Then init reads the file /etc/ttys	and forks several times
     to	create a process for each typewriter specified in the
     file.  Each of these processes opens the appropriate type-
     writer for	reading	and writing.  These channels thus receive
     file descriptors 0	and 1, the standard input and output.
     Opening the typewriter will usually involve a delay, since
     the open is not completed until someone is	dialed up and
     carrier established on the	channel.  Then /etc/getty is
     called with argument as specified by the last character of
     the ttys file line.  Getty	reads the user's name and invokes
     login (q.v.)  to log in the user and execute the Shell.

     Ultimately	the Shell will terminate because of an end-of-
     file either typed explicitly or generated as a result of
     hanging up.  The main path	of init, which has been	waiting
     for such an event,	wakes up and removes the appropriate
     entry from	the file utmp, which records current users, and
     makes an entry in /usr/adm/wtmp, which maintains a	history
     of	logins and logouts.  Then the appropriate typewriter is
     reopened and getty	is reinvoked.

     Init catches the hangup signal (signal #1)	and interprets it
     to	mean that the switches should be examined as in	a reboot:
     if	they indicate a	multi-user system, the /etc/ttys file is
     read again.  The Shell process on each line which used to be
     active in ttys but	is no longer there is terminated; a new
     process is	created	for each added line; lines unchanged in
     the file are undisturbed.	Thus it	is possible to drop or
     add phone lines without rebooting the system by changing the
     ttys file and sending a hangup signal to the init process:
     use ``kill	-1 1.''

美味しいところを摘むと、こんな感じです。

  • コンソールスイッチを見て、single user mode(0173030)かどうかチェック
    • single user modeならば
      • システムコンソール用に/dev/tty8端末をstdin, stdout用に開き、シェルプログラムを実行する
      • ログイン処理は行われない
    • single user modeでなければ
      • (プロセスを生成し、)シェルプログラムを駆動し、/etc/rcに書かれている処理(一時ファイルの削除など)を行わせる。これはお掃除専用のプロセス
      • /etc/ttysを読み、それぞれの端末にプロセスを生成する。システムコンソール用の端末/dev/tty8の情報も/etc/ttys中に含まれている様子
      • それぞれのプロセスは適切な端末をstdin, stdout用に開く
      • 端末のオープン処理は、ユーザのダイアルアップ接続&接続確立処理を待たないといけないため、遅延が発生する
      • それぞれのプロセスは、/etc/ttysファイルの各行の最後の文字列を引数に、/etc/gettyを実行する。/etc/gettyは、ユーザのログイン処理、シェルプログラムの実行を行う
      • utmpに現在実行中のユーザ情報を記録する
      • /usr/adm/wtmpに、ユーザのヒストリ情報(誰が過去にログインしていたか)を記録する
      • initは、端末からSIGHUP(hangup, signal #1)が送られてくると、utmpから該当エントリを削除し、/usr/adm/wtmpに該当エントリを追加する


いくつかのキーワードがでてきました。それらについてもマニュアルに記述がありました。/etc/rcと/etc/ttysはコードも載せておきます。

これらについてはinitを実際に読むときに、必要に応じて参照していくつもりです。


あと、"SETTING UP UNIX - Sixth Edition"を見ると、/dev/tty8というのはシステムコンソール端末用のスペシャルファイルのようですね。initは起動時の処理なので、この資料にも結構お世話になりそうな予感です。

http://minnie.tuhs.org/PUPS/Setup/v6_setup.html

Special Files

... snip ...

The same goes for the character devices. Here the names are arbitrary except that devices meant to be used for teletype access should be named /dev/ttyX, where X is any character. The files tty8 (console), mem, kmem, null are already correctly configured.

... snip ...

Multiple Users

If UNIX is to support simultaneous access from more than just the console teletype, the file /etc/ttys (ttys-V) has to be edited. For some historical reason tty8 is the name of the console typewriter. To add new typewriters be sure the device is configured and the special file exists, then set the first character of the appropriate line of /etc/ttys to 1 (or add a new line). Note that init.c will have to be recompiled if there are to be more than 20 type- writers. Also note that if the special file is inaccessible when init tries to create a process for it, the system will thrash trying and retrying to open it.

コンソールスイッチというものが、明確にはよくわかっていないのですが、PDP-11/40にマッピングされているレジスタの一つで、システムをsingle user modeかmultiple users modeか、どちらで動かすか決めるものだと思います。ハードウェア的に、スイッチをパチパチいじって値を操作するのでしょうか?

終わりに

長くなってきたので、ここで一端切ります。initがどのような処理を行うのか、だいたい想像がついてきました。次回は実際にinitのコードを追っていきます。

ところで、initには駆動処理以外に、(親を失い)宙ぶらりんになったプロセスを処理する責務もあるのですが、それについてマニュアルで触れられていないような……見落としていますかね?