Linuxがブートするまで

September 27, 2016
by Keichi Takahashi

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

基本的にOSの起動は、単純・低機能なプログラムが、より複雑・高機能なプログラムを読み込み起動するという処理を連鎖的に行う仕組みになっている。Linuxでは、下記のプログラムが順に起動していく:

  1. BIOS
  2. ブートローダ (GRUB)
    • Stage 1
    • Stage 1.5
    • Stage 2
  3. Linuxカーネル
  4. Init

以下では、それぞれのプログラムについて順に要約して述べていく。

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のPIDは常に1である。Initには、SysVinit,Upstart,Systendなど様々な実装があるが、最近のCentOS, Fedora, Ubuntuなどのディストリビューションでは、Systemdが採用されている。