GNU/Linux でよくある苦情 (手ごろなカーネル・デバッガーがないことは別として) は、オペレーティング・システムが起動するまでにかかる時間です。このプロセスはブートとしてひとまとめにすることもできますが、実際には、電源が入っていない状態のシステムからシェルやウィンドウ・マネージャーを通して対話可能なシステムにするまでには、いくつかの個別のタスクが関わっています。この記事では、このLinux のブートおよび初期化プロセスについて検討します。
Linux をブートするには多数のステップが伴いますが、このプロセスは図 1 に示すように、BIOS、カーネル・ブート、そしてシステム初期化という3 つの基本ステップに区分することができます。
図 1. Linux ブート・プロセスの時系図
コンピューターの電源を入れたり、あるいはリセットを行うと、コンピューターのプロセッサーがいわゆる BIOS (基本入出力システム) と呼ばれているよく知られた場所で実行を開始します。BIOSは通常、システムのマザーボード上にあるフラッシュ・メモリー・デバイスに格納されています。BIOS には、基本コンポーネント (システムのメモリーなど)の初期テストを実行したり、オペレーティング・システムのブート方法を決定するなどの多くのジョブがあります。PC ベースのコンピューターは極めて柔軟なため、ブート・デバイスは、マザーボードに接続された多数の個別デバイス(ハード・ディスクなど) のうちの 1 つにすることも、CD-ROM、あるいはネットワーク・インターフェールなどの別のデバイスにすることも可能です。
ブート・デバイスの決定プロセスは、最もよくブートに使用するデバイス (一般的にはハード・ディスク) を選択することで最適化できます。一方、BIOSの段階で一番時間がかかるのは、間違いなくメモリーのテストです。このテストの特定の側面 (完全なメモリー・テストなど) を無効にすれば、ブート速度には確かに有効ですが、ブート時のシステム保全性テストが犠牲になります。
ブート・デバイスが見つかると、Linux カーネル・ブート・プロセスが開始されます。このプロセスは (おおまかに言うと) 第一段階ブートおよび第二段階ブートという2 段階で行われます。第一段階を構成するのは (ブート・デバイスのマスター・ブート・レコード (MBR) にある) 単純なブート・ローダーで、このブート・ローダーのジョブは第二段階のブート・ローダーをロードすることです。第一段階のブート・ローダーは、パーティション・テーブルを使用して、第二段階のブート・ローダーを見つけます。第一段階のブート・ローダーは、このテーブルをスキャンしてアクティブなパーティションを検索し、パーティションを見つけると第二段階のブート・ローダーをRAM にロードして起動します。
RAM にロードされた第二段階ブート・ローダーにより、Linux カーネル・イメージと初期 RAM ディスク・イメージ (initrd) が RAM にロードされます。カーネルは起動されると、それ自体を高位メモリーに解凍し、initrdをコピーして後でマウントして使用できるようにします。
カーネル・ブート・プロセスはかなり複雑なものですが、コードの大部分がシステムのマシン言語で作成されているため、非常に高速に行われます。カーネル・ブート・シーケンスが終わると、次に開始されるのはinit プロセスです。init は Linux システムで作成される最初のプロセスであるため、その他すべてのプロセスの親になります (つまり、すべてのプロセスはinit の子孫となります)。
この記事の焦点である init プロセスは、カーネル・ブート・シーケンスの完了時に作成される最初のプロセスです。Linux は init プロセスを使用してサービスおよびアプリケーションを初期化し、Linux を使用できる状態にします。
init プロセスは開始時に、/etc/inittab という名前のファイルを開きます。このファイルは init の構成ファイルで、システムの初期化方法を定義します。さらに電源障害が発生した時の措置 (システムがサポートする場合) に関する情報や、Ctrl-Alt-Deleteキー・シーケンスを検出した場合にどのように対応するかについての情報もこのファイルに含まれています。ファイルが定義する内容を理解するには、リスト 1 に記載するファイルの一部を見てください。
inittab 構成ファイルが定義する複数のエントリーに使用されている共通フォーマットは、id:runlevels:action:process です。idは該当エントリーを一意に識別する文字列で、runlevels はアクションの実行対象となる runlevel を定義します。action は実行する特定のアクションを指定し、processは実行プロセスを定義します。
リスト 1. inittab ファイルの抜粋
# The default runlevel
id:2:initdefault
# Boot-time system configuration/initialization script
si::sysinit:/etc/init.d/rcS
# Runlevels
l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
z6:6:respawn:/sbin/sulogin
# How to react to ctrl-alt-del
ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
|
init は /etc/inittab をロードした後、システムを initdefault アクションで定義された runlevel に引き上げます。リスト 1 を見るとわかるように、ここで定義されているのは runlevel 2 です。runlevel はシステムの状態のようなものだと考えてください。例えば、runlevel0 はシステム停止状態で、runlevel 1 はシングル・ユーザー・モードです。runlevel 2 から 5 はマルチユーザー状態で、runlevel6 はリブートを示します (一部のディストリビューションでは runlevel の表記が異なることに注意してください)。別の見方をすれば、runlevelは実行可能なプロセス (システムの状態を定義するプロセス) を定義する 1 つの手段とも考えられます。
注: システムの現行 runlevel を調べるには、runlevelコマンドを使用してください。
リスト 1で定義されているように、initdefault ではデフォルトの init レベルを 2 (マルチユーザー・モード) に指定しています。初期 runlevel が定義されると、rc スクリプトが引数 2 (runlevel) で呼び出され、システムを起動します。rc スクリプトは次に各種のサービスおよびアプリケーション・スクリプトを呼び出して特定の要素を起動あるいは停止します。これらのファイルは/etc/rc2.d/ 内に定義されています。例えば、MySQL アプリケーションが起動されるとすると (システム始動など)、/etc/rc2.d/S20mysql start として呼び出されることになります。システムのシャットダウン時には、同じ一連のスクリプトが今度は stop 引数を使って呼び出されます。
最後に多数のスクリプトが順次実行され、必要な各種のサービスを開始します (Linux では通常、この様子はブート画面の一部として表示されます)。サービスが互いに関連していないとしても、これらのサービスは1つひとつ開始されるため、その結果、このプロセスには時間がかかる場合があります (特に多くのサービスを持つ大規模なシステムの場合には顕著です)。
この問題に対する明らかなソリューションは、init コマンドのシリアルに処理される性質を取り除き、並行して動作するような方法に置き換えることです。このような方法が使用されているのはマルチプロセッシング・システムだけではありません。例えばソケット・ストライピング(複数のソケットを使用してデータを並行して移動) は、このテーマを基にしたソリューションです。RAID (Redundant Array ofIndependent Disk) システムも、(通常は並行して) 複数のディスクを使用してストライプを行い、入出力のパフォーマンスを向上させています。
従来の init プロセス (sysvinit) はシリアル処理であるため、最適化に直結するのはシステムのこの部分です。実際、init プロセスの最適化には何通りもの方法があります。この記事ではこれから、そのうちのいくつかの方法を取り上げ、それぞれの方法がどのように問題を解決するかを説明していきます。最初の方法は依存関係に基づくもので(依存関係を使用して並列化を実現する方法)、2 番目の方法はイベント・ベースのシステムです (プロセスが、プロセスの開始または停止を指示するイベントに依存する方法)。
最初に紹介する initng (次世代の initng) は initng に完全に置き換わるオプションで、プロセスを非同期に開始して init プロセスをより短時間で完了させます。この記事を書いている時点では、initng はベータ版の製品で、作成者は Jimmy Wennlund です。
initng の根底にある概念は、サービスの依存関係が満たされ次第、そのサービスを開始するというものです。このようなシステムによって、CPU と入出力はよりバランスのとれた関係となります。つまり、あるスクリプトがディスクからロードされている間、あるいはハードウェア・デバイスの起動を待機している間に、別のスクリプトを実行して別のサービスを開始できます。
initng は依存関係をベースとしたソリューションとして、サービスとデーモンの依存関係をエンコードする独自の初期化スクリプトのセットを使用します。リスト 2 はその一例です。このスクリプトは、特定の runlevel に対して開始するサービスを指定しています。このサービスには need キーワードで定義されているように、system/initial、そして net/all という 2 つの依存関係があります。この両方のサービスが使用できる状態になっていないと、system/my_serviceを開始できません。サービスが使用可能になると関わってくるのは exec キーワードです。start オプションを指定した exec キーワードは、サービスの開始方法を有効なオプションと一緒に定義します。サービスを停止するときには、stop オプションを指定した exec キーワードが使用されます。
リスト 2. initng でのサービス定義
service system/my_service {
need = system/initial net/all;
exec start = /sbin/my_service --start --option;
exec stop = /sbin/my_service --stop --option;
}
|
リスト 2 に示すように、サービス定義によってシステム全体をエンコードすることができます。依存関係を持たないサービスは即時に (並行して) 開始できますが、依存関係を持つサービスは安全に開始できるようになるまで待機します。initngはいわば目標ベースのシステムで、その目標とはサービスを起動させることです。明示的な計画は一切ない代わりに、依存関係によって単にサービス開始のフローが定義され、プロセス内で暗黙的に並列化が行われます。
一般的な用途の場合、initngパッケージは比較的簡単にインストールできますが、標準以外のパッケージ (デフォルト構成に含まれないもの) を使用するシステムについては、何らかのアセンブリーが必要になることがあります。
initng の標準的なインストールで必要なのは、initng ディストリビューション (ソースまたはバイナリー) と ifiles ディストリビューションです。initng ディストリビューションは、./configure、make、および make install でビルドできます。ifiles (スクリプト・ファイル) をビルドするには、cmake を使用します。システム要件によっては、(initng コミュニティーの誰かがすでに作成している可能性もありますが) 新しいサービス/デーモン定義を作成して、新しい /sbin/initng を指定するようにLILO または GRUB 構成を変更しなければならない場合もあります。
initng の制御には、ngc (従来の init では telinit) を使用します。telinit と構文は多少異なりますが、機能には変わりありません。
init の代わりとなるもう 1 つの選択肢である upstart は、上記で説明した initng とは異なる方法を採ります。Upstart は init をイベント・ベースに置き換えたものなので、サービスの開始と停止はイベントの通信に基づきます。Upstart は Ubuntu ディストリビューション用に Scott James Remnant によって開発されている段階ですが、あらゆる Linuxディストリビューションで汎用的に init の代わりとなるように意図されています。
Upstart を使うには、イベント・ベース・モードの操作をサポートするために初期化スクリプトを更新しなければなりません。Upstart は、(その他すべての方法と同じく) システム起動時に開始する独自の init プロセスを維持します。init が最初に出力するのは startupイベントです。これは、init の 2 つのコア・イベントのうちの 1 つで、init はシステム起動時には startup イベントを出力し、システムがシャットダウンするときには shutdown イベントを出力します。その他のコア・イベントには、Ctrl-Alt-Deleteキーが押されたことを示すctrlaltdel、Alt-Up 矢印キーが同時に押されたことを示す kbdrequest があります。
これ以外の用途で新しいイベントを作成することもできます。例えば、myevent という任意のイベントを作成し、echo コマンドでこのイベントを受信したことを示すことができます。それには、以下の短いジョブを使用します。
on myevent exec echo myevent received console output |
上記のコードでは、myevent イベントの受信時にジョブがトリガーされるように指定しています。ジョブがトリガーされると、コードは指定されたアクションを実行します(コンソールにテキストを出力)。このファイルを upstart 構成 (/etc/event.d) に含めると、以下の initctl ユーティリティーを使ってこのジョブをトリガーできるようになります。
initctl emit myevent |
upstart のスクリプト・ファイルは従来の rc init ファイルと同じように機能しますが、非同期イベントに応じて自律的に動作するという点が異なります。リスト 3 に 3 つのイベントを受信する単純なスクリプトの例を記載します。このスクリプトが受信するイベントは、ジョブを開始させる startup、そしてジョブを停止させるshutdown および runlevel-3 の 3 つです。シェルが、ジョブの script 部分のコンテンツを実行します (エラー時にスクリプトを終了する -e オプションを使用)。
リスト 3. sysvinit rc 2 スクリプトの簡易 upstart スクリプト
start on startup
stop on shutdown
stop on runlevel-3
script
set $(runlevel --set 2 || true)
exec /etc/init.d/rc 2
end script
|
initctl ユーティリティーが提供する機能は telinit と同様ですが、upstart に固有の機能がいくつか追加されています。上記で見たように、initctl は upstart に対するイベントを生成する emit オプションと一緒に使用できます。また、ジョブの状態を識別してシステム動作の詳細を示す list オプションもあります。このオプションを使用すると、現在どのジョブが待機中で、どのジョブがアクティブなのかがわかります。initctl ユーティリティーではさらに、デバッグを目的とした受信イベントを表示することもできます。
upstart が init の代用として興味深いのは、init に明らかに勝るいくつかの利点があるろことです。まず、システムは使用可能なハードウェアだけでブートするため、runlevel が必要な理由はなくなります。存在しないハードウェアが、そのハードウェアを必要とするジョブをトリガーすることがないためです。また、upstartはホット・プラグ・デバイスにも対応します。例えばシステムがブートした後、しばらくしてから PCMCIA ネットワーク・カードを接続すると network-interface-addedイベントが生成され、このイベントにより DHCP (Dynamic Host Configuration Protocol) ジョブがネットワーク・カードを構成してnetwork-interface-up イベントが生成されます。デフォルト・ルートが新しいインターフェースに割り当てられると、default-route-upイベントが生成されます。この時点で、ネットワーク・インターフェースを必要とするジョブ (メール・サーバーや Web サーバーなど) が自動的に開始するというわけです(また、インターフェースが無くなった場合は停止します。)。
upstart を作成してインストールする方法は簡単で、一般的な configure、make、make installパターンに従います。Upstart には、通常の init 構成の runlevel と互換する一連のサンプル・ジョブが用意されています。initng と同じく、新しいアプリケーションにはそれぞれ固有の要件 (新規イベントを追加する可能性) に応じて作成された独自のジョブが必要となります。upstartと initng のいずれにしても、新しい init システムをデプロイするには何らかのリスクが伴いますが、upstart の提供する利点がそのようなリスクや必要となる追加作業を上回ることは間違いありません。
上記で説明したように、initctl ユーティリティーは telinit に期待される機能だけでなく、トレースやデバッグのための追加機能も提供します。
選択肢は、この記事で取り上げた initng と upstart の 2 つだけではありません。init に代わる選択肢としては、runit、pardus、minit、einit もあります。いずれの選択肢にも支持者がいて、Linux コミュニティーではある程度の勢いがありますが、現時点で注目に値するのはおそらく upstart だと思います。なぜなら upstartt は人気の Ubuntu ディストリビューションで init の代わりとして採用されているからです。詳細については、「参考文献」を参照してください。
システム・ブート・プロセスの全体を変える際に役に立つのは、プロセスの変更内容とその変更がブート全体に要する時間にどの程度影響があるかを理解することです。ブート・プロセスの構成は、ZigaMahkovec が作成した bootchart という非常に便利なツールによって視覚化できます。このツールは、データ・ロガー・ユーティリティーや視覚化ユーティリティーをはじめとする複数の要素で構成されています。
データ・ロガー (bootchart) は init プロセスの代わりに実行されます (通常は、grub または lilo.conf ファイルに指定)。bootchart が初期化を行うと、制御は実際の init プロセスに戻されます (通常は /sbin/init)。bootchart は基本的には定期的 (デフォルトでは 200 ms 毎) に環境をサンプリングするプロファイラーです。環境のサンプリングとは、現行の CPU統計、入出力およびアイドル時間、ディスク使用率、そしてあらゆるアクティブ・プロセスについての情報を (proc ファイル・システムを介して) 読み取ることです。このデータは、後処理のために一時ファイル (/var/log/bootchart.tgz)に保管されます。
bootchartd は次に後処理ツールを使用して生データをブート・チャートに変換します。このプロセスは Java™ アプリケーション (bootchartd ディストリビューションの一部) を使用してローカル側で行うこともできますが、それよりも簡単な方法は、bootchart ホーム・ページにある Web フォームを使用することです。一例としてブート・チャートの抜粋を図 2 に示します。ブート・チャートは、開始されたサービスとアプリケーションの数によってはかなり大きなサイズになることに注意してください。完全な例については、「参考文献」にリンクが記載されています。
図 2. bootchartd で作成されたブート・チャートの抜粋
Linux 自体と同じく、ブート時間の最適化にはさまざまなオプションと高い柔軟性があるため、initng のような依存関係ベースのソリューションから upstart のようなイベント・ベースのソリューションまで、ニーズに合った最適化ソリューションが必ずあるはずです。bootchart パッケージを使用すれば、システムのブート時間がどの部分で長くかかっているのかを一層理解して、さらに最適化を図れます。
学ぶために
- 各種 Linux ディストリビューションの観点から見たシステム管理については、developerWorks の記事「Differentiating UNIX and Linux」を読んでください。
- 少々古くはなっていますが、この Red Hat 資料では Linux のさまざまなrunlevelsを説明しています。
-
developerWorks Linux ゾーンには、Linux 開発者用の資料が豊富に揃っています。
-
developerWorks technical events and Webcastsで最新情報を入手してください。
製品や技術を入手するために
-
次世代
initシステム (initng)は、initシステムに代わる依存関係ベースの手法です。 -
Ubuntu
upstartシステムは、initシステムに代わるイベント・ベースの手法です。 -
Bootchartは、ブート・プロセスのパフォーマンスを分析および可視化するツールです。このツールでは、システム初期化プロセス中にパフォーマンス・データを収集し、データを後処理して時系列にします。 -
einitパッケージは初期化スクリプトに対する別の手法で、構成ファイルに XML (Extensible Markup Language) を使用します。 -
Pardusも興味深い並列化方法です。この方法は、Linux ブートの連続性を取り除くだけでなく、Python 言語の使用によって柔軟性を加えます。 -
runitパッケージはサービス監視によるinitの代替案です。 -
minitパッケージは小規模ながらも、initシステムの完全バージョンです。sysvinitソースについても調べてみてください。 -
Linux SEK を注文してください。この 2 枚組 DVD セットには、Linux 対応の DB2、Lotus®、Rational®、Tivoli®、そしてWebSphere® の最新 IBM トライアル・ソフトウェアが収録されています。
- developerWorks から直接ダウンロードできる IBM トライアル・ソフトウェアを使用して、Linux で次の開発プロジェクトを構築してください。
議論するために
-
developerWorks blogsからdeveloperWorks コミュニティーに加わってください。
