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()実行個所に戻ってくるようです。
今回のまとめ
長くなってきたので、ここでいったん区切ります。今回見てきたことをまとめると、以下のようになります。
終わりに
次回もinitのコードの続きを見ていきます。起動時、そして、hangupが起きた時にどういう処理が行われるのかが見られると思われます。