Vmlinux
Linuxシステムにおいて、vmlinuxとは、内部にLinuxカーネル本体を包含する静的リンクされた実行ファイルである。ELF、COFF 、a.outのような実行可能バイナリ形式に準じた形式が利用されるが、通常の実行可能バイナリと異なりカーネル(のexecシステムコール)が対応している形式ではなく、ブートローダーが対応している形式でなければならない。vmlinuxファイルはカーネルデバッグ、カーネルのシンボルテーブル生成、またはその他の用途で利用される。通常、コンパイラが生成するバイナリから、さらに全てのシンボルを取り除き、圧縮をかけ、Multiboot Specification用ヘッダ、ブートセクタ、BIOS用ローレベルブートセットアップルーチン、自己伸長ルーチンなどを追加して最終的に、ブート可能なイメージが完成する(ブート用に付加的な部分を追加し、圧縮(オプション)したカーネルのことをカーネルイメージと呼んで区別する場合もある)。
通常はカーネルのビルドが正常終了すると、(ビルド完了後には同名や類似した名前の中間ファイルが幾多もあるため注意が必要であるが、)ソースコードのトップディレクトリにこのファイルが存在する。
古くから、UNIXのカーネルイメージはunixというファイル名であり、cp (作成したイメージのファイル名) /unixのようにコピーしてインストールされていた。BSDで仮想記憶が実装され、仮想記憶をサポートしたカーネルであることを示すvm-という接頭辞を付けvmunixという名前が使われるようになった。vmlinuxという名前はそのvmunixを基にした命名法によるものである。さらにLinuxでは圧縮イメージという機能も追加され、vmlinuzという名前が付けられた。vmlinuxを圧縮したものであることを表す文字z(zipped)を後尾に付している。
Unix系システムにおける古くからの慣習で、カーネルはルートディレクトリ (/) に置かれることが多かった。しかし、Linuxに限らずPC-UNIXでは、ブートローダがハードディスクアクセスにBIOSを使用するため、ハードディスクの先頭1024シリンダー (Cylinder 1024) までしかアクセスできないという制限があるシステムがあった(BIOS割り込みルーチン・INT 13H (0x13) を参照せよ)。
この制限に対処するため、Linuxディストリビューターは、ユーザにハードディスクの先頭にブートのためのファイルシステム、すなわちカーネルと、システムのブートに必要なファイルを置くパーティションを作成するよう推奨した(正確にはファイルシステムに含まれるもの以外に、ブートのためにはそのパーティションの先頭のVolume boot recordも正しく設定される必要がある。XFSは実装上そのための空間が確保されないため、このファイルシステムには使えない。あるいはブート手順中のそこを迂回する何らかの別の方法が必要となる)。
慣習として、ブート後にそれとは別のパーティションにあるファイルシステムをルートとする場合は、このブート専用ファイルシステムはブート後は /boot にマウントされた(安全のため、カーネルのアップデート時など以外はマウントしない運用とすることもある)。のちにこれは (FHS) により標準化された。詳しくは、を参照してほしい。
旧来より、ブート可能なカーネルイメージを生成した際、カーネルはまた、zlibアルゴリズムを用い圧縮されている。Linuxカーネルバージョン2.6.30からはこれに加え、圧縮率が高いLZMAやBZIP2アルゴリズムによる圧縮もサポートされている。システムに電源を投入し、BIOSがブートローダを読み込み、ブートローダが圧縮されたカーネルイメージコードを読み込む。このコードからカーネルコードが伸長(展開)されると、いくつかのシステムでは、ブートプロセスが進行するにつれコンソールにドット(.)が何個も表示される。
圧縮ルーチンは、いくつかのアーキテクチャ、とりわけ、i386のようにブート時に読み込めるデータサイズが極端に制限される場合必須であったが、bzImageが開発された現在においては、ブートプロセスにおいては、取るに足らない要素である。
x86アーキテクチャとは異なり、SPARCアーキテクチャにおいては、カーネルイメージはvmlinuxファイル自体を単にgzipで圧縮したものである。これはSPARC Linuxシステムにおいて使用される、SILO (ブートローダ)ブートローダがgzipで圧縮されたイメージを透過的に伸長できるからである。
ブートイメージのファイル名はさほど重要ではなく(なぜなら近年のブートローダはイメージを設定ファイルで指定可能、もしくはブート時に指定可能だからである)、しかし、慣習的には、<tt>vmlinuz</tt> または <tt>zImage</tt>となる場合が多い。
Linuxカーネル開発が成熟するにつれて、ユーザーが生成するカーネルのサイズはいくつかのアーキテクチャにおいて課される制限である、圧縮されたカーネルコードが保持可能なサイズを越えるまでになってきた。
この制限に対処するため、カーネルを不連続なメモリ領域にうまく分離した、bzImage (big zImage) フォーマットが開発された。
bzImageはzlibアルゴリズムを用い圧縮されている。また2.6.30からは更によりよいアルゴリズムで圧縮可能である。もっともらしい誤解は、bzがファイル名の接頭辞にあるため、このイメージがbzip2で圧縮されているのだということが語られるが、そうとは限らない。(確かに、bzip2パッケージは"bz"の接頭辞のついたツールともに配布されている。例えば、bzip2で圧縮されたテキストファイルをlessページャを通して透過的に読むことができる、<tt>bzless</tt>、同様にcatコマンドに対する<tt>bzcat</tt>などが付属している。)
Linuxカーネルイメージは通常、Linuxカーネルソースのディレクトリ(もしくはオプションを指定し、最終生成物を出力するディレクトリ)において次のコマンドを実行することで得られる。
<pre>
make</pre>
現行のバージョン2.6では、アーキテクチャによって、このとき暗黙のうちに指定されるターゲットは異なる。例えばx86(i386ならびにx86-64)アーキテクチャにおいては、暗黙のうちに
<pre>
make bzImage</pre>
と指定されている。これは<tt>bzImage</tt>を生成せよとの指令を発行したことになる。
正常にビルドが進むと、端末エミュレータに以下のように表示される(以下、i386をターゲットとしたバージョン2.6.38-rc2のビルドより一部抜粋した。ビルドプロセスはリリース毎、アーキテクチャ毎に多岐に渡るため、この環境での説明に限定する)。
[... ビルドプロセス("表示"をクリックしてください。) ...]
<pre>
...
CC      init/calibrate.o
LD      init/built-in.o
...
AR      arch/x86/lib/lib.a
LD      vmlinux.o
MODPOST vmlinux.o
...
LD      .tmp_vmlinux1
KSYM    .tmp_kallsyms1.S
AS      .tmp_kallsyms1.o
LD      .tmp_vmlinux2
KSYM    .tmp_kallsyms2.S
AS      .tmp_kallsyms2.o
...
LD      vmlinux
SYSMAP  System.map
SYSMAP  .tmp_System.map
CC      arch/x86/boot/a20.o
...
CC      arch/x86/boot/edd.o
VOFFSET arch/x86/boot/voffset.h
LDS     arch/x86/boot/compressed/vmlinux.lds
AS      arch/x86/boot/compressed/head_32.o
CC      arch/x86/boot/compressed/misc.o
CC      arch/x86/boot/compressed/string.o
CC      arch/x86/boot/compressed/cmdline.o
CC      arch/x86/boot/compressed/early_serial_console.o
OBJCOPY arch/x86/boot/compressed/vmlinux.bin
HOSTCC  arch/x86/boot/compressed/relocs
RELOCS  arch/x86/boot/compressed/vmlinux.relocs
GZIP    arch/x86/boot/compressed/vmlinux.bin.gz
HOSTCC  arch/x86/boot/compressed/mkpiggy
MKPIGGY arch/x86/boot/compressed/piggy.S
AS      arch/x86/boot/compressed/piggy.o
LD      arch/x86/boot/compressed/vmlinux
ZOFFSET arch/x86/boot/zoffset.h
AS      arch/x86/boot/header.o
...
CC      arch/x86/boot/video-bios.o
LD      arch/x86/boot/setup.elf
OBJCOPY arch/x86/boot/setup.bin
OBJCOPY arch/x86/boot/vmlinux.bin
HOSTCC  arch/x86/boot/tools/build
BUILD   arch/x86/boot/bzImage
Root device is (8, 3)
Setup is 15068 bytes (padded to 15360 bytes).
System is 2828 kB
CRC 5051e6e4
Kernel: arch/x86/boot/bzImage is ready  (#1)</pre>
カーネルに静的リンクされる全てのオブジェクトコードは<tt>vmlinux.o</tt>にリンクされる。これとは別に、全てのオブジェクトファイルからシンボルのみを取り出し、単一のELFセクションヘッダとして保持するオブジェクトコード<tt>.tmp_kallsyms2.o</tt>(数字はカーネルコンフィグレーションにより異なる)が生成される。この2つのファイルをリンクしたものが本記事の対象とする実行ファイル<tt>vmlinux</tt>である。続いてこのファイルにnmコマンドをかけ、シンボルテーブルSystem.mapファイルを生成する(<tt>.tmp_System.map</tt>は比較検査のため生成する)。続いて圧縮ルーチンを含めたカーネルイメージのブート用ルーチンのビルドが開始される。
(以下、x86アーキテクチャにおいて$(BITS)はビット数に読み替えてほしい)
カーネルの圧縮、カーネルイメージの伸長などに関するソースコードは<tt>arch/x86/boot/compressed</tt>に存在する。
<tt>arch/x86/boot/compressed/head_$(BITS).o</tt>はカーネルイメージのメモリアドレス前方に位置し、BIOSから得た低レベルなハードウェアの情報を処理するためのコードである。詳細はソースコードの<tt>arch/x86/boot/compressed/head_$(BITS).S</tt>やそのエントリポイントstartup_$(BITS)、リンカスクリプトなどを参照せよ。
<tt>arch/x86/boot/compressed/misc.o</tt>は後方に位置するカーネル本体を含むコードをzlibなどのアルゴリズムを用いて伸長するコードである。有名なUncompressing Linux...というコンソールの表示はこの処理において見られる(実際の処理はアーキテクチャ毎に様々で、BIOSや起動直後のシステムが認識可能なメモリアドレスの制限のため、段階的に複雑なメモリ配置を行いこれを実現している。詳しくは、<tt>arch/x86/boot/compressed/misc.c</tt>のdecompress_kernelなどのキーワードを参考に処理内容を見てほしい)。
<tt>head_$(BITS).o</tt>, <tt>misc.o</tt>その他カーネルイメージの伸長後に利用する低レベルコードをビルドし終わると、<tt>piggy.o</tt>というオブジェクトコードと一緒にリンクされ、<tt>arch/x86/boot/compressed/vmlinux</tt>という実行ファイルを生成する(このファイルは本記事で説明の対象としている<tt>vmlinux</tt>ではないことに注意せよ)。<tt>piggy.o</tt>は次のようなプロセスをたどり生成される。ディレクトリのトップにある<tt>vmlinux</tt>は、GNU Binutilsにより配布されるobjcopyコマンドを利用し、.commentなど不要なELFセクションヘッダとシンボル情報を全て削除されたうえで実行ファイル<tt>arch/x86/boot/compressed/vmlinux.bin</tt>に変換される。このファイルに圧縮をかけ更に専用のツールを用いて、<tt>vmlinux.bin.gz</tt>(圧縮アルゴリズムにより拡張子は異なる)をELFのセクションヘッダに埋め込んだ特殊なELFオブジェクトファイルが生成される。これが<tt>piggy.o</tt>である(詳しくは<tt>arch/x86/boot/compressed/Makefile</tt>を参照せよ)。
この後ブート用の低レベルコードのビルドが続く。カーネルのブートに関するコードは前述の圧縮・伸長コードも含め、<tt>arch/x86/boot</tt>に存在する。
<tt>arch/x86/boot/header.o</tt>はカーネルイメージの先頭メモリアドレスに存在するコードである。エントリーポイント_startから開始されるこのコードにより前述した伸長用ルーチンを後ほど呼び出す。
このコードは以前のリリースにおける、2つのコード<tt>bootsect.o</tt>、<tt>setup.o</tt>の一部を抜きだし、再構成したものである。ちなみに<tt>bootsect.o</tt>はフロッピーディスク用のダイレクトブート用コードであったがバージョン2.6では、<tt>header.S</tt>の一部にダイレクトブートできない旨のメッセージを表示するコードのみ残っているに過ぎない。<tt>setup.o</tt>の残部は細かく分けられ、一部は前述のhead_$(BITS).oにも含まれている。
このオブジェクトファイルも含め、ビデオBIOS用処理などの低レベルなコードが<tt>arch/x86/boot/setup.elf</tt>という実行ファイルとしてリンクされる。
ここまで正常に終了したならば、bzImageの完成は目前となる。<tt>arch/x86/boot/setup.elf</tt>と<tt>arch/x86/boot/compressed/vmlinux</tt>はobjcopyコマンドの特殊なオプションによりそれぞれELFバイナリからrawバイナリ(「生バイナリ」とも。リロケーション情報を削除し、メモリ上に直接展開・実行可能なコード。詳細はobjcopyのマニュアルまたはInfoドキュメントを参照せよ)<tt>arch/x86/boot/setup.bin</tt>と<tt>arch/x86/boot/vmlinux.bin</tt>に変換される。この二つをカーネルビルド時にしか使われない専用ツールで結合すると、<tt>arch/x86/boot/bzImage</tt>が完成する。
ビルドの終了メッセージの意味は次の通りである。
"Root device is (8, 3)"、これはルートディレクトリのマウントされているファイルシステムはメジャー番号8番、マイナー番号3番(デバイスファイル参照)にあることを予めカーネルイメージに埋め込んだことを示す(ルートファイルシステムがブートローダで指定されていない場合、デフォルトで使用される)。すなわちこれはSCSIディスクの第一番目のディスク先頭第3パーティション(/dev/sda3)にルートファイルシステムがあることを示す。次の2行はカーネルイメージのデータサイズを示している。CPUがリアルモード状態のときに使用されるセットアップコード("Setup")は、セクタサイズにきれいに収めるため、512の倍数となる15360バイトにパディングされたことを通知している。"System"はカーネル本体を含むコードでこの場合1MBをゆうに越え、2828kBとなっている。続いてイメージのCRCが検出され、これが初回(#1)のビルドであることを通知し、ビルドプロセスは終了する。
以上よりbzImageファイルは特殊なバイナリフォーマットを持っている: すなわち、主に次のデータを連結し、構成されていることが分かる。
<pre>
bzImage = setup.bin + vmlinux.bin
setup.bin <---(raw binary)--- setup.elf <--- header.o + main.o + more...
vmlinux.bin <---(raw binary)--- head_(BITS).o + misc.o + more... + piggy.o
piggy.o <---(compressed + embedded)--- vmlinux <--- vmlinux.o + .tmp_kallsyms2.o <--- *.o *.a
`|
System.map
</pre>
bzImageが開発された経緯の通り、アーキテクチャによってはブート開始直後のメモリ領域は非常に限られている。bzImageは限られたメモリ領域を有効活用するため、ジャンプ命令などを巧みに利用しており、その処理は一般には複雑である。x86システムのブートでは、BIOSから起動されたブートローダがカーネルイメージ(とinitrd)をメモリにロードする。このとき、カーネルイメージは前項の2セクションで分割した上でロードされる。"Setup"部分がブードローダの直後のメモリアドレスにBIOSなどの予約エリアを上書きしないようにロードされる。"System"はメモリアドレス0x100000(=1MB)から後方にロードされる(こちらはもとよりリアルモード下では原則アクセスできないはずなので、上書きの心配はない)。initrdは"System"より後方のメモリアドレスにロードされる。"Setup"はBIOS割り込みルーチンを駆使し、<tt>header.o</tt>などの処理を経て<tt>head_$(BITS).o</tt>コードに移行し、CPUをプロテクトモードに遷移する。続いて、<tt>head_$(BITS).o</tt>から<tt>misc.o</tt>が呼び出されその後続のデータとなっているカーネル本体が伸長される。伸長後はより複雑な処理となる。以降の処理内容は順に述べるのみとし、必要ならばソースコードを参照して欲しい。伸長済みのカーネル本体に処理を移すと、
CPU認識と特定の処理実行、SMP関連の処理実行
ここまでは(インラインアセンブラを含む)アセンブラコードが多く含まれている。以降はアーキテクチャ非依存となる。
<tt>init/main.o</tt>のstart_kernel()に処理が移り
カーネルスレッドの起動、マルチタスク処理とスケジューラ起動
"真の"ルートディレクトリのマウント
init起動準備 (以降の処理はユーザー空間)
を行い、システムのブートアップが完了する。
現時点ではbzImageファイルを伸長する特定のツールはない。しかし、カーネルソースコードのアーカイブ内にというbashベースの簡易なシェルスクリプトが存在する。このスクリプトは、引数に与えたイメージを伸長し、イメージからカーネルビルドコンフィグレーション(Kconfig)を抽出する。場合によっては、伸長したイメージを直接得るため、bzImageを改変することも可能である。いくつかのディストリビューション、例として、RedHatとそのクローン(CentOSなど)にはカーネルのRPMパッケージに対応する<tt>vmlinux</tt>ファイルを持つ<tt>kernel-debuginfo</tt>なるパッケージがあるかもしれない。概して、そのようなパッケージは<tt>/usr/lib/debug/lib/modules/`uname -r`/vmlinux</tt>にインストールされる。
以下は、x86-64アーキテクチャにおけるGentoo Linuxで稼働する、カーネル(バージョン2.6.29)の実行可能イメージからヘッダ情報を抽出したものである(GNU Binutilsパッケージに付属するreadelfコマンドは、ELF定義済みヘッダを出力するコマンドである)。
<pre>
$ readelf -h vmlinux
ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                             ELF64
Data:                              2's complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX - System V
ABI Version:                       0
Type:                              EXEC (Executable file)
Machine:                           Advanced Micro Devices X86-64
Version:                           0x1
Entry point address:               0x1000000
Start of program headers:          64 (bytes into file)
Start of section headers:          13951312 (bytes into file)
Flags:                             0x0
Size of this header:               64 (bytes)
Size of program headers:           56 (bytes)
Number of program headers:         5
Size of section headers:           64 (bytes)
Number of section headers:         45
Section header string table index: 42
</pre>
ちなみに、同じコマンドをbzImageに実行しても次のようなエラーが返されるだけである。
<pre>
$ readelf -h arch/x86/boot/bzImage
readelf: Error: Unable to seek to 0xc031f2eb for section headers
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
</pre>
bzImageは前述の通りELFヘッダを持たないrawバイナリである。