RedHat系Linuxの起動ファイル |
YAMAMORI Takenori ●yamamori |
このページでは,RedHat系Linuxの起動ファイルについて解説します. なお,解説にあたってはVine Linux 2.1.5をもとにしていますが, RedHat Linux 7.x系などでもほぼ同様であることを確認しています.
前章での解説のとおり,Linuxで使用されている/sbin/initはSysV系のinitであり, これは/etc/inittabの記述をもとに動作します. さらにRedHat系Linuxの場合,inittabの記述によって起動されるrcスクリプトが, さらにrc3.dなどのディレクトリ以下にある,Sで始まる多数のファイルを, startの引数を付けながら順番に起動するという,SysV系本来の方式を用いています. これは,SysV系のinitを使いつつもrcスクリプトはBSD風になっている, Slackware系(Plamoなど)とは異なる点です.
BSD系のrcスクリプトでは,処理内容が少数のファイルにまとめられており, その中心となる/etc/rcを上から順に読んで行けば処理の流れを把握することができ, 中で実際に実行されているコマンドを確認することも比較的容易にできます. 一方SysV系rcスクリプトでは,スクリプトが多数の細かいファイルに分かれているため, その流れを追いかけるのは大変です.さらに,RedHat系の場合, 個々のrcスクリプト中からさらに別のディレクトリにあるスクリプトを読み込んだり, あるいはrcスクリプト内だけで有効なシェル関数を定義したりしているため,
「結局どの順に何が実行されているのか」
ということが非常にわかりにくくなっています. たとえば,詳しくは後述しますが,ネットワークインターフェイスを立ち上げるための ifconfigコマンドがどの時点で実行されているかを,RedHat系の/etc以下を追いかけて 調べようとするとかなり苦労するはずです.
このように,SysV系rcスクリプトは流れを読むには不向きであると言えますが, 一方でrcスクリプトをパッケージごとに細かく分けて管理できるというメリットが あります.たとえば,新しいアプリケーションをインストールし, そのアプリケーションのためのrcスクリプトが必要な場合, SysV方式ならば,既存のrcスクリプトを編集せずに, rc.dディレクトリ以下に追加のrcスクリプトを配置するだけで済みます.
なお,initの動作については「man init」で, inittabの書式については「man inittab」で, オンラインマニュアルを読むことができます.
RedHat系initとrcスクリプトの動作の全体像をつかむには, /etc以下の関連するファイルやディレクトリについて, ディレクトリ構成図を描いてみるのがよいでしょう. さっそく下図を御覧下さい.
*RedHat系Linuxの起動ファイル関連図(クリックでPDFファイル)
図では,inittabから起動されるrcスクリプトや, /etc/sysconfig以下にあるファイルのうちネットワーク関連のファイルについて, 各種ファイルの関係をまとめてあります. ここではランレベル3で起動されることを想定していますが, ランレベル5であっても内容はほとんど同じです. なお,実際には/etc/rc.d/rc3.dディレクトリ以下にはもっと多数のファイルが 存在しますが,これらについては説明の便宜上,省略しています.
この図での処理の流れの概要は,
なお、以下の解説はすべてPDFファイルの図をもとにしています。
カーネルによって/sbin/initが起動されると,initは/etc/inittabを読み込み, その記述内容にしたがって動作を開始します.(PDF図(1)) inittabの各行は「:」(コロン)で区切られた4つのフィールドで構成されており, 次のような形式になっています.
ラベル:ランレベル:アクション:プロセス
この中の3番目のアクションフィールドは, 実際にはinitdefault,sysinit,wait,respawnなどのキーワードになります. これらのうちinitdefaultはデフォルトのランレベルを指定するためのもので, PDF図(2)ではデフォルトのランレベルは3に設定されます.
inittabファイルは基本的には上の行から順に, 条件に合致した行が実行されて行きますが, initの起動直後には,アクションフィールドがsysinitと記述された行が まず最初に実行されます. この場合,この行の4番目のプロセスフィールドの記述にしたがい, /etc/rc.d/rc.sysinitが実行されることになります.(PDF図(3)) rc.sysinitはシェルスクリプトであり,その中では,ランレベルに関係なく, まず最初に実行しなければならないシステムの初期設定が行なわれます. なお,sysinitはinitの起動直後に1度だけ実行されるものであり, システム稼働中は(ランレベルが変更されても)再度実行されることはありません.
sysinitの実行後は,ランレベル別の動作になります. ここではランレベル3で起動されているため,inittabの各行のうち, ランレベルのフィールドが3に指定された行が上から順に実行されます. この場合はまず,PDF図(10)の行が該当するため, この記述にしたがって「/etc/rc.d/rc 3」というプロセスが実行されます. (ここで仮に,起動するランレベルが3ではなく5であったとすると, 代わりにその2行下の「/etc/rc.d/rc 5」の方が実行されたはずです) PDF図(10)ではアクションフィールドがwaitと記述されているため, initはinittabのほかの行の実行に移る前にこのプロセスの終了を待ちます.
/etc/rc.d/rcはシェルスクリプトであり,その中から, ランレベル3の場合は/etc/rc.d/rc3.dディレクトリ以下にある KやSで始まるスクリプトを実行します. なお,/etc/rc.d/rcはランレベル0〜6まですべて共通で呼び出され, ランレベルを表す数字を引数に付けることによって どのランレベルで呼び出されたのかを区別するようになっています.
rcスクリプトの実行終了後,initは再びinittabの読み込みを続け, 条件に合致した行としてPDF図(23)の行(計6行)を見つけます. これらの行の記述により/sbin/mingettyが起動され, コンソールに「login:」のプロンプトが表示されるのです. これらの行では,ランレベルのフィールドに「2345」と複数のランレベルが 記述されていますが,この中にランレベル3も含まれているため条件に合致します. mingettyの行が6行もあるのは,[Ctrl]+[Alt]+[ファンクションキー」で切り替わる 6つの仮想コンソールそれぞれにmingettyを起動するためです. 仮想コンソールが6つも必要ない場合は,これらの行のうち不要な分を「#」で コメントアウトすれば仮想コンソールの数を減らすことができます. なお,これらの行のアクションフィールドはrespawnとなっているため, initは(rcスクリプトの場合とは違って)プロセスの終了を待たずに処理を進め, かつ,プロセスが終了した場合には同じプロセスを再度起動します. この仕組みによって,ユーザがコンソールからログアウトしたあとに, 再び「login:」のプロンプトが表示されるのです.
ここではランレベル3で起動しているため,inittabの処理は前項のmingettyの 起動までで終了します.しかし,もしランレベル5で起動した場合は,さらにPDF図(24)の 行が条件に合致し,この記述によってグラフィカルログイン画面が起動されます. この行ではプロセスとして「/etc/X11/prefdm -nodaemon」と記述されていますが, prefdmはRedHat系で用いられるシェルスクリプトであり, wdm,gdm,kdm,xdmなどの中から,設定により好みのグラフィカルログインプログラムが 起動されるようになっています.ここで「-nodaemon」というオプションは, xdmなどのプログラムをフォアグラウンドで動作させるためのもので, inittabのrespawnのアクションが正常に動作するために必要です. なお,ランレベル5の場合であってもランレベル3と同様にmingettyは6つとも 起動されており,グラフィカルログイン画面はその次の7番目の仮想コンソールに 立ち上がります.
それではrc.sysinitの動作を詳しく見て行きましょう. rc.sysinitでは,システム起動時にまず必要な初期設定が行なわれます. 実際のスクリプトでは,最初に「.」コマンドで, ホスト名などが記述された/etc/sysconfig/networkと, シェル関数の定義などが行なわれている/etc/rc.d/init.d/functionsというファイルが 読み込まれます.(PDF図(4),(5)) その後,echoコマンドで「Welcome to Linux」などのメッセージを表示したあと (PDF図(6)),システムクロックの設定(PDF図(7)),ホスト名の設定(PDF図(8)), rootファイルシステムのfsckとremount(PDF図(9))といった処理が行なわれます. (これ以外にも各種処理が行なわれていますが,それらについての説明は省略します)
システムクロックの設定は,システムの時刻をタイムゾーンを考慮した上で 正しく設定しなおすために必要です.Linuxのカーネルは, ブート時にはPCのハードウェアクロックの示す時刻をデフォルトでUTC(協定世界時) であるとみなして立ち上がってしまいます.しかし,多くの場合, PCのハードウェアクロックはJST(日本標準時)にセットされているため, このままではJSTとUTCの差の9時間だけ時刻が進んでしまいます. そこで,rc.sysinit上で/sbin/hwclockコマンドを実行し, システムの時刻を正しい時刻に合わせ直す必要があるのです. このクロックの設定はrcスクリプト中でなるべく早く行なうべきで, そうしないと,rcスクリプトの実行によって更新されるファイルのタイムスタンプが 9時間進んでしまいます.
次にホスト名の設定です.これは1台のホストとして当然必要です. 実際のホスト名は,最初にPDF図(4)で読み込んだ/etc/sysconfig/networkの中の, HOSTNAMEという変数に代入する形で記述されています. なお,NISを使用する場合はNISドメイン名もこの時点で設定されます.
そのあと,rootファイルシステムのfsckと,書き込み可でのremountが行なわれます. rootファイルシステムは,カーネルによってブート時に直接マウントされますが, この時点ではまだfsckによるチェックが行なわれていないため, リードオンリでのマウントになっています. そこで,rc.sysinit内でrootファイルシステムに対してfsckを実行し, エラーがなければ「mount -n -o remount,rw /」というコマンドを実行して rootファイルシステムを書き込み可でマウントし直します. なお多くの場合,fsckの実行はcleanフラグのチェックのみですぐ終ります. このあと,PDF図では省略していますがroot以外のファイルシステムについても fsckとマウントが行なわれます.
/etc/rc.d/rcは,ランレベルを引数として受け取り, ランレベル3の場合は/etc/rc.d/rc3.dというディレクトリ(PDF図(a))の 下に置かれたスクリプトを順次実行して行きます. そこでまず,rc3.d関連のディレクトリ構成について説明します.
/etc/rc.d/rc3.dなどのランレベルごとに分けられたディレクトリ以下には, SやKで始まるファイル名のスクリプト(以下それぞれSファイル,Kファイルと呼ぶ)が 配置されています. SファイルもKファイルも,SやKのあとには2桁の数字が続きます. たとえば,ネットワークの起動を行なうスクリプトは, S10networkというファイル名でここに存在します.(PDF図(b)) 各Sファイルは,/etc/rc.d/rcによって,ファイル名の数字の小さい順に, startという引数を付けた状態で起動され, Sファイルではデーモンの起動などの処理を行ないます. 一方Kファイルは,/etc/rc.d/rcからstopの引数を付けて起動され, Kファイルでは起動されているデーモンのkillなどを行ないます.
SファイルとKファイルは,すべてとなりの/etc/rc.d/init.dディレクトリ(PDF図(c)) 以下のファイルへのシンボリックリンクになっています. (ただし,S99localというファイルだけは例外で,/etc/rc.d/rc.localへの シンボリックリンクになっています) /etc/rc.d/init.d以下にあるスクリプトは,実はひとつのファイルが同時に Sファイルとしても,Kファイルとしてもシンボリックリンクによって参照されています. つまり,SファイルもKファイルも実体は同じであり, スクリプト中でstartやstopの引数を場合分けすることにより, それぞれSファイルまたはKファイルとして機能するようになっているのです. たとえば,PDF図(b)のS10networkというファイルはシンボリックリンクで, その実体は/etc/rc.d/init.d/networkにあり,かつ,このファイルは /etc/rc.d/rc0.d/K90networkというKファイルからもシンボリックリンクされています. (rc0.dディレクトリはPDF図では省略)
どのランレベルで何を起動するかということについては, すべて所定のSファイルやKファイルのシンボリックリンクを作ることによって 行ないます.このシンボリックリンクの作成や削除は, シェル上で手動で行なっても構いませんが,これを自動的に行なう ntsysvというツール(下図)が便利です. そのほか,linuxconf,Tcl/Tkベースのtksysvや,コマンドラインで動作する chkconfigも使えます.
*図 ntsysv の実行例
それでは/etc/rc.d/rcの動作を順を追って見て行きましょう. まず,PDF図(11)で,rc.sysinitの時と同様にfunctionsが読み込まれます.
そしていよいよSファイルやKファイルの実行になりますが, 順序としては,先にまずKファイルがstopの引数付きでひととおり実行され, そのあとでSファイルがstartの引数付きで実行されるという順になります.
Kファイルの実行では,まずその前に/var/lock/subsys以下の ロックファイル(サイズゼロのファイル)のチェックが行なわれます.(PDF図(12)) このロックファイルは,すでに該当のスクリプトのstart処理が行なわれているか どうかを示しており,ロックファイルが存在しているもののみに対して Kファイルが実行されます.
/etc/rc.d/rcの中からこの処理を行なっている部分を抜き出し, わかりやすいように一部簡略化を行なうと下のリスト(Kファイルの呼び出し部分の簡略リスト)のようになります. ここで,変数runlevelにはランレベルが代入されており, この場合はrunlevel=3となっています.
for i in /etc/rc.d/rc$runlevel.d/K*; do ← (1) subsys=${i#/etc/rc.d/rc$runlevel.d/K??} ← (2) [ ! -f /var/lock/subsys/$subsys ] && continue ← (3) $i stop ← (4) done |
リスト全体はfor文によるループになっており, この場合は「/etc/rc.d/rc3.d/K*」というワイルドカードの展開順 (つまり数字の小さい順)にループが実行されていくことになります.(リスト*(1))
リスト(2)では,Kファイルのファイル名から頭のKとその次の2桁の数字を 取り除くという文字列処理をしており, 次の行のリスト(3)で,testコマンド(リスト上では[ ]で記述)を使って そのロックファイルの存在をチェックしています. この場合「-f」オプションの前に「!」が付いているため, ロックファイルが存在しなければ真となります. ロックファイルが存在しない場合は,「&&」の右のcontinue文によって for文のループが次に進み,Kファイルは実行されません. たとえば,K20rstatdというファイルについては, /var/lock/subsys/rstatdというロックファイルがチェックされ, もしそれが存在しなければK20rstatdの実行は飛ばして 次のKファイルの処理に進みます.
ロックファイルが存在した場合は,Kファイルがstopの引数を付けて呼び出されます. (リスト(4),PDF図(13))
なお,実際にはシステムの起動時にはロックファイルは全く存在していないはず ですので,ここでは結局,Kファイルは全く実行されないことになるでしょう. ただし,いったんあるランレベルで起動してから別のランレベルに移行する場合には, 新しいランレベルでは起動しないものについてKファイルが実行されます.
Kファイルの処理が終ると,次はSファイルです. Sファイルの実行の前にも,同様にロックファイルのチェックが行なわれます. (PDF図(14)(15)) この部分を抜き出して簡略化すると下のリスト(Sファイルの呼び出し部分の簡略リスト)のようになります. これは先ほどのKファイルの場合とほとんど同じですが, ロックファイルの判定条件が逆になっており, 「ロックファイルが存在しない場合のみSファイルをstartの引数を付けて呼び出す」 という点が違います.
for i in /etc/rc.d/rc$runlevel.d/S*; do subsys=${i#/etc/rc.d/rc$runlevel.d/S??} [ -f /var/lock/subsys/$subsys ] && continue $i start done |
実際には,システムの起動時にはロックファイルが存在しないため, すべてのSファイルが実行されることになるはずです.
このようにして次々にSファイルが起動され,システムの初期化が行なわれて行く わけですが,/etc/rc.d/rc3.d以下の多数のSファイルのうち, S10networkについてはその動作が少々複雑なため,次項で詳しく解説します.
なお,Sファイルの中で最後に実行されるのはS99localで,このファイルだけは ほかのSファイルとは違って/etc/rc.d/rc.localにシンボリックリンクされています. これは,BSD系のrc.localと同じように,独自のシステム設定をちょっと追加したい ような場合に使うことができます.
/etc/rc.d/rc3.d以下には多数のSファイルがありますが, この中から代表してS10networkの動作を見て行きましょう.
まず最初に,ほかのスクリプトと同様に,functionsとnetworkのファイルが 読み込まれます.(PDF図(16),(17))
このあとS10networkでは,最終的にはifconfigコマンドによる ネットワークインターフェイスの立ち上げが行なわれるはずです. しかし,実際のスクリプトでは/etc/sysconfig/network-scripts以下にある さらに別のスクリプトを呼び出すなどの複雑ことをしているため, S10networkを読みこなすのは少々大変です.
S10networkのうち,主要部分を抜き出して簡略化すると下のリスト(S10networkの主要部分の簡略リスト)のようになります. ここではスクリプト内のcdコマンドにより, /etc/sysconfig/network-scriptsにカレントディレクトリを変更していることに 注意して下さい.(リスト(1))
cd /etc/sysconfig/network-scripts ← (1) 変数interfacesに,存在するインターフェイス名(lo0などは除く)をセットする ←(2) case "$1" in start) for i in $interfaces; do ← (3) action "Bringing up interface $i" ./ifup $i boot ← (4) done touch /var/lock/subsys/network ← (5) ;; stop) …省略… ;; …省略… esac |
次にinterfacesという変数に,存在するインターフェイス名をセットします. これは,/etc/sysconfig/network-scripts以下にある, ifcfg-eth0のようなファイル名を見て,その末尾のeth0という文字列だけを取り出して 変数interfacesに代入するという方法で処理が行なわれます.(リスト(2)) なお,Linuxでは,ネットワークカード(以下NIC)の種類に関わらず, イーサネットのインターフェイス名はeth0,eth1,...のように付けられます.(※注) NICを1枚だけ使用しているマシンでは,eth0のみが変数interfacesに代入される ことになるはずです.
そしてcase文によるstart引数の場合分けあと, 各インターフェイスに対してfor文のループでネットワークが立ち上げられます. (リスト(3)) NICが1枚だけのマシンではループは1回だけ実行され, 結局「./ifup eth0 boot」というコマンドが実行されることになります. (リスト(4),PDF図(18)) ここではifupコマンドの頭に「action "Bringing up interface $i"」というものが 付いていますが,actionというのは/etc/rc.d/init.d/functionsの中で定義されている シェル関数であり,これはおもにコンソールに緑色の[ OK ]というメッセージを 表示するために使用されています. (コラム「シェル関数action()を直接使ってみよう」参照)
さて,話はまだ終りません. S10networkから呼び出された/etc/sysconfig/network-scripts/ifup(※注)は シェルスクリプトであり,ここからまた別のスクリプトを読み込んでいます. 具体的には,PDF図(19)のところで,source_config()などのシェル関数の定義された network-functionsというファイルをまず読み込んでいます. 次にこのシェル関数source_configを使って, ifcfg-eth0というIPアドレスなどの設定情報が書かれたファイルを読み込みます. (PDF図(20)) そのあとifupスクリプト内で最終的にifconfigコマンドを実行して, ここでようやくネットワークインターフェイスが立ち上がるのです. (PDF図(21))
ifupスクリプトが終了すると再び呼び出し元のS10networkに流れが戻り, 最後にtouchコマンドによって/var/lock/subsys以下にロックファイルが作成されます. (PDF図(22),リスト(S10networkの主要部分の簡略リスト)(5))
このようにRedHat系のS10networkスクリプトの実行は大変複雑です. これは,/etc/rc.d/rc.inet1にifconfigコマンドがわかりやすく記述されている Slackware系とは対照的であると言えます.
起動処理の最後はログイン画面の表示です. 前述のように,initはrcスクリプトの一連の処理が終ると, inittabの記述にしたがってmingettyを起動し, 「login:」のプロンプトを表示します.(PDF図(23)) ランレベル5の場合はそれに加えてprefdmというスクリプト経由で, xdm系のグラフィカルログイン画面も起動します.(PDF図(24))
これらのログインプログラムの起動は,rcスクリプトからではなく inittabの記述によって直接行なわれるため, rcスクリプトのような複雑さはなく,容易に理解できると思います.
SysV系initでは,いったんあるランレベルでOSを起動したあと, 別のランレベルに変更することも可能です. ランレベルを変更するにはinit(またはtelinit)コマンドに, ランレベルの数字を引数に付けて実行します.(initの実行にはroot権限が必要です) たとえば,ランレベル3の状態から「init 5」を実行すると, グラフィカルログインの画面が立ち上がります.
また,SysV系initではシステムのシャットダウンや再起動の際にもランレベルの 切替の仕組みが用いられているため,「init 0」や「init 6」の実行により, それぞれシステムのシャットダウン・再起動を行なうことができます. (ただしRedHat系では,「init 0」を直接実行するよりも, 一般ユーザからも実行できるpoweroffコマンドを使った方が便利でしょう)
OSを最初からデフォルト以外のランレベルで起動したい場合は, LILOなどのブートローダのプロンプト上で,ブートラベルのあとにランレベルの数字を 付けて起動します.たとえば,「boot: linux 1」とすると, ランレベル1の,いわゆるシングルユーザモードで起動することができます.
また,システムのデフォルトのランレベル自体を変更したい場合は, PDF図(2)の,initdefaultの行のランレベルのフィールドを, テキストエディタで修正します. なお,RedHat系ではXサーバの設定ツールであるXconfiguratorが, Xサーバの設定のあとにinittabのinitdefaultのランレベルのフィールドを, 3または5に上書きするため注意が必要です.
自分で/etc以下をいろいろと検索したい場合は, find,locate,grepなどのコマンドが役に立ちます.
たとえば,前述のaction()というシェル関数について, これが定義されているファイルを検索して見つけるには 次のようにすればよいでしょう.
$ grep -r 'action()' /etc 2> /dev/null /etc/rc.d/init.d/functions:action() { /etc/rc.d/init.d/functions:qaction() { |
このようにして,/etc/rc.d/init.d/functionsというファイルが見つかります. LinuxではGNU grepが使われているため,「-r」オプションを付けて サブディレクトリを含めた検索ができます. なお,ここで単にactionではなく,action()という文字列で検索しているのは, シェル関数を定義しているファイルのみ(呼び出しているファイルではない)が 検索されるようにするためです. action()をシングルクオート(' ')で囲んでいるのは, 「()」がシェルによって解釈されないようにするためです.
そのほか,findやlocateを使ったファイル名の検索も有効です. ただし,locateを使う場合はlocateのデータベースが作成されている必要があります. findやlocateは,探したいファイル名をオプションで指定して実行する方法のほか, ファイル名を指定せずに全ファイルのリストをいったん標準出力に出し, それをパイプで受けてlessで表示させながらlessの機能を使って前後にサーチして使う という方法もあります. 具体的には「locate /etc | less」または「find /etc | less」(※注)を 実行します.どちらも画面の様子は同じになります. このあとは,lessの機能を使って画面を前後にスクロールしながら ファイル名を検索します.(下図)
*図「find /etc | less」の実行例
(lessの「/」コマンドでnetworkというファイル名をサーチしてみたところ)