Linuxがブートするまで

普段Linuxを使っていながら、vmlinuzinitrd.imgというファイルは何なのか、 あやふやにしか理解していなかったので、一通りLinuxマシンのブートの仕組みを 勉強してみた結果を書き留めておく。なお、BIOSとGRUB Legacyの環境を前提としている。 EFIやGRUB2を使った環境については、今後いずれ勉強していきたい。

基本的にOSの起動は、BIOS -> ブートローダ -> カーネルというように、単純・低機能 なプログラムが、より複雑・高機能なプログラムを読み込み起動するという処理を 連鎖的に行っていく仕組みになっている。以下ではブートの各プロセスについて、 時系列順に簡単に要約して述べていく。

1. BIOS

現在一般的なx86/x86-64 CPUは、電源が投入されると、0xfffffff0 (Reset Vector) 番地から実行を開始する。この領域には、BIOSが格納されているマザーボード上のROMが マップされている。つまり、電源投入直後にはBIOSが実行される。BIOSはハードウェア の検出や初期化 (Power On Self Test) を実行した後、ブートローダを探索する 処理に入る。

ブートローダは、ディスクの先頭セクタにある Master Boot Record (MBR) という領域に収められ ている。MBRは下記のような構造になっている。

  1. ブートローダ (446B)
  2. パーティションテーブル (64B)
  3. Boot Signature (2B)

MBRの探索は、優先順位の高いブートデバイスから順に、そのデバイスの先頭1セクタの 末尾2バイトが0x55 0xaa (Boot Signature) であるか確認することによって行われ る。BIOSは、Boot Signatureが存在するセクタを発見すると、そのセクタの内容を 0x7c00 番地にロードし、実行を移す。

2. ブートローダ (GRUB)

ブートローダはOSをディスクからメモリに読み込み、起動する役目を持ったプログラム である。Linuxでは一般的にGRand Unified Bootloader (GRUB) というブートローダが用いられること が多い。GRUB以外にも、 SyslinuxLILOなどのブートローダが用いられている。 GRUBは複数の Stage と呼ばれるプログラムに分かれている。

Stage 1

GRUBのStage 1は、MBRの先頭446Bに存在する。Stage 1の役割は、Stage 2 (Stage 1.5) をロードすることである。1セクタしかないMBR内でブートローダの処理を全て実現する ことが難しいため、このような仕組みになっている。

Stage 1.5

Stage 1.5はの役目は、ディスク上のファイルシステムを解釈し、Stage 2をロードする ことにある。Stage 1.5は、ディスク上ではMBRと最初のパーティションの間に存在する、 DOS Compatibility Regionという領域に収められており、は以下のように解釈する ファイルシステムごとに存在する。

$ ls -lah /boot/grub | grep stage1_5
-rw-r--r--. 1 root root  14K  3月 13 22:41 2014 e2fs_stage1_5
-rw-r--r--. 1 root root  13K  3月 13 22:41 2014 fat_stage1_5
-rw-r--r--. 1 root root  12K  3月 13 22:41 2014 ffs_stage1_5
-rw-r--r--. 1 root root  12K  3月 13 22:41 2014 iso9660_stage1_5
-rw-r--r--. 1 root root  13K  3月 13 22:41 2014 jfs_stage1_5
-rw-r--r--. 1 root root  12K  3月 13 22:41 2014 minix_stage1_5
-rw-r--r--. 1 root root  15K  3月 13 22:41 2014 reiserfs_stage1_5
-rw-r--r--. 1 root root  12K  3月 13 22:41 2014 ufs2_stage1_5
-rw-r--r--. 1 root root  12K  3月 13 22:41 2014 vstafs_stage1_5
-rw-r--r--. 1 root root  14K  3月 13 22:41 2014 xfs_stage1_5

Stage 2

Stage 2は、GRUBの本体である。これまでのstageでファイルシステムを読み込めるよう になっているので、stage 2はファイルシステム上に普通のファイルとして置かれている。

$ ls -lah /boot/grub/stage2
-rw-r--r--. 1 root root 124K  3月 13 22:41 2014 /boot/grub/stage2

Stage 2はmenu.lstというファイルを読み込み、インストールされているOSの 一覧を得る。これを元に、普段目にするOSの選択画面が表示される。下記は、 menu.lstの例である。

# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You have a /boot partition.  This means that
#          all kernel and initrd paths are relative to /boot/, eg.
#          root (hd0,0)
#          kernel /vmlinuz-version ro root=/dev/mapper/VolGroup-lv_root
#          initrd /initrd-[generic-]version.img
#boot=/dev/vda
default=0
timeout=5
serial --unit=0 --speed=115200
terminal --timeout=5 serial console
title CentOS (2.6.32-573.18.1.el6.x86_64)
	root (hd0,0)
	kernel /vmlinuz-2.6.32-573.18.1.el6.x86_64 ro root=/dev/mapper/VolGroup-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarcyrheb-sun16 crashkernel=auto console=ttyS0,115200n8 rd_LVM_LV=VolGroup/lv_root  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM
	initrd /initramfs-2.6.32-573.18.1.el6.x86_64.img
title CentOS (2.6.32-573.12.1.el6.x86_64)
	root (hd0,0)
	kernel /vmlinuz-2.6.32-573.12.1.el6.x86_64 ro root=/dev/mapper/VolGroup-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarcyrheb-sun16 crashkernel=auto console=ttyS0,115200n8 rd_LVM_LV=VolGroup/lv_root  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM
	initrd /initramfs-2.6.32-573.12.1.el6.x86_64.img

ユーザがどれか1つのOSを選択すると、GRUBはそのOSのカーネルvmlinuz-*と RAMディスクinitramfs-*.imgをメモリ上にロードし、カーネルの先頭アドレスに ジャンプし、その役目を終える。

3. カーネル

ブートローダから起動されたカーネルの実行バイナリ vmlinuz は、低レベルな初期 化処理を実行した後、カーネルの本体を自己解凍し、メモリにロードする。カーネルの 本体の先頭にジャンプした後、さらに様々な初期化処理を実行する。ここら辺の仕組みは、 linux-insides と いう資料が詳しい。また、Linuxではないものの、30日でできる! OS自作入門 という書籍も勉強になった。

カーネルは全ての初期化処理を終えると、初期RAMディスク (initrd) を展開し、 仮のルートファイルシステムとしてマウントする。初期RAMディスクは、 本番のルートファイルシステムが置いてあるディスクをマウントするために必要な ドライバや、各種ユーティリティが含まれている。

initrdには、主にinitrdとinitramfsの2形式があり、現在は後者が使われることが多い。 initramfsは、ルートディレクトリをcpioという形式でアーカイブし、 gzipで圧縮したものである。実際に、 /boot にあるinitramfsの中身を覗くためには、 次のようにすれば良い。

$ zcat initramfs-xxx.img | cpio -idv

CentOSやFedoraでは、initramfsの先頭にCPUマイクロコードが入っているので、これを スキップしてから解凍する。

$ /usr/lib/dracut/skipcpio initramfs-xxx.img | gunzip -c | cpio -idv

ファイルリストを見るだけなら、lsinitrd というコマンドもある。

なぜtarでなくcpioなのか気になったので調べたところ、initramfsのドキュメンテーションに 答えが 書いてあった

  • cpioは標準化されている
  • cpioは既にLinuxで広く使われている (rpmの内部など)
  • cpioの方がtarより単純で綺麗 (なので作成・展開ともに容易)

などの理由があるそうだ。

4. init

カーネルはinitrdのマウントに成功すると、initrdに含まれているの /sbin/init というファイルを実行する。Initは実行される最初のプロセスであり、各種デーモン など、他のプロセスを起動する。Initには、 SysVinit, Upstart, Systend など様々な実装がある。最近のCentOS, Fedora, Ubuntuなどのディストリビューション では、Systemdが採用されている。