Linuxシステムにおいて、vmlinuxとは、内部にLinuxカーネル本体を包含する静的リンクされた実行ファイルである。ELF、COFF 、a.outのような実行可能バイナリ形式に準じた形式が利用されるが、通常の実行可能バイナリと異なりカーネルのexecシステムコールが対応している形式ではなく、ブートローダーが対応している形式でなければならない。vmlinuxファイルはカーネルデバッグ、カーネルのシンボルテーブル生成、またはその他の用途で利用される。通常、コンパイラが生成するバイナリから、さらに全てのシンボルを取り除き、圧縮をかけ、マルチブート用ヘッダ、ブートセクタ、BIOS用ローレベルブートセットアップルーチン、自己伸長ルーチンなどを追加して最終的に、ブート可能なイメージが完成する。これをカーネルイメージ[1]と呼んで区別する場合もある。
通常はカーネルのビルドが正常終了すると、ソースコードのトップディレクトリにこのファイルが存在する。なお、同名や類似した名前の中間ファイルが幾多も生成されるため注意が必要である。
古くから、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ディストリビューターは、ユーザにハードディスクの先頭にブートのためのファイルシステム、すなわちカーネルと、システムのブートに必要なファイルを置くパーティションを作成するよう推奨した(正確にはファイルシステムに含まれるもの以外に、ブートのためにはそのパーティションの先頭のen:Volume boot recordも正しく設定される必要がある。XFSは実装上そのための空間が確保されないため、このファイルシステムには使えない[2]。あるいはブート手順中のそこを迂回する何らかの別の方法が必要となる)。
慣習として、ブート後にそれとは別のパーティションにあるファイルシステムをルートとする場合は、このブート専用ファイルシステムはブート後は /boot
にマウントされた(安全のため、カーネルのアップデート時など以外はマウントしない運用とすることもある)。のちにこれはFilesystem Hierarchy Standard (FHS) により標準化された。詳しくは、FHSバージョン2.3のセクション3.5.2を参照してほしい。
旧来より、ブート可能なカーネルイメージを生成した際、カーネルはまた、zlibアルゴリズムを用い圧縮されている。Linuxカーネルバージョン2.6.30[3]からはこれに加え、圧縮率が高いLZMAやBZIP2アルゴリズムによる圧縮もサポートされている。システムに電源を投入し、BIOSがブートローダを読み込み、ブートローダが圧縮されたカーネルイメージコードを読み込む。このコードからカーネルコードが伸長(展開)されると、いくつかのシステムでは、ブートプロセスが進行するにつれコンソールにドット(.)が何個も表示される。
圧縮ルーチンは、いくつかのアーキテクチャ、とりわけ、i386のようにブート時に読み込めるデータサイズが極端に制限される場合必須であったが、bzImageが開発された現在においては、ブートプロセスにおいては、取るに足らない要素である。
x86アーキテクチャとは異なり、SPARCアーキテクチャにおいては、カーネルイメージはvmlinuxファイル自体を単にgzipで圧縮したものである[4]。これはSPARC Linuxシステムにおいて使用される、SILOブートローダがgzipで圧縮されたイメージを透過的に伸長できるからである。
近年のブートローダは、カーネルイメージを設定ファイルもしくはブート時に指定可能であるため、そのファイル名は重要ではなくなっているが、慣習的には、vmlinuz または zImageとなる場合が多い。
Linuxカーネル開発が成熟するにつれて、ユーザーが生成するカーネルのサイズはいくつかのアーキテクチャにおいて課される制限である、圧縮されたカーネルコードが保持可能なサイズを越えるまでになってきた。
この制限に対処するため、カーネルを不連続なメモリ領域にうまく分離した、bzImage (big zImage) フォーマットが開発された。
bzImageはzlibアルゴリズムを用い圧縮されている。また2.6.30[3]からは更によりよいアルゴリズムで圧縮可能である。もっともらしい誤解は、bzがファイル名の接頭辞にあるため、このイメージがbzip2で圧縮されているのだということが語られるが、そうとは限らない。(確かに、bzip2パッケージは"bz"の接頭辞のついたツールともに配布されている。例えば、bzip2で圧縮されたテキストファイルをlessページャを通して透過的に読むことができる、bzless、同様にcatコマンドに対するbzcatなどが付属している。)
Linuxカーネルイメージは通常、Linuxカーネルソースのディレクトリ(もしくはオプションを指定し、最終生成物を出力するディレクトリ)において次のコマンドを実行することで得られる。
make
現行のバージョン2.6では、アーキテクチャによって、このとき暗黙のうちに指定されるターゲットは異なる。例えばx86(i386ならびにx86-64)アーキテクチャにおいては、暗黙のうちに
make bzImage
と指定されている。これはbzImageを生成せよとの指令を発行したことになる。 正常にビルドが進むと、端末エミュレータに以下のように表示される(以下、i386をターゲットとしたバージョン2.6.38-rc2のビルドより一部抜粋した。ビルドプロセスはリリース毎、アーキテクチャ毎に多岐に渡るため、この環境での説明に限定する)。
... 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)
カーネルに静的リンクされる全てのオブジェクトコードはvmlinux.oにリンクされる。これとは別に、全てのオブジェクトファイルからシンボルのみを取り出し、単一のELFセクションヘッダとして保持するオブジェクトコード.tmp_kallsyms2.o(数字はカーネルコンフィグレーションにより異なる)が生成される。この2つのファイルをリンクしたものが本記事の対象とする実行ファイルvmlinuxである。続いてこのファイルにnmコマンドをかけ、シンボルテーブルSystem.mapファイルを生成する(.tmp_System.mapは比較検査のため生成する)。続いて圧縮ルーチンを含めたカーネルイメージのブート用ルーチンのビルドが開始される。
(以下、x86アーキテクチャにおいて$(BITS)はビット数に読み替えてほしい)
カーネルの圧縮、カーネルイメージの伸長などに関するソースコードはarch/x86/boot/compressedに存在する。
arch/x86/boot/compressed/head_$(BITS).oはカーネルイメージのメモリアドレス前方に位置し、BIOSから得た低レベルなハードウェアの情報を処理するためのコードである。詳細はソースコードのarch/x86/boot/compressed/head_$(BITS).S[5]やそのエントリポイントstartup_$(BITS)
、リンカスクリプトなどを参照せよ。
arch/x86/boot/compressed/misc.oは後方に位置するカーネル本体を含むコードをzlibなどのアルゴリズムを用いて伸長するコードである。有名なUncompressing Linux...[注釈 1]というコンソールの表示はこの処理において見られる(実際の処理はアーキテクチャ毎に様々で、BIOSや起動直後のシステムが認識可能なメモリアドレスの制限のため、段階的に複雑なメモリ配置を行いこれを実現している。詳しくは、arch/x86/boot/compressed/misc.c[6]のdecompress_kernel
などのキーワードを参考に処理内容を見てほしい)。
head_$(BITS).o, misc.oその他カーネルイメージの伸長後に利用する低レベルコードをビルドし終わると、piggy.oというオブジェクトコードと一緒にリンクされ、arch/x86/boot/compressed/vmlinuxという実行ファイルを生成する(このファイルは本記事で説明の対象としているvmlinuxではないことに注意せよ)。piggy.oは次のようなプロセスをたどり生成される。ディレクトリのトップにあるvmlinuxは、GNU Binutilsにより配布されるobjcopy[7]コマンドを利用し、.comment
など不要なELFセクションヘッダとシンボル情報を全て削除されたうえで実行ファイルarch/x86/boot/compressed/vmlinux.binに変換される。このファイルに圧縮をかけ更に専用のツールを用いて、vmlinux.bin.gz(圧縮アルゴリズムにより拡張子は異なる)をELFのセクションヘッダに埋め込んだ特殊なELFオブジェクトファイルが生成される。これがpiggy.oである(詳しくはarch/x86/boot/compressed/Makefile[8]を参照せよ)。
この後ブート用の低レベルコードのビルドが続く。カーネルのブートに関するコードは前述の圧縮・伸長コードも含め、arch/x86/bootに存在する。
arch/x86/boot/header.o[9]はカーネルイメージの先頭メモリアドレスに存在するコードである。エントリーポイント_start
から開始されるこのコードにより前述した伸長用ルーチンを後ほど呼び出す。
このオブジェクトファイルも含め、ビデオBIOS用処理などの低レベルなコードがarch/x86/boot/setup.elfという実行ファイルとしてリンクされる。
ここまで正常に終了したならば、bzImageの完成は目前となる。arch/x86/boot/setup.elfとarch/x86/boot/compressed/vmlinuxはobjcopyコマンドの特殊なオプションによりそれぞれELFバイナリからrawバイナリ(「生バイナリ」とも。リロケーション情報を削除し、メモリ上に直接展開・実行可能なコード。詳細はobjcopyのマニュアル[7]またはInfoドキュメント[10]を参照せよ)arch/x86/boot/setup.binとarch/x86/boot/vmlinux.binに変換される。この二つをカーネルビルド時にしか使われない専用ツールで結合すると、arch/x86/boot/bzImageが完成する。
ビルドの終了メッセージの意味は次の通りである。 "Root device is (8, 3)"、これはルートディレクトリのマウントされているファイルシステムはメジャー番号8番、マイナー番号3番(デバイスファイル参照)にあることを予めカーネルイメージに埋め込んだことを示す(ルートファイルシステムがブートローダで指定されていない場合、デフォルトで使用される)。すなわちこれはSCSIディスクの第一番目のディスク先頭第3パーティション(/dev/sda3)にルートファイルシステムがあることを示す。次の2行はカーネルイメージのデータサイズを示している。CPUがリアルモード状態のときに使用されるセットアップコード("Setup")は、セクタサイズにきれいに収めるため、512の倍数となる15360バイトにパディングされたことを通知している。"System"はカーネル本体を含むコードでこの場合1MBをゆうに越え、2828kBとなっている。続いてイメージのCRCが検出され、これが初回(#1)のビルドであることを通知し、ビルドプロセスは終了する。
以上よりbzImageファイルは特殊なバイナリフォーマットを持っている: すなわち、主に次のデータを連結し、構成されていることが分かる。
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
bzImageが開発された経緯の通り、アーキテクチャによってはブート開始直後のメモリ領域は非常に限られている。bzImageは限られたメモリ領域を有効活用するため、ジャンプ命令などを巧みに利用しており、その処理は一般には複雑である[11]。x86システムのブートでは、BIOSから起動されたブートローダがカーネルイメージ(とinitrd)をメモリにロードする。このとき、カーネルイメージは前項の2セクションで分割した上でロードされる。"Setup"部分がブードローダの直後のメモリアドレスにBIOSなどの予約エリアを上書きしないようにロードされる。"System"はメモリアドレス0x100000(=1MB)から後方にロードされる(こちらはもとよりリアルモード下では原則アクセスできないはずなので、上書きの心配はない)。initrdは"System"より後方のメモリアドレスにロードされる。"Setup"はBIOS割り込みルーチンを駆使し、header.oなどの処理を経てhead_$(BITS).oコードに移行し、CPUをプロテクトモードに遷移する。続いて、head_$(BITS).oからmisc.oが呼び出されその後続のデータとなっているカーネル本体が伸長される。伸長後はより複雑な処理となる。以降の処理内容は順に述べるのみとし、必要ならばソースコードを参照して欲しい。伸長済みのカーネル本体に処理を移すと、
ここまでは(インラインアセンブラを含む)アセンブラコードが多く含まれている。以降はアーキテクチャ非依存となる。
init/main.o[12]のstart_kernel()
に処理が移り
を行い、システムのブートアップが完了する。
現時点ではbzImageファイルを伸長する特定のツールはない。しかし、カーネルソースコードのアーカイブ内にscripts/extract-ikconfigというbashベースの簡易なシェルスクリプトが存在する。このスクリプトは、引数に与えたイメージを伸長し、イメージからカーネルビルドコンフィグレーション(Kconfig)を抽出する。場合によっては、伸長したイメージを直接得るため、bzImageを改変することも可能である[13]。いくつかのディストリビューション、例として、RedHatとそのクローン(CentOSなど)にはカーネルのRPMパッケージに対応するvmlinuxファイルを持つkernel-debuginfoなるパッケージがあるかもしれない。概して、そのようなパッケージは/usr/lib/debug/lib/modules/`uname -r`/vmlinuxにインストールされる。
以下は、x86-64アーキテクチャにおけるGentoo Linuxで稼働する、カーネル(バージョン2.6.29)の実行可能イメージからヘッダ情報を抽出したものである(GNU Binutilsパッケージに付属するreadelfコマンドは、ELF定義済みヘッダを出力するコマンドである[14])。
$ 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
ちなみに、同じコマンドをbzImageに実行しても次のようなエラーが返されるだけである。
$ 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
bzImageは前述の通りELFヘッダを持たないrawバイナリである。
objcopy(1)
– Linux User Commands Manual (en)
readelf(1)
– Linux User Commands Manual (en)