前回紹介したように、シェルスクリプトは現在でもUNIX/Linux環境の基盤技術としてさまざまな場面で利用されています。なかでもシステム起動時の処理はシェルスクリプトの独擅場といってもいいでしょう。今回はこのシステム起動時に働くシェルスクリプトを読んでみましょう。
システムの起動、終了の仕組みは、パッケージ管理システムと共にディストリビューションの個性が反映される部分であり、関係するスクリプトの種類や構造などはディストリビューションごとに大きく異なっています。そのため、今回はFedora Core 9のβ版の環境を取りあげることにしました。Fedora Coreのシリーズの中ではスクリプトの機能や配置などの概要はそれほど変っていないとは思いますが、細部はバージョンによって多少異なるかも知れません。
Fedora Core の起動用スクリプトの構成
Fedora Coreに代表されるRed Hat Linux系のディストリビューションでは、システム起動時に働くスクリプト群は /etc/rc.d/以下に集められています。
Linuxの起動の仕組みについては回を改めて紹介するつもりですが、Fedora Coreが採用している sysvinit形式では、各種サービスの起動/終了スクリプトは/etc/rc.d/init.d/ディレクトリに集めて、そこからランレベルに応じたrc0.d/からrc.6.d/のディレクトリにシンボリックリンクを配置する、という構成になっています。これらのスクリプトはそれぞれのソフトウェアごとのrpmパッケージに収録されており、それらのパッケージをインストールすることで /etc/rc.d/init.d/ディレクトリに追加されます。
一方、/etc/rc.d/の直下にあるrcやrc.local、rc.sysinitは特定のランレベルではなくシステム全体を管理するためのスクリプトです。rcは指定したランレベルに応じて必要なスクリプトを走らせる機能を、rc.localは汎用的な設定には馴染まないそのシステム固有の設定処理を、rc.sysinitはシステムの起動時に一度だけ実行される処理を、それぞれ担っています。
これらの中から、今回はシステム起動時に最初に実行されるrc.sysinitスクリプトを眺めてみます。ただしrc.sysinitは900行近くある大きなスクリプトなので、要点だけを摘み読みすることにします。
必須のファイルシステムのマウント
まずは先頭部分です。
1行目はシェルスクリプトのお約束であるシェバン、2行目から6行目はコメントで、実際の処理を行うのは8行目からです。8行目から10行目ではHOSTNAME、HOSTTYPE、unamerという変数にそれぞれ/bin/hostname
やuname -m
(CPUの種類)、uname -r
(カーネルのリリース番号)の各コマンドを実行した結果を設定しています。前回も述べましたが、シェルスクリプトの中ではシングルクォート(')、ダブルクォート(")、バッククォート(`)の3種の引用符が使い分けられ、バッククォートの部分はコマンドとして実行した結果に展開されます。
12行目のsetはbashの動作を制御するためのコマンドで、set -m
はジョブ制御(指定したジョブを一時停止したり、バックグラウンドで実行させる機能)を有効にする指定です。このスクリプトは一部のジョブをバックグラウンドで実行するような処理をするため、この設定を明示しているようです。
14行目から16行目では/etc/sysconfig/networkというネットワーク関係の設定ファイルの有無を調べ(-f)、このファイルが存在すれば.(ピリオド)コマンドで読み込ませます。手元の環境では/etc/sysconfig/networkは以下のような内容になっていて、rc.sysinitを実行しているシェル環境の中にNETWORKINGという変数とHOSTNAMEという変数を追加(HOSTNAMEという変数は8行目で設定しているので上書き)することになります。
.(ピリオド)はbashの内部コマンドの一つで、指定したファイルを読み込んで、現在のシェル環境の中で実行します。. はsourceとも書くことできます。
17行目から19行目では、$HOSTNAMEという変数に何らかの値が設定されているかを改めてチェックして、設定されていなければlocalhostという値を設定しています。
21行目から24行目では、/proc/mountsというファイルが存在するかをチェック(-e)して、存在しなければ/procと/sysをマウントしています。
25行目から29行目も同様に/proc/bus/usbというディレクトリの存在をチェック(-d)し、存在しなければusbcoreというモジュールをロードした上でUSBファイルシステムという仮想ファイルシステムを/proc/bus/usbにマウントしています。
31行目は15行目と同様 .(ピリオド)コマンドを使って/etc/init.d/functionsというファイルを読み込んでいます。/etc/init.d/functionsには/etc/rc.d/以下にあるシェルスクリプト群が使う関数をまとめて定義してあり、このファイルを読み込めばそれぞれのスクリプトごとに関数を定義する手間を省けるようになっています。
バナーメッセージの表示
この先の32行目から230行目あたりはセキュリティを強化するSELinuxや、ファイルシステムを暗号化するcrypto-loopの設定で、シェルスクリプトというよりはそれぞれのシステムの専門的な話になるため割愛し、232行目からを眺めてみます。
この部分は/etc/redhat-releaseというファイルを$redhat_releaseという変数に読み込んで、その内容に応じて、メッセージを"Welcome to Red Hat"か"Welcome to Fedora"に切り替える処理になっています。
echo -en "\\033[0;31m"
の部分は、エスケープシークエンスと呼ばれるコンソール画面の表示色の指定です。ここではRed HatやFedoraという名前を強調するために文字の色を赤(31)か青(34)に変更し、39で元に戻しています。
236行目に見られる$BOOTUPは/etc/init.d/functions経由で読み込まれる/etc/sysconfig/initに設定されている変数です。/etc/sysconfig/initには$BOOTUP以外にもさまざまな変数が設定されており、それらを使って起動時の振舞を細かく調節できるようになっています。
モジュールドライバの読み込み
256行目くらいからは雑多な起動時処理という感じですが、ざっと眺めて行きます。
256行目から259行目は$LOGLEVEL
という変数が設定されているかを調べて、設定されていれば/bin/dmesg -n $LOGLEVEL
というコマンドを実行します。$LOGLEVEL
という変数も236行目にあった$BOOTUP同様、/etc/sysconfig/initの中で定義されています。
/bin/dmesg
の-n
オプションはカーネルのログメッセージのうち、コンソール画面に出力されるメッセージレベルを指定するもので、0から7の数字を指定でき、数字が大きくなるほど詳細なメッセージがコンソール画面に出力されるようになります。
262行目は$( .. )
内のコマンドの結果を$cmdlineという変数に代入する処理で、右辺は`cat /proc/cmdline`
と同じです。/proc/cmdline
は起動時にブートローダからカーネルに与えられるパラメータを記録しているファイルで、これらの情報はルートファイルシステムの位置やマウントオプション、カーネルの機能やシステムの動作を調整するために利用されます。
264行目以降はモジュールドライバを読み込んで各種ハードウェアを利用可能にするための処理です。267行目で実行しているsysctlコマンドは/procファイルシステム経由でカーネルの動作を変更するコマンドで、ここではカーネルが必要なモジュールを組み込む際に利用するコマンドの指定であるkernel.modprobeに/sbin/modprobeを設定しています。もし起動時カーネルパラメータとしてnomodulesが指定されると270 行目のようにkernel.modprobeに/bin/trueが設定され、カーネル自身によるモジュールの読み込み処理はできなくなります。
少し飛ばして、290行目で実行している/sbin/start_udevは、最近のLinuxで採用されているudev機能を開始するコマンドです。udevとは、アプリケーションがハードウェアを操作する際に利用するデバイスファイルを動的に生成する仕組みで、udevdというデーモンがカーネルの認識してしているハードウェア情報を利用して、それぞれのハードウェアに応じたデバイスファイルを/dev以下に作っていきます。この/sbin/start_udevは必要な環境を整えてudevdを起動するためのコマンドです。
通常はカーネルが自動認識して組み込むモジュールドライバだけで十分なはずですが、自動的には組み込まれないモジュールドライバを組み込ませたい場合は 293行目に見られるように/etc/sysconfig/modules/の下にmy.modulesのようなファイルを用意して、そこで必要なモジュールを組み込む指定をしておけば、rc.sysinitの中で組み込んでくれます。また、/etc/rc.modulesというファイルでモジュールを組み込むことも可能です。
対話モードの確認とrc.sysinitの終了
この後、Red Hat系Linuxに独自のrhgbというコマンドを起動してXウィンドウを用いた綺麗な画像を表示し、ユーザにはプログレスバーによる進捗表示だけを示しながら、処理は背後で進めていきます。rc.sysinitの中ではLVMを用意したり、ファイルシステムのチェックをしたりと大忙しになりますが、それらの部分はばっさりと飛ばして、最後の部分だけを見てみます。
このあたりまでにファイルシステムのチェックとマウントが終了し、各種ログファイルの初期化やスワップファイルの設定なども済ませて、ほぼ基本的なコマンド類は利用可能になっています。
828、829行目ではすでに同じ名前のファイルがあれば別名で保存した上で、dmesgコマンドでここまでのカーネルのログを/var/log/dmesgに保存しています。
832行目では/.autofsckという隠しファイルを作ります。このファイルは、haltやshutdownコマンドによりシステムが正常に終了した場合には削除されるようになっており、もし起動時にこのファイルが残っていれば、何らかの理由で前回は正常終了していないことから、ファイルシステムをチェックした方がいいことがわかります。今回は省略しましたが、rc.sysinitの375行目からがそのためのチェックになっています。
834行目から851行目はキーボードからの入力をチェックする部分です。今回読んでいるrc.sysinitを採用しているRed Hat系のディストリビューションでは、rc.sysinitの動作中にiを入力すると、rc.sysinit以降で各種サービスを起動するかどうかを手動で指定できるようになっており、この部分がそのためのチェックになっています。
具体的には847行目の/sbin/getkey i
でiが入力されたかを調べ、入力されていれば/var/run/confirmというファイルを作り、848行目で/var/run/getkey_doneを作成して入力チェックが終わったことを他のプロセスに知らせます。作成された/var/run/confirmファイルは、rc.sysinitの次に起動されるスクリプトから参照され、起動すべきサービスごとに確認を求めるようになります。
シェルスクリプト的に見ると、(リストを省略した)771行目から842行目が中括弧({ })で囲われたグループコマンドとして別プロセスで実行され(842行目の&)、そちらのプロセスと850行目のwaitで同期を取りながらキーボードからの入力チェックを行う、という複雑な作りになっています。このような構成を取った理由はよく分かりませんが、独立性の高い処理を並列化することで処理速度を上げるような意図があるのでしょう。
最後の853行目から856行目は、グラフィカルな画面を表示しているrhgbにrhgb-client経由でrc.sysinitが終了したことを伝えています。このコマンドを受けた rhgb はXウィンドウでのキーボードの設定を行なって対話モードの入力に備えます。
以上、駆け足でrc.sysinitスクリプトを眺めてみました。rc.sysinitが終了すれば、/sbin/initがランレベルを引数にして/etc/rc.d/rcスクリプトを起動し、/etc/rc.d/rcスクリプトがそれぞれのレベルに応じたサービスを起動してシステムの準備を整えていきます。それらの過程については次回にでも読んでみる予定です。