前回 は/etc/rc.d/rc.sysinitという起動時に実行されるスクリプトを読んでみました。その際に簡単に触れたように、ほとんどのLinuxディストリビューションではsysvinit と呼ばれるソフトウェアを使ってシステムの起動処理を行っています。
sysvinitはSystemV initの略で、UNIX SystemV(システムファイブ)と呼ばれるAT&T社謹製の古典的なUNIXが採用した起動メカニズムと同じ動作をするように設計されたソフトウェアです。SystemV系のinitの特徴は「ランレベル 」という概念を持つことで、ランレベルの設定によりシステムの振舞いを柔軟に変えることができます。Linuxを始め、最近のほとんどのUNIX系OSではこのSystemV initを採用していますが、BSD系のUNIXではランレベルという概念を持たない独自のinitを伝統的に利用しています。
このsysvinitの処理を含めた起動時の流れを把握しておけば、何らかのトラブルが発生して正しく起動しなくなった場合にも原因追求が容易になるでしょう。そこで今回はsysvinitが起動するまでの流れを解説し、その際に使用される各種設定ファイルを読んでみます。
Linuxの起動の流れ
あらかじめお断りしておきますが、筆者はx86系(いわゆるIBM-PC互換機)以外のハードウェア(組み込み環境とか大型コンピュータとか)については無知なので、今回紹介する話題はx86系のハードウェアを前提とした話 になります。カーネルの起動以後の処理はそれほど違いはないはずですが、カーネルをロードするための仕組みはハードウェア環境に大きく依存しているため、組み込み用のLinuxや大型コンピュータ用のLinuxでは今回紹介するgrubとは異なる、それぞれ独自の仕組みが用意されているはずです。本連載ではそれらについては扱わないので別途適切な資料を探してください。
さて、いわゆるPC互換機の環境では、システムの起動は「ブートローダ段階」 、「 カーネル段階」 、「 /sbin/init段階」の3つのステップに分けることができます。「 ブートローダ段階 」とは、電源ONからliloやgrubといったブートローダソフトウェアが起動し、指定されたカーネルイメージをメモリに読み込む段階、「 カーネル段階 」とはメモリ上に読み込まれたカーネルが起動し、CPUやメモリ、周辺機器の認識や初期化を行う段階、「 /sbin/init段階 」とはハードウェアの初期化を終えたカーネルから処理を委ねられた/sbin/initが/etc/inittabの設定に従ってシステムの動作に必要な各種サービスを起動していく段階を言います。以下ではこれら3つのステップそれぞれについて、制御する設定ファイルなどを読みながら紹介していくことにします。
ブートローダ段階
PC互換機の電源を入れると、まずマザーボード上のフラッシュメモリに書き込まれたBIOS(Basic Input/Output System) と呼ばれるソフトウェアが動き始めます。BIOSはマザーボード上のCPUやメモリ、接続されている各種拡張カードを認識、初期化していきます。接続されたハードウェアの初期化処理を完了すると、最初のHDDの先頭のセクタ(MBR:マスターブートレコード )に書き込まえたソフトウェアを起動して以後の処理を委ねます。このMBRの512バイトの部分に書き込まれているのが「ブートローダ 」と呼ばれるソフトウェアです。
MS-DOSの時代には、BIOSはBasic Input/Output Systemの名称の通り、画面表示やハードディスクの読み書きなど、接続されているさまざまなメーカ製のハードウェアに対する操作を抽象化するために使われていました。しかし、BIOSはもともと16ビットCPU用に設計されているため、LinuxやWindows 95以降の32ビットCPU用のOSからは使用しづらく、それらのOSではBIOSを利用せずに直接ハードウェアを操作するようになったため、現在のBIOSは電源投入直後の周辺機器の初期化のためにのみ使われる程度になっています。一方、最近ではBIOSに代るEFI(Extensible Firmware Interface) と呼ばれる新しい規格が提案され、Intel版MacやIA-64(Itanium)など過去との互換性がそれほど重視されない環境ではPC互換機に先駆けて採用されています。
ブートローダはOSごとに異なり、Linuxの中でもliloやgrubなどいくつかの種類がありますが、今回は最も広く使われているgrub を取りあげます。
grubは起動するカーネルやカーネルパラメータを対話的なメニューから指定したり、さまざまな種類のファイルシステムを理解してカーネルをファイル名で読み出すことができるなど高機能なブートローダです。一方、MBRは512バイト分しか使えないので大規模なソフトウェアを保存することはできません。そのためgrubでは機能を分割し、本来の機能はstage2 と呼ばれる部分に置いてファイルシステム上に保存し、MBRにはstage2を読み込むだけの機能を持ったstage1 と呼ばれる部分を置くようになっています。
stage2はカーネルをファイル名で読み込めますが、stage1はstage2をHDD上の物理的な位置情報を用いて(BIOS経由で)読み込みます。そのためcpやmvでstage2の位置を変更すると、ファイルは存在していてもstage1が正しくstage2を読み込めなくなり、grubの起動に失敗することがあります。
MBR上のstage1がHDD上のstage2を正しく読み込めば、stage2はgrub.conf という設定ファイルを使って起動メニューを表示します。以下では前回同様、Fedora Core9のβ版の環境を元に設定ファイルの内容を紹介します(リスト1) 。Fedora Coreではgrub.confは/boot/grub/grub.confに実体が置かれ、/etc/grub.confにそこへのシンボリックリンクが置かれています。
リスト1 grub.confの内容
1 # grub.conf generated by anaconda
2 #
3 # Note that you do not have to rerun grub after making changes to this file
4 # NOTICE: You have a /boot partition. This means that
5 # all kernel and initrd paths are relative to /boot/, eg.
6 # root (hd0,0)
7 # kernel /vmlinuz-version ro root=/dev/VolGroup00/LogVol00
8 # initrd /initrd-version.img
9 #boot=/dev/sda
10 default=0
11 timeout=5
12 splashimage=(hd0,0)/grub/splash.xpm.gz
13 #hiddenmenu
14
15 title Fedora (2.6.25-0.163.rc7.git1.fc9.i686)
16 root (hd0,0)
17 kernel /vmlinuz-2.6.25-0.163.rc7.git1.fc9.i686 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
18 initrd /initrd-2.6.25-0.163.rc7.git1.fc9.i686.img
19 title Fedora (2.6.25-0.121.rc5.git4.fc9)
20 root (hd0,0)
21 kernel /vmlinuz-2.6.25-0.121.rc5.git4.fc9 ro root=UUID=7b908992-10bd-46d3-95b6-1e04548399ed rhgb quiet
22 initrd /initrd-2.6.25-0.121.rc5.git4.fc9.img
grub.confも他のほとんどの設定ファイル同様、#(シャープ)以降は行末までコメントと解釈されるので、1行目から9行目、13行目はコメントとして無視されます。コメント以外の部分は大きく2つに分けることができ、10行目から12行目がgrubの動作全体を制御する設定、15行目からが起動すべきカーネルごとの設定になります。
10行目のdefault=0
は、起動すべきカーネルが指定されなかった場合に起動するカーネルの指定で、数字はtitle行で指定された順番を意味し、0 ならば最初に現われたtitle行(今回は15行目)のカーネルを起動します。
timeout=5
はメニュー画面を5秒間表示する指定、splashimage=(hd0,0)/grub/splash.xpm.gz
はメニュー画面の背景に表示する画像ファイルの指定です。ここにも出てきていますが、grubではHDDやパーティションを/dev/hda1のような形ではなく、(hd0,0)
(最初のHDDの最初のパーティション)の形式で指定しますのでご注意ください。
15行目からが起動すべきカーネルの設定で、15行目から18行目と19行目から22行目がそれぞれ異なる2つのカーネルを起動するための設定になります。
title行 はgrubのメニュー画面に表示されるメッセージになると同時に、起動すべきカーネルとその設定を区別するためのラベルになります。
root行 はルートパーティションになるパーティションの指定で、今回の例では(hd0,0)
で最初のHDDの最初のパーティションを指定しています。この設定によりカーネルのファイル名を/vmlinuz-2.6.25.0...
のように、パス名で指定できるようになります。
最近のfedoraでは、インストーラの自動設定でLinux用のパーティションを作成すると、カーネルや起動用ramdisk(initrd)を保存するための200Mバイトほどのパーティションを最初に確保した上で、残りはLVM用のボリュームグループに割りあてるようになっています。確保された最初のパーティション(grubで言う(hd0,0))はハードディスク上の物理的なパーティションなのでgrubから直接読み出すことができますが、 残りの部分はカーネルのLVM機能を利用しないと読み出すことができません。
kernel行は起動するカーネルの指定で、(hd0,0)で指定されたパーティションの直下にあるカーネルイメージファイル/vmlinuz-2.6.25-0.163.rc7.git1.fc9.i686 をメモリに読み込むことを指定しています。カーネルのファイル名の指定以降は読み込んだカーネルに渡すパラメータの設定で、ルートファイルシステムとそのマウント方法(ro root=/dev/VolGroup00/LogVol00) 、起動時にはグラフィカルなブート画面を表示し(rhgb) 、詳しいメッセージは表示しない(quiet)ことをカーネルに指示しています。
/dev/VolGroup00/LogVol00 は最近のFedoraで採用されているLVM(Logical Volume Manager)上のファイルシステムの指定です。LVMとは従来のようにHDD上の物理的なパーティションを直接指定するのではなく、物理的なパーティションを抽象化した論理ボリューム上にファイルシステムを構築する仕組みです。LVMを使えば論理ボリュームを追加することでファイルシステムを必要に応じて拡張することが可能です。
また、ルートファイルシステムは21行目のようにUUID=7b908992.. のように、そのファイルシステムを作成する時に付けたID番号で指定することも可能です。
initrd行 はカーネルと共に読み込む初期化用ramdisk(INITtial RamDisk)イメージの指定で、上記の例ではカーネルと同じ場所にある/initrd-2.6.25-0.163.rc7.git1.fc9.i686.imgというファイルを指定しています。この初期化用ramdiskはシステムのインストール時に作成され、HDDやルートファイルシステムを読み込む際に必要となるモジュールドライバが組み込まれています。
grubはこの設定ファイルに従って指定されたカーネルと initrdをメモリ上に読み込み、指定されたパラメータをカーネルに渡して以後の処理をカーネルに委ねます。
カーネル段階
ブートローダから処理を委ねられたカーネルは、まず初期化用ramdiskを展開、マウントして、ルートファイルシステムをマウントするために必要なモジュールドライバ類を組み込みます。
初期化用ramdiskから組み込まれるモジュールドライバは使用しているSCSIアダプタやUSBコントローラに応じたドライバ、LVM構築に必要なドライバ、ext3ファイルシステム用のドライバなどです。なお、SCSIアダプタやUSBコントローラなどのハードウェア環境に応じたドライバは、インストーラがシステムインストール時に検出したハードウェアを元に用意されるので、initrdファイルをハードウェア構成が異なる環境に持っていくと動作しない場合があります。
初期化用ramdiskから必要なモジュールドライバを組み込んだカーネルは、それらのドライバも利用しながらハードウェアの認識や初期化を進めていきます。カーネルには動作を調整するための設定ファイルはありませんが、ブートローダから渡されるカーネルパラメータでさまざまな調整が可能になっています。
UNIX/Linuxでは、ほとんどのコマンドが設定ファイルで動作を調整することが可能なのに、なぜご本尊たるカーネルには設定ファイルが無いのでしょうか?
答は簡単で、設定ファイルを読みだすためにはカーネルの機能が必要になりますが、カーネル自身が初期化中の段階ではそのような機能を使えないからです。そのためカーネルの動作は起動時に与えるパラメータでさまざまに調整できるようになっています。また、最近では起動時ramdiskの段階でprocファイルシステムを用いて設定するようなスタイルもあるようです。
起動されたカーネルはCPUやメモリ、接続されている周辺機器の認識や初期化を行い、最終的にはカーネルパラメータで指定されたルートパーティションをマウントして、そこにある /sbin/initを起動し、その後の処理を委ねます。
カーネルはデフォルトでは/sbin/initを起動しますが、カーネルパラメータのinit=...オプションで起動するコマンドを変更することも可能です。たとえば、ブートローダからinit=/bin/bash
を指定してやると、カーネルは初期化終了後、/sbin/initではなく/bin/bashを起動して、システムを対話的に操作することが可能になります。このテクニックは正しく起動しなくなった環境を修復する際には便利ですが、rootのパスワードを知らなくてもroot権限での作業ができてしまうので、セキュリティの観点からは注意が必要です。
/sbin/init段階
ハードウェアの認識、初期化を終了したカーネルは/sbin/initを起動し、その後の処理を委ねます。/sbin/initに処理を委ねたカーネルは表の世界からは姿を消し、以後は黒子として世界を支える仕事に徹します。
一方、/sbin/initは全てのプロセスの祖として必要なプロセスを起動していきますが、その際に利用する設定ファイルが/etc/inittab (リスト2)です。
リスト2 /etc/inittabの内容
1 #
2 # inittab This file describes how the INIT process should set up
3 # the system in a certain run-level.
4 #
5 # Author: Miquel van Smoorenburg,
6 # Modified for RHS Linux by Marc Ewing and Donnie Barnes
7 #
8
9 # Default runlevel. The runlevels used by RHS are:
10 # 0 - halt (Do NOT set initdefault to this)
11 # 1 - Single user mode
12 # 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
13 # 3 - Full multiuser mode
14 # 4 - unused
15 # 5 - X11
16 # 6 - reboot (Do NOT set initdefault to this)
17 #
18 id:5:initdefault:
19
20 # System initialization.
21 si::sysinit:/etc/rc.d/rc.sysinit
22
23 l0:0:wait:/etc/rc.d/rc 0
24 l1:1:wait:/etc/rc.d/rc 1
25 l2:2:wait:/etc/rc.d/rc 2
26 l3:3:wait:/etc/rc.d/rc 3
27 l4:4:wait:/etc/rc.d/rc 4
28 l5:5:wait:/etc/rc.d/rc 5
29 l6:6:wait:/etc/rc.d/rc 6
30
31 # Trap CTRL-ALT-DELETE
32 ca::ctrlaltdel:/sbin/shutdown -t3 -r now
33
34 # When our UPS tells us power has failed, assume we have a few minutes
35 # of power left. Schedule a shutdown for 2 minutes from now.
36 # This does, of course, assume you have powerd installed and your
37 # UPS connected and working correctly.
38 pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
39
40 # If power was restored before the shutdown kicked in, cancel it.
41 pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
42
43
44 # Run gettys in standard runlevels
45 1:2345:respawn:/sbin/mingetty tty1
46 2:2345:respawn:/sbin/mingetty tty2
47 3:2345:respawn:/sbin/mingetty tty3
48 4:2345:respawn:/sbin/mingetty tty4
49 5:2345:respawn:/sbin/mingetty tty5
50 6:2345:respawn:/sbin/mingetty tty6
51
52 # Run xdm in runlevel 5
53 x:5:respawn:/etc/X11/prefdm -nodaemon
/etc/inittabも他の設定ファイル同様、#から行末までがコメントと見なされるので、17行目まではコメント、18行目が最初の設定行です。
/etc/inittabでは1つの行が1つの設定になっており、各行は :(コロン)で4つの欄に分割されています。各欄の意味は、最初の欄がそれぞれの設定を区別するためのラベル、2つめがその設定が動作するランレベルの指定、3つめがどのような動作を行なうかの指定、4つめが起動するプロセスの指定になります。
前回も簡単に触れましたが、「 ランレベル」というのはシステムの動作モードを意味し、レベルによって設定する機能やサービスを変えて、システムを安全かつ効率よく運用するための仕組みです。それぞれのレベルの意味はディストリビューションによって多少異なりますが、ランレベル0がシステムの停止モード、6が再起動モード、1がシングルユーザモードというのは共通しています。
それ以外のモードをどのように使うかはディストリビューションごとに異なり、Red Hat系のディストリビューションでは、9行目から17行目に記されているように、2が(セキュリティ等の問題がある場合に)NFS機能を使わないマルチユーザモード、3が標準のマルチユーザモード、4は未使用で、5がログイン時にもX Window Systemを用いるGUIモードになります。
3つめの動作欄にinitdefault と指定した18行目がデフォルトのランレベルの設定で、ここでは5がデフォルトのランレベルとなります。
動作欄にsysinitと指定した21行目がシステムの初期化処理の指定です。/sbin/initはこの行を見てシステムの初期化のために/etc/rc.d/rc.sysinit を起動します。
23行目から29行目が /etc/rc.d/rc.sysinitを実行してから、それぞれのランレベルで実行すべき処理の指定です。具体的には、それぞれのランレベル(0から6)を引数に /etc/rc.d/rc というスクリプトを実行するようになっています。これらの行では動作欄(3つめの欄)が wait とされており、指定したランレベルになった際に 4 つめの欄に指定したプロセスを実行し、プロセスの実行終了まで待つことを意味します。この処理により、それぞれのランレベルに応じたサービス(プロセス)を起動します。
32行目、38行目、41行目はそれぞれ特定の状態になった際の動作の指定です。32行目は、いわゆる「三本指の挨拶(three fingers salute) 」と呼ばれるctrl+alt+deleteの3つのキーが同時に押された際の動作の指定で、今(now)から3秒待ってから(-t3) 、シャットダウン後再起動するように(-r)指定して/sbin/shutdownコマンドを起動します。
38行目はUPSから電源断の信号が送られてきた際の処理で、2分後に(+2) 、"Power Failure; System Shutting Down" というメッセージをログファイルに記録して、再起動ではなく停止状態になるように(-h)/sbin/shutdown を起動します。-f の指定は次回の起動時にはファイルシステムのチェック(fsck)を動かさないようにする指定です。
41行目は電源断の信号が届いてからシステムをシャットダウンするまでの2分間に電源回復を示す信号が届いた場合にシャットダウンをキャンセルするための処理です。
45行目から50行目は2から5までのランレベルの際に/sbin/mingetty というプロセスを起動してtty1からtty6までの端末を監視させる指定です。ランレベルは0が halt、6がrebootなので、それ以外のランレベル(動作用のランレベル)ではtty1からtty6の6つの端末からログインできるようになります。
ここで言う「端末」はX Windows System上のgnome-terminal等の端末ソフトウェアではなく、コンソール画面上の仮想端末を意味します。Xを起動していない状態では、Altキーとファンクションキーを同時に押すことで仮想端末を切り替えることができ、45行目から50行目の指定でALT+F1からALT+F6までの6つの仮想端末が同時に利用できるようになります。なお、X上の端末ソフトウェアはここで言う仮想端末とは異なる仕組みで動いているので、6つという数字に制限されず、いくつでも同時に起動することが可能です。
これらの指定の動作欄に指定されているrespawn は、プロセス欄に指定したプロセスが終了すれば再度同じプロセスを起動することを意味します。この指定により、コンソール画面からログアウト(/sbin/mingetty を終了)しても、同じコンソール画面に再度ログインメッセージが表示(/sbin/mingettyの再起動)されるようになります。
53行目はX Window Systemを用いたログイン処理の指定で、/etc/X11/prefdm というコマンド(実体はシェルスクリプト)を用いて利用可能なディスプレイマネージャ(X用のログイン管理ソフトウェア)を起動しています。スクリプトの中では/etc/sysconfig/desktopの指定に基づいてGNOME用やKDE用のディスプレイマネージャを切り替えるようになっています。
以上、ざっと/etc/inittabの設定を眺めてきましたが、この設定ファイルによる/sbin/initの動作を整理してみます。
/sbin/init はまず18行目でデフォルトのランレベルが5 であることを認識します。一方、21行目のsysinit の指定で、まず最初に/etc/rc.d/rc.sysinit を起動します。その後、デフォルトのランレベルである5に入って、ランレベル5の際に実行すべき処理を実行していきます。まず 28 行目で/etc/rc.d/rc 5
を実行し、その終了まで待ちます(wait) 。
41行目にもランレベル5で実行すべき処理がありますが、この処理はpowerokwaitの指定でUPSから特別な信号が入ったという条件の際に実行することになります。
次に45行目から50行目の指定で/sbin/mingetty を6つの仮想端末に対して実行し、コンソールからのログインを待ちます。最後に53行目の指定により/etc/X11/prefdm 経由でX Window System用のディスプレイマネージャを起動します。その際にはXウィンドウやGNOME/KDEといったデスクトップ環境も必要になるので、それらも合わせて起動されることになります。
前回紹介したrc.sysinitはこの/sbin/initの処理の流れの中で、システムの初期化用に実行される最初のスクリプトというわけです。