2011/06/24(金)Linuxブートシーケンスまとめ。GRUBと起動トラブルのメモ

mdadmで作ったソフトウェアRAIDに障害があってRAIDを組みなおしてたのですが、まともに起動しないトラブルに見舞われて格闘してました。

その時に調べたGRUB2と起動の仕組みについてのメモ。

環境

  • Ubuntu 10.04 / Debian 6
  • ブートローダー GRUB2 (Ver1.98)
  • 起動ディスク RAID1(ミラー)
  • ファイルシステム構成 / から起動(/bootは / ファイルシステム内)*1

ここで説明するGRUBはGRUB2です。GRUB(Ver0.98)とは異なりますのでご注意ください。

*1 : 昔の8GBの壁時代ならいざしらず、今更 /boot を分けるとかよく理解できない。

GRUBが呼ばれるまで

BIOSからブートローダーに制御が移ります。GRUBには stage1 と stage2 の2つのブート段階があり、BIOSからstage1が呼び出されると別セクタにあるsatage2以降がロードされます。細かいことはともかく、GRUBはBIOSから直接起動されます。

GRUBにはext3/ext4等のファイルシステムを解読する能力があります。

/boot/grub/grub.cfg

をGRUBはファイルシステムを解釈して直接読み込みます。逆に言えば、/boot の存在するパーティションにはきちんとファイルシステムがあって、このファイルが置かれている必要があります。

GRUBはRAIDされたディスクでもここまで辿ることができますが、RAID構成情報が変わるとそれを認識することができないようで、RAID1構成でディスク1台構成から2台構成に変った時、grub-install を再度やり直す必要がありました。

きちんとパーティーションタイプを識別するようで、fdiskのトルグで一時的に違うものに変更してもロード失敗してました。

GRUBメニューとブートオプション

GRUBが grub.cfg をきちんと認識できると、GRUBメニューが立ち上がります。

実際に稼働中の Ubuntu 10.04 では次のようなエントリが構成されています。

menuentry 'Ubuntu, with Linux 2.6.32-32-generic' --class ubuntu --class gnu-linux --class gnu --class os {
	recordfail
	insmod raid
	insmod mdraid
	insmod ext2
	set root='(md1)'
	search --no-floppy --fs-uuid --set dbba431a-7cc1
	linux   /boot/vmlinuz-2.6.32-32-generic root=UUID=dbba431a-7cc1 ro video=800x600-16
	initrd  /boot/initrd.img-2.6.32-32-generic
}
※UUIDは長いだけなので一部省略しました。

この中で重要な情報は、当然 linux と書かれたカーネル指定ですが、同じぐらい重要なのが initrd です。これについては後で述べます。

  • recordfail は grub に対するオプションです。debian 6(squeeze)のgrubでは無効でした。
  • insmod raid, insmod mdraid は grub に対するオプションです。モジュールを読み込めという指定ですが、このモジュールは /boot/grub/*.mod に実際に存在します。

set root='(md1)' はルート(/)ファイルシステムの存在するパーティションを指定します*2。指定できるデバイスの一覧は、GRUBコンソールモードで「ls」を入力すると出てきます。これはGRUBに対する指定です

HDDのパーティションを指定するときは、set root='(hd0,msdos2)'という風に指定します。これは /dev/sda2 を指定するようなものです。ですが、2番目以降のHDDから起動した場合、そのHDDが例え /dev/sdb に相当するものでも、GRUBを起動しているHDDは常に hd0 になります。注意してください。hd1以降は、GRUBを起動しているHDDを除いてデバイス順に並べたものです。また、(hd0,1)(/dev/sda2相当)といった旧来のGRUB方式の指定は無効です。これも注意してください。

search という以前のGRUBには無かった行は「set root=」の代替手段を示しています。「set root=」が指定されないとき、または「set root=」で指定されたデバイスが無効であるとき、GRUBは「--set」以降で指定されたUUIDを持つファイルシステムを探索し、それをGRUBに対するルート(/)ファイルシステムだと解釈します。

linux 行にある「root=UUID=dbba431a-7cc1-a56e」はlinux kernelに対するルート(/)ファイルシステムの指定でまた意味が違ってきます。

GRUBのルート(/)ファイルシステムとはなんぞや?

GRUBの重要な役割はカーネルをメモリに展開(ロード)して、起動シーケンスをカーネルに渡すことです。ロードするカーネルはどこに書かれているかというと、HDDのパーティション内に書きこまれています。

GRUBはファイルシステムを(insmodすればRAIDすらも)理解することができますが、膨大なファイルの中からカーネルを自動で探すことはできません。カーネルがどこに存在するのかきちんとGRUBに教える必要があります

GRUBのルート(/)ファイルシステムとは、Linuxカーネルをロードするときのファイルシステムの基準(/)を指定するものです。もし、/boot パーティションが /dev/sda1 に分かれていて、/dev/sda3 がLinuxのルート(/)ファイルシステムならばGRUBは次のように書かれます。

set root='(hd0,msdos1)'
linux   /vmlinuz-2.6.32-32-generic root=/dev/sda3 ro
initrd  /initrd.img-2.6.32-32-generic

もしsearch行を書くなら、そこでは /dev/sda1のUUID なりを記述します。

また見て分かるとおりlinux, initrd で書かれたパスが異なっています。これはroot='(hd0,msdos1)'で指定した位置を基準(/)としたパスで記述する必要があるからです。

*2 : ソフトウェアRAIDの新しい名づけ方式には対応していないようで、RAIDデバイスののマイナーバージョンに相当する数字を付加します。多分何のことかわからないと思うので、通常は気にしないでください。

Linuxカーネルとカーネルモジュール

GRUBオプションにより正しくLinuxカーネルを発見できたら、GRUBはそれをファイルシステムからロードし、また同時にinitrdが指定されていればinitramfsを展開します

Linuxが起動しないというトラブルにも何段階かありますが、カーネルがとりあえずロードされるなら、GRUBの設定には(linux行のroot指定やinitrdが合っていれば)問題はないと言えます。


昔のLinuxカーネルはモノリシックな作りになっていて、カーネルが標準で対応していないデバイスを使いたい場合、多くはカーネルを全部つくり直す必要がありました*3。かといって予めすべてのデバイスに対応するようなカーネルを作っておくとカーネルが大きくなってそれはそれでメモリを圧迫しましたし起動もロードも遅かったのでした*4

今はカーネルモジュール(*.ko)と言って、デバイスドライバの部分を切り離して別ファイルとして置いておき、その特定のデバイスを見つけ必要になったとき自動ロードするようになりました。

しかし、これには大きな問題があります。カーネルモジュールは当然ファイルシステムのどこかに置かれているわけですが、ファイルシステムを認識する前に必要なモジュールや設定情報はどうやって知ればいいのでしょうか?

例えば、標準のディスク(ATAやSATAのext3やext4)なら当然カーネルに組み込まれていますが、ソフトウェアRAIDや専用のRAIDカードから起動されたとき、モジュールを読み込もうにもモジュールを読み込むのに必要なデバイスドライバをカーネルが持っていないので起動できません。*5

GRUBの「root=」で正しくLinuxルート(/)ファイルシステムを指定しているのに、ルート(/)ファイルシステムのマウントに失敗して起動できない原因はここにあります。

Begin: Mounting root file system...

と表示される当たりで起動シーケンスが止まってしまうのです。


しかしだからと言って、起動時に必要になるかもしれないすべてのデバイスをカーネルに組み込むのも考えものです。メモリとリソースのムダでしかありません。これを解決するためLinux2.6から導入されたのがinitramfsです。

*3 : デバイスを抜き差しするたびにカーネル再構築とか実際にやった記憶があります。昔のマシンは遅いからまたコンパイル待ちが大変で(苦笑)

*4 : 大量に定義された業務サーバ向けと思われるデバイスを無効に設定してカーネルを再コンパイルってのも、これまたよくやりました(苦笑)

*5 : GRUBはGRUB専用のモジュールと、BIOSの力を借りてディスクにアクセスしているので(ハードウェアドライバがなくても)カーネルをロードできますが、Linuxカーネルが一度起動した後はディスクBIOSを呼ぶことはできません。

initramfs

カーネル起動時にあらかじめメモリに展開することでファイルシステムを構成する仕組みです。CDブートするディスクトップ環境とかでも使われてるメモリファイルシステムです。GRUBで指定する initrd行 がそれです。

メモリなのでカーネル起動時でもファイルを参照することができ、必要に応じてモジュールをロードできます。そして、本来のルート(/)ファイルシステムをマウントする段階で破棄されますのでメモリを圧迫することもありません。


いいこと尽くめなのですが、1つだけ難点がありあらかじめinitrd.imgとしてRAMファイルシステム全体を固めておく必要があります。UbuntuやDebianでは次のコマンドを使用します。

# update-initramfs -u

設定等は /etc/initramfs にありますが特にいじる必要はなさそうです。

update-initramfsは次のような場合に実行する必要があります。

  • カーネルモジュールを手動追加したり、blacklist登録したとき。
  • ソフトウェアRAIDを組んでいて、RAID構成情報(/etc/mdadm/mdadm.conf)を変更した場合。→詳細はこちら

Begin: Mounting root file system...

が表示されるまでが、initramfsでの動作になります。

復旧時に大事なchrootする方法

/dev/md1 が復旧対象のLinuxのルートファイルシステムです。

#!/bin/sh
mount /dev/md1 /main-root
mount --bind /dev /main-root/dev
mount --bind /dev/pts /main-root/dev/pts
mount --bind /proc /main-root/proc
mount --bind /sys /main-root/sys
chroot /main-root

mount --bindで、chroot後もデバイスを参照できるようにしてあげるのがコツらしい。