電源ON | ・電源スイッチONでパワーコネクタの PS_ON の信号をONにする。 |
・電源ユニットは PS_ON の信号を受けるとパソコン各部に電源供給を開始。 | |
・給電直後は電圧が不安定なので、クロック回路がCPUへのリセット信号をONにしCPUの処理を抑止する。 | |
・電源出力が安定すると PWR_OK の信号をONにする。 | |
・クロック回路は PWR_OK の信号を受けるとリセット信号をOFFにしCPUの動作を許可する。 | |
↓ | |
BIOSの起動 | ・M/BのフラッシュROMに書き込まれたBIOS(Basic Input/Output System)というソフトウェアが起動。 |
・POST(Power On Self Test)実行。 | |
・起動するためのデバイスを探す。 | |
・MBRをメモリ上にロードし、MBR領域にあるプログラムに制御を移す。 | |
↓ | |
1st Boot Loader | ・パーティションテーブルから起動フラグのあるパーティションを探す。 |
・IPL(Initial Program Loader)に制御を移す。 | |
↓ | |
2nd Boot Loader | ・ブートイメージ(vmlinuz)をメモリ上にロード。 |
・イニシャルRAMディスク(initrd)をメモリ上にロード。 | |
・制御をカーネルに移す。 | |
↓ | |
カーネルの起動 | ・ハードウェアリソースの初期設定。 |
・ルートファイルシステムのマウント。 | |
・初期化プロセス。 |
Windowsでも、Linuxでも、だいたいこんな感じ。
が、もはやBIOSではなく、UEFIになっている。BIOSは古いもの。
とは言っても、基本となる流れのイメージは大きくはずれてないので、
抽象的にはOKとして、もっとざっくりと書くと、
電源ON |
↓ |
起動プログラムの実行 |
↓ |
Boot Loaderの実行 |
↓ |
カーネルの起動 |
「Boot Loaderの実行」は、Ubuntuの標準であれば「GRUB2の実行」となる。
GRUBは、/boot が存在するパーティションのファイルシステムをマウントするためのドライバを持ち、ブートイメージ(/boot/vmlinuz-*.*.*)とイニシャルRAMディスク(/boot/initrd.img-*.*.*)をメモリ上にロードすることができる。
ブートイメージについて
ブートイメージは、"vmlinuz-*.*.*"。
古くから、UNIXのカーネルイメージはunixというファイル名であり、cp (作成したイメージのファイル名) /unixのようにコピーしてインストールされていた。BSDで仮想記憶が実装され、仮想記憶をサポートしたカーネルであることを示すvm-という接頭辞を付けvmunixという名前が使われるようになった。vmlinuxという名前はそのvmunixを基にした命名法によるものである。さらにLinuxでは圧縮イメージという機能も追加され、vmlinuzという名前が付けられた。vmlinuxを圧縮したものであることを表す文字z(zipped)を後尾に付している。
引用:Wikipedia
vmlinuzのファイルタイプを見てみる。
$ sudo file vmlinuz-4.4.0-79-generic vmlinuz-4.4.0-79-generic: Linux kernel x86 boot executable bzImage, version 4.4.0-79-generic (buildd@lcy01-30) #100-Ubuntu SMP Wed May 17 1, RO-rootFS, swap_dev 0x6, Normal VGA |
bzImageだった。

bzImage内のカーネルイメージ "bvmlinux.out" を取り出してみる。
作業ディレクトを作って、そこで作業。
$ mkdir /tmp/work
$ cd /tmp/work
/boot/vmlinuz-*.*.* を コピーしてリネーム。read許可しておく。
$ sudo cp /boot/vmlinuz-4.4.0-79-generic ./vmlinuz
$ sudo chmod +r ./vmlinuz
カーネルイメージは gzip 圧縮されていて、自己解凍形式になっている。
gzipなので、gzipヘッダーを探す('1f 8b 08 00')。
odコマンドを使う。-Ad で表示されるオフセットの基数を10進数に、-tx1 で1byte単位で16進数出力。
$ od -Ad -tx1 vmlinuz | grep '1f 8b 08 00'
0018864 ac fe ff ff 1f 8b 08 00 00 00 00 00 02 03 ec fd |
18864 byte から 4 byte目に発見。
つまりこのブートイメージでは、18864 + 4 = 18868 byte目からがカーネルイメージ。
18868 byte以降を抽出する。
バイナリファイルなので、dd コマンドで開始位置までskipし、それを zcat に渡して展開し、vmlinux ファイルとして保存する。
$ dd if=vmlinuz bs=1 skip=18868 | zcat > vmlinux
gzip: stdin: decompression OK, trailing garbage ignored |
ファイルタイプを見てみる。
$ file vmlinux vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=29e4c9d663eaf39dc69806020ec82421b1086729, stripped |
version を確認してみる。
バイナリデータだが、"Linux version"文字列は含まれているはずなので、
$ strings vmlinux | grep "Linux version" Linux version 4.4.0-79-generic (buildd@lcy01-30) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #100-Ubuntu SMP Wed May 17 19:58:14 UTC 2017 (Ubuntu 4.4.0-79.100-generic 4.4.67) |
GRUBから起動されたカーネルは、
自分自身で展開し、次のinitrdファイルをマウントする。
イニシャルRAMディスクについて
initrdファイルと書いたが、実のところは、initramfs。
ブート時に、ルートファイルシステムをマウントするためには、格納先が様々な媒体だったり、RAIDや暗号化されていたりなどの仕組みから、読み出すためのドライバをサポートしていなければいけない。
すべてに対応していくとカーネルが巨大化してしまう。
そこで、カーネルにドライバの対応コード追加していくことを避け、ブートに必要なモジュール群をまとめたもの置いておく一時的なルートファイルシステム(ミニルート)を準備し、それをブート時にマウントして使う、initrd (Initial RAM Disk) という仕組みを作った。
ブートローダは、カーネルを起動するときに合わせて initrdイメージ をロードしたメモリアドレスも渡す。
カーネルは initrd イメージの先頭を読み込み、そのフォーマットを判断する。
initrd はブロックデバイス(RAM ディスク)をgzip圧縮したもの。
Linux 2.6からは、RAM上に直接ファイルシステムを作る ramfs を用いたinitramfsを導入。
initramfsはファイルシステムをcpio形式でアーカイブしたうえで、gzipで圧縮したファイル。
展開してみる。
また作業ディレクトを作って、そこで作業。
$ mkdir /tmp/initramfs
$ cd /tmp/initramf
/boot/initrd.img-*.*.* を コピーしてリネーム。
$ sudo cp /boot/initrd.img-4.4.0-79-generic ./initrd.img
中身を展開する。
$ zcat ./initrd.img | cpio -id
gzip: ./initrd.img: not in gzip format cpio: 予期しない書庫終了です |
あれ、gzip じゃないのか?
ファイルの先頭を見てみる。
$ hexdump -C -n 512 ./initrd.img
|
cpioのマジックナンバー "070701" が入っている。単なるcpio形式なのかな?
だったら、直接cpioで展開してみる。
$ cpio -id < ./initrd.img 22 ブロック |
が、x86のマイクロコードらしきものしか展開されない。
$ tree kernel/
|
それなら、また、gzipヘッダーを探してみる。
$ od -Ad -tx1 initrd.img | grep '1f 8b 08 00'
0011264 1f 8b 08 00 4a 7e 37 59 00 03 d4 3b 6b 57 db 48 |
見つかったので展開してみる。
$ dd if=initrd.img bs=1 skip=11264 | zcat > initrmfs
gzip: stdin: decompression OK, trailing garbage ignored |
cpioで展開する。
$ cpio -id < ./initrmfs
197303 ブロック |
ミニルートが現れた。
$ tree -d -L 1
|
このミニルートをブート時のルートファイルシステムとしてマウントし、
実際のルートファイルシステムをマウントするために必要なドライバをカーネルに読み込み、それから実際のルートファイルシステムをマウントする。
やっと、ルートファイルシステム内にあるファイルを用いて初期化が実行される。
次の初期化プロセスが完了すれば、Linuxが使えるようになる。
初期化プロセスについて
カーネルが起動した後、initプロセスが起動する。
init(/sbin/init が実行)は、他のすべてのプロセスを起動するプロセスであり,すべての親プロセスになる。
最初に起動するので、PIDは必ず1になる。
/etc/inittab ファイルに書かれた ランレベルを設定し、該当する/etc/rc.d/rc*を実行していく。
というのが、System V系のinit。
Ubuntuではinitの代わりとして、Ubuntu 6.10以降で導入された、Upstartが使われていたが、
Ubuntu 14.04 $ dpkg -S /sbin/init upstart: /sbin/init |
Ubuntu 15.04以降では systemdが採用された。
(参考:SystemdForUpstartUsers)
Ubuntu 16.04 $ dpkg -S /sbin/init systemd-sysv: /sbin/init |
で、systemdによるシステム起動のプロセスを確認していたが、
新たな考え方が必要になってきたので、このあたりまでにしておいて、
こんな流れでLinuxが起動して、使えるようになる。
まとめ
ざっと吐き出してみて、全体的にぼんやりとであるが流れの確認ができた。
とは言え、自身の理解は表層しかわかっておらず、
足りない所も多く、間違っていることもあるだろうし、具体的な動きはブラックボックスのまま。
その辺りに、いつか踏み込む日が来たら、そのときに改めて理解しようと思う。
いまは、こんな感じなのか、ふーん・・ という程度でよしとしておこう。