|
initは,まず/etc/inittabで指定されているプログラムを起動する.Linuxのinittabファイ ルの書式はSysVと同じ形式になっている.
inittabには,システム起動時にどのプロセスがinitによって起動されるべきかが記述され ている.システムは,様々な起動レベルに別れて起動されている.このレベルをrunlevelと 呼ぶ.以下がそのrunlevelの定義だ.
runlevelの定義
# Runlevel 0 is halt. これはシステム終了時のレベル. # Runlevel 1 is single-user. シングルユーザでシステムを走らせる場合のレベル. # Runlevels 2-5 are multi-user. マルチユーザでシステムを走らせる場合のレベル. # Runlevel 6 is reboot. 再起動させるときのレベル. |
/etc/inittabファイルの一部
1: # The default runlevel. 2: id:2:initdefault: 3: # Boot-time system configuration/initialization script. 4: # This is run first except when booting in emergency (-b) mode. 5: si::sysinit:/etc/init.d/rcS 6: # What to do in single-user mode. 7: ~~:S:wait:/sbin/sulogin 8: # /etc/init.d executes the S and K scripts upon change 9: # of runlevel. 10: # 11: l0:0:wait:/etc/init.d/rc 0 12: l1:1:wait:/etc/init.d/rc 1 13: l2:2:wait:/etc/init.d/rc 2 14: l3:3:wait:/etc/init.d/rc 3 15: l4:4:wait:/etc/init.d/rc 4 16: l5:5:wait:/etc/init.d/rc 5 17: l6:6:wait:/etc/init.d/rc 6 18: # Normally not reached, but fallthrough in case of emergency. 19: z6:6:respawn:/sbin/sulogin |
****Line 2
id:2:initdefault: は,actionフィールドがinitdefaultになっている.この指定は,システムの起動が終了したときに,デフォルトでどのrunlevelに入るべきかを指定している.したがって,runlevelの指定のための行だから,processフィールドに何かプログラム名を記入しても無視され,プロセスは何も起動されない.
もし,この行をinittabに含めないと,システム起動終了時にinitがどのrunlevelに入るべきかを尋ねてくる.この時,ユーザはコンソールから番号を入力する.試しに,inittabからこの行を削除して,システムを再起動してみるとよいだろう.
****Line 5
si::sysinit:/etc/init.d/rcS
actionフィールドがsysinitの場合,processフィールドに記述されたプログラム,またはスクリプトは,システムの起動中に実行される.したがって,runlevelsは無視される.実際,runlevelsフィールドは空である.
/etc/init.d/rcS スクリプトの詳細は,下で述べる.
****Line 7
runlevelフィールドの"S"は,シングルユーザモード(runlevel 1)に入ったとき, 間接的に参照されプロセスが起動されるので,直接には何も意味しない.
****Line 11--17
各runlevelにおいて,rcスクリプトがそのrunlevelの番号を引数にとって起動されるの が分かる.actionフィールドにあるwaitは,initデーモンに,対応するrunlevelに入ったと きに1度だけそのプロセスを起動し,そのプロセスが終了するまで先へ進まず待つことを 指示している.
****Line 18--19
通常,この行には達しないとコメントに書いてある.これは,'rc 6'が最後に実行するコ マンドが,'reboot -d -f -i'でこれがシステムを再起動するので,initは通常ここの行に は到達しない.ちなみに,respawnはプロセスが終了した場合再びプロセスを起動するよう initに指示している.
/sbin/suloginプログラムは,シングルユーザ・ログインの略で,シングルユーザモード専用のログイン環境を提供する.
ちなみに,/sbin/suloginは,LILOなどのブートローダにおいて-bフラグが指定された時か,シングルユーザモードで起動されたときにも,initによって起動される.
/etc/init.d/rcS スクリプト
1: #! /bin/sh 2: # 3: # rcS Call all S??* scripts in /etc/rcS.d in 4: # numerical/alphabetical order. 5: # 6: # Version: @(#)/etc/init.d/rcS 1.10 06-Nov-1997 miquels@cistron.nl 7: # 8: 9: PATH=/sbin:/bin:/usr/sbin:/usr/bin 10: runlevel=S 11: prevlevel=N 12: umask 022 13: export PATH runlevel prevlevel 14: 15: # 16: # See if system needs to be setup. 17: # 18: if [ -x /sbin/unconfigured.sh ] 19: then 20: /sbin/unconfigured.sh 21: fi 22: 23: # 24: # Source defaults. 25: # 26: . /etc/default/rcS 27: export VERBOSE 28: 29: # 30: # Call all parts in order. 31: # 32: for i in /etc/rcS.d/S??* 33: do 34: # Ignore dangling symlinks for now. 35: [ ! -f "$i" ] && continue 36: 37: case "$i" in 38: *.sh) 39: # Source shell script for speed. 40: . $i 41: ;; 42: *) 43: # No sh extension, so fork subprocess. 44: $i start 45: ;; 46: esac 47: done 48: 49: # 50: # For compatibility, run the files in /etc/rc.boot too. 51: # 52: [ -d /etc/rc.boot ] && run-parts /etc/rc.boot 53: 54: # 55: # Finish setup if needed. 56: # 57: if [ -x /sbin/setup.sh ] 58: then 59: /sbin/setup.sh 60: fi |
runlevelにSを代入しているのは,rcSスクリプトが起動されるのは,sysinitすなわちシス テム起動時なので,その頭文字でSを用いている.single-userモードは1なので,ここは 恐らくsisinitのSであろう.
prevlevelのNは,起動時の前のrunlevelは無い(None)という意味であろう.
****Line 18--21
/sbin/unconfigured.sh存在し且つ実行可能であれば,実行する.恐らく,Debianディストリビューションのシステム設定を行うスクリプトなのだろう.設定が無事完了すると削除されるのだろう.なぜなら,私のシステムには,このスクリプトは残っていないからだ.
****Line 26--27
"."は,bashの組み込みコマンドで,". filename"で,filenameの中にあるコマンドを現在のシェルの中で実行する.filenameの中の最後のコマンドの終了ステイタス値を戻す. したがってここでは,/etc/default/rcSをまず実行する.
/etc/default/rcSの内容は,以下の通りである.
/etc/default/rcS ファイル
1: # 2: # Defaults for the boot scripts in /etc/rcS.d 3: # 4: 5: # Time files in /tmp are kept. 6: TMPTIME=0 7: # Set to yes if you want sulogin to be spawned on bootup 8: SULOGIN=no 9: # Set to no if you want to be able to login over telnet/rlogin 10: # before system startup is complete (as soon as inetd is started) 11: DELAYLOGIN=yes 12: # Set GMT="-u" if your system clock is set to GMT, and GMT="" if not. 13: GMT="" 14: # Set VERBOSE to "no" if you would like a more quiet bootup. 15: VERBOSE=yes 16: # Set EDITMOTD to "no" if you don't want /etc/motd to be editted automatically 17: EDITMOTD=yes 18: # Set FSCKFIX to "yes" if you want to add "-y" to the fsck at startup. 19: FSCKFIX=no |
****Line 32--47
"?"は,ファイル名のうち1文字とマッチする."*"は,ファイル名の0文字以上から成る 文字とマッチする.したがって32行目は,"S"で始まり最低でも3文字のファイル名のスクリプトを順に変数"i"に格納し,"do"から"done"までを全ての"i"に格納される全てのスクリプトに対して実行する.
その内容は,まず,35行目でそのファイルが通常のファイルかどうか調べ,そうでない場合は,bashのビルトインコマンドcontinueが実行され,次の"i"の中身に対応するループへ行く. テストの"-f"は,ファイルが存在し且つそのファイルが通常のファイル(デバイスファイル などの特殊ファイルではないファイル)であるかどうかを調べる.
ちなみにシンボリックリンクファイルは,リンク先の実体ファイルと同一のi-nodeをも足せ ることによって,名前は異なるが参照している実体は実体ファイルと同じである.したがっ て,"-f"テストがリンクファイルに対して行われていても,実際にはリンク先ファイルに対 して"-f"テストが実行されているのである.したがって,リンクファイルが存在しても,リ ンク先の実体ファイルが存在しないか特殊ファイルである場合には,ことテストは偽を返す.
"i"に格納されているファイルは,32行目より"/etc/rcS.d"内に既に存在するはずであるが, これらは全てリンクファイルである.これらのリンクファイルの実体は,"/etc/init.d"内 にあるスクリプトファイル群である.したがって,ここのテストは,リンクファイルのみの 存在ではなく,そのリンク先の実体であるスクリプトが存在することを保証している.
37行目からのcase文では,サフィックスが"sh"のファイル名を持つスクリプトは,実行速度を速めるため,サブプロセスを起動するのではなく,bashの組み込みコマンド"."を用いて,現在のシェル内で実行する.それ以外のファイルは,引数に"start"を指定してスクリプトを実行する.
例えば,私の環境では,以下のファイルのうちREADMEを除くファイルが,以上の動作の対象となる.
$ ls /etc/rcS.d/ README S20modutils S40hostname.sh S55bootmisc.sh S05keymaps.sh S25mdutils.sh S40network S55urandom S10checkroot.sh S30checkfs.sh S45mountnfs.sh S15isapnp S35mountall.sh S50hwclock.sh末尾に"sh"エクステンションが付いているファイルと付いていないファイルの中身を調べると,その違いに気付く."sh"エクステンションが付いているファイルには,1行目に#!/bin/shがなく,shエクステンションの付いていないファイルには,1行目にシェルの指定が書かれている.
実は,カーネルはプログラムを実行する際,バイナリファイルやスクリプトを見分けるのにこの1行目の最初の数バイトをチェックする.もし,実行しようとしているファイルの最初に#!を見つけると,カーネルは#!以下に指定されているプログラムを,このファイルの内容を実行するためのインタープリータとして起動する.したがって,bashの組み込みコマンド"."で実行しない場合#!を必ず記述しなければならない.それでは,bashの組み込みコマンド"."で実行する場合に#!を記述するとどうなるのだろうか?たとえ,ファイルの最初に#!を記述しても,そのファイルは現在のシェルから,すなわち現在のシェルをインタプリタとして起動されるため,新しいプロセスは生成されない.自分で簡単なシェルを書いて実験してみよう.
なお,カーネルがどのようにバイナリやスクリプトを実行しているかは,とても興味深いトピックである.これについては,execシステムコールの実装,a.outやELFなどのバイナリローダ,そしてkerneldデーモンのコードをハッキングするときにさらに詳細に触れられるであろう.「夜は気長にカーネルハッキング」のページをお楽しみに!
****Line 52
もし,/etc/rc.bootディレクトリが存在すれば,そのディレクトリ内にある全ての実行ファイルをrun-partsコマンドで実行する.run-partsコマンドは,インタープリターを自動で起動しないので,スクリプトファイルの先頭には,必ず#!以下にインタープリターを指定しなければならない.
私のシステムには,デフォルトの"0setserial"スクリプトのみがこの中にあった.これは,シリアルポートを初期化するスクリプトである.このスクリプトは大変長いので,シリアルポートに関するハッキングの際に扱うことにする.
****Line 55
最後に,/sbin/setup.shというファイルが存在し,且つそれが実行ファイルなら,それを実行し終わる.これは,恐らく18行目の/sbin/unconfigured.shと同様に,システムインストール時の設定が完了していない場合に残るファイルなのであろう.私のシステムには,存在しなかった.
/etc/init.d/rc スクリプト
1: #! /bin/bash 2: # 3: # rc This file is responsible for starting/stopping 4: # services when the runlevel changes. 5: # 6: # Optimization feature: 7: # A startup script is _not_ run when the service was 8: # running in the previous runlevel and it wasn't stopped 9: # in the runlevel transition (most Debian services don't 10: # have K?? links in rc{1,2,3,4,5} ) 11: # 12: # Author: Miquel van Smoorenburg |
startup()関数は,第1引数で与えられるスクリプトを実行する.もし第1引数が shスクリプトなら 'sh 第1引数' でシェルからスクリプトを実行する.デバッギングの時には,19行目のdebug=echo をコメントアウトすることにより,第1引数で与えられるスクリプトを実行せずに, スクリプト名だけ表示する.
****Line 36
trap "command" signals
で,signalsを受け取ったらcommandを実行するように設定する.ここでは,INT, QUIT,
TSTPを受け取ったら":" を実行するようになっている.":"は,bashのビルトインコマンド
で,全く何もしないというコマンドである.もし,ここを何もしないという意味で,""と空
文字にしてしまうとsignalsを無視するように設定されてしまう.この設定を解除して
signalsを再認識させるには,"trap signals"を実行しなければならない.
シグナルの種類は,'kill -l'で見ることができる.どのシグナルにどのキーが割り当てら れているかを見るには,'stty -a'で見ることができる.
INT, QUIT, TSTPのシグナルはそれぞれ,INTerrupt(CTRL-C), QUIT(CTRL-\), Terminal SToP(CTRL-Z)を表す.QUITシグナルを受け取ったプロセスはcoreファイルを吐き 出して死ぬ.したがって,このスクリプトが実行されるシェル内では,上の3つのシグナル を受け取っても何もしない.何もしないと無視するの違いはあるのかどうかは不明.
ここで行っているのは,キーボードからの割り込みシグナルを受け取ってもrcスクリプトが 何もしないため,そのシグナルは子プロセスへパスされて,子プロセスが割り込みを受ける ことを可能にしている.
次のスクリプトを実行して実験をしてみよう.
trap の実験用スクリプト
#!/bin/bash trap ":" INT QUIT TSTP sleep 3m echo Finished the 1st subprocess.\n sleep 3m echo Finished the 2nd subprocess.\n sleep 3m echo Finished the 3rd subprocess.\n |
$ ./experiment1 (ここでCtrl-Cを押すと) Finished the 1st subprocess.n (ここでCtrl-Cを押すと) Finished the 2nd subprocess.n (ここでCtrl-Cを押すと) Finished the 3rd subprocess.nしたがって,Ctrl-Cはexperiment1をInterruptすることはできなかった.もし,上のスクリ プトで,trapの行を削除すれば,Ctrl-Cでexperiment1を子プロセスもろとも殺せる.しか し,注意しなければならないのは,Ctrl-Cなどのシグナルは,バックグラウンドプロセスで はなく,フォアグランドプロセスのみに対して有効である.したがって,rcスクリプトから 実行するプログラムの後に&を付加してバックグラウンドで走らせてしまうと,Ctrl-Cをタ イプしてもそのプログラムは終了しない.
****Line 39
onlcrモード端末に表示されるアウトプットをコントローするモードで,改行(NL)を キャリッジリターン改行(CR-NL)にマップする.こうすることにより,カーソルが必ず行頭 に戻ることを保証し,表示が階段上になるのを避けることができる.
後半のリダイレクションの意味は不明.'0>&1'とあるが,0は端末のbaudスピードを設定し ている.この設定を端末に割り当てるのではなく,ファイル1に設定しているように見える. 後で,ファイル1から読込むのだろうか?少なくとも,rcスクリプト内では読込まないが.
'stty -a 'で,現在の端末設定が表示される.
****Line 43--49
$RUNLEVELと$PREVLEVLはそれぞれ,/etc/init.d/functionsの中で事前に設定されている. grepを用いて,RUNLEVELとPREVLEVEL変数の設定部分を検索してみると以下のような結果を 得る.
$ grep RUNLEVEL /etc/init.d/* /etc/init.d/functions:# Oh - we setup RUNLEVEL and PREVLEVEL if needed /etc/init.d/functions:if [ "$RUNLEVEL" = "" ] /etc/init.d/functions: RUNLEVEL=$2 /etc/init.d/functions: export RUNLEVEL PREVLEVEL /etc/init.d/rc: runlevel=$RUNLEVEL $ grep PREVLEVEL /etc/init.d/* /etc/init.d/functions:# Oh - we setup RUNLEVEL and PREVLEVEL if needed /etc/init.d/functions: PREVLEVEL=$1 /etc/init.d/functions: export RUNLEVEL PREVLEVEL /etc/init.d/rc: previous=$PREVLEVEL
しかし,この/etc/init.d/functionsスクリプトがいつどこから呼び出されているかがいま だ不明.どこからも,呼び出されていないようだ.この,/etc/init.d/rcスクリプトの前に initデーモンによって呼び出されるスクリプトの中には,RUNLEVEl,PREVLEVELいずれの変 数も設定されていない.runlevel,prevlevel変数については,/etc/init.d/rcSの10--13行 目で,それぞれSとNに設定され,環境変数としてexportされてはいるが,/etc/init.d/rcで はrunlevelを設定されていないRUNLEVELで上書きしている.さらに,ここではprevlevelで はなくpreviousに名前が変わっている.
しかし,45行目ではrcスクリプトがinitによって呼び出されるときに,第一引数に渡される runlevel番号をrunlevel変数に代入している.これを行うなら,43行目は全く不必要に見え るのだが?
ここのスクリプト以外で,うまくこの二つの変数(RUNLEVEL,PREVLEVEL)は初期化されて いるのだろうか???
****Line 52
もし,現在のrunlevelに対応したrcディレクトリ(/etc/rc1.dなど)があれば,次の行以下 を実行し,無ければ何もせずにこのrcスクリプトを終了する.
****Line 54--65
もし,47行目で正しくpreviousが設定されているとしたら,/etc/init.d/rcSスクリプトの 11行目より,システム起動から最初のrunlevelに入ったときには,前のrunlevelが存在しな いので,previousにはNが設定されているはずである.
したがって,もしシステム起動後の最初のrunlevelでない場合には,57行目から64行目を実 行し,システム起動後の最初のrunlevelの場合は,66行目へ進む.
現在のrunlevelに入る前に,他のrunlevelにいた場合には,まず,現在のrunlevelに入った ときに終了されるべきプロセスをkillする"/etc/rc$runlevel.d/K(2桁の番号)(任意の名前) "スクリプトを第一引数にstopを指定して実行する.この実行は,24行目で定義されている startup()関数を通して行われる.startup()関数は,debug時にはスクリプトは起動せずス クリプト名のみをechoする.debugではないときには,*.shファイルは,shを起動して実行 し,そうでないときは各スクリプトの1行目に指定されているインタープリタにまかせて実 行する.ここでは,"/etc/init.d/rcS"スクリプトの40行目のように,実行速度を速めるた めに現在のシェル内でスクリプトをsourceして実行することはしていない.
通常,"/etc/rc$runlevel.d"以下にあるスクリプトは全てリンクファイルである.その実体 は,"/etc/init.d"内にあるのだが,60行目でそれらの実体ファイルが存在することを確認 している.
****Line 67
この行に到達する前に,現在のrunlevelに移ったときに停止すべきプロセスをkillする手続 きを終えたので,ここからはいよいよ,現在のrunlevelに移ったときに起動されるべきプロ セスをstartさせるスクリプトを起動する手続きにはいる.
プロセスの起動を司るスクリプトは,"/etc/rc$(runlevel).S*"である.このスクリプトも "/etc/rc$(runlevel).K*"と同様リンクファイルで,その実体は"/etc/init.d"内にある.
実は,プロセスをkillするための"/etc/rc$(runlevel).K*"ファイルも,プロセスをスター トさせる"/etc/rc$(runlevel).S*"ファイルも,"/etc/init.d"内の同じスクリプトにリンク されている."/etc/init.d"内のスクリプトは,第一引数が"start"なのか"stop"なのかで, プロセスを起動するか殺すかを判断している.したがって,"/etc/rc$(runlevel).K*"ファ イルと"/etc/rc$(runlevel).S*"ファイルは,各プロセスの管理を行う"/etc/init.d"内の スクリプトを,プロセスの起動のために呼び出すか停止のために呼び出すかの区別を容易 にするために,わざわざ異なるファイル名で作られているのである.
****Line 69
プロセス起動用スクリプトが存在するか,さらにリンクファイルならその実体も存在するか をチェック.存在しないのなら次のスクリプトファイルの処理にcontinueで進む.
****Line 71
前のrunlevelが存在していたかをチェック.もし存在せず,現在のrunlevelがシステムの起 動から初めてのrunlevelである場合には,73--85行目は実行されない.
ちなみに,ループの度にこのチェックが実行されるのは非効率で,実行速度改善の余 地がある.コードの最適化によく使われるように,このようなテストは,ループの外で1回 だけ行うのが良い.
****Line 77--79
77行目は,現在のrunlevelに対応した"rc$(runlevel).d"ディレクトリ内のStart up(rc$runlevel.d/S*)スクリプトから,"/etc/rc$(runlevel).d/S番号"の部分を取り除いた スクリプト名をsuffix変数に代入している.たとえば,スクリプト名が "/etc/rc4.d/S98canna"なら,"$suffix"は"canna"となる.
78行目は,stop変数に,現在のrunlevelで起動されるStart up(rc$runlevel.d/S*)スクリプ トに対応したKill(rc$runlevel.d/K*)スクリプト名を格納している.ただし,正規表現の [0-9][0-9]はここでは展開されていない.この展開は,stop変数に格納された名前のファイ ルが存在するかをテストする85行目で行われる.
79行目は,現在のrunlevelで起動されるStart up(rc$runlevel.d/S*)スクリプトと同じ動作 をする一つ前のrunlevel用のStart upスクリプトファイル名をprevious_start変数に代入し ている.
これらの変数は,85行目で使用される.つまり,現在のrunlevelで起動しようとしているプ ロセスが,一つ前のrunlevelでも起動されていて,且つ,現在のrunlevelに入ったときにそ のプロセスがkillされていないのなら,そのプロセスを改めて起動する必要はない.したがっ て,この時はcontinueによって,87行目以下を実行せず次のループへ進み(67行目に戻る), 次のStart upスクリプトの処理をする.
****Line 87--94
既に起動されているプロセスでなければ,いよいよ87行目以下でそのプロセスが起動される. これは,92行目で行われる.
しかし,runlevelが0(halt)か6(reboot)の時には,プロセスが起動してはまずいので rc$(runlevel).d/S*スクリプトであっても,stopを引数に指定してプロセスを終了させる.
以下がそのファイルの全てである.
/etc/init.d/skelton ファイル
1: #! /bin/sh 2: # 3: # skeleton example file to build /etc/init.d/ scripts. 4: # This file should be used to construct scripts for /etc/init.d. 5: # 6: # Written by Miquel van Smoorenburg |
startおよびstopの時の処理は,どちらもまず起動および停止する旨とそのプロセス名を画 面に表示する.そして,プロセスの起動は実際には,start-stop-daemonというDebianパッ ケージ用のデーモン起動終了プログラムを通して行われる.
これは,プロセスがデーモンの場合には有効だが,デーモンではなく単にユーザの書いたス クリプトを実行したいだけなら,そのスクリプト名を記述するだけで良く, start-stop-daemonプログラムを使用する必要はない.