起動するまでの長い道のり IPL編(9) ビルド環境makeの巻

 512バイトの制限を超えてカーネルの読み込みができるところまでをやった。これからソースファイルがどんどん増えていくと、ビルドがかなりめんどくさくなる。そこで今回は、makeを使った一括ビルドの方法を解説しようと思う。

makeについて

 makeとは、UNIX系OSのプログラミング環境で使われる一括ビルドのためのツールだ。一般にMakefileと呼ばれる設定ファイルにビルド方法を書いておくと、後はそのMakefileがあるディレクトリでmakeを実行するだけで自動的にビルドが行われる。あるソースコードが変更された場合、そのソースだけがコンパイルされ直して後は再リンクされるだけ、といったスマートなこともやってくれたりする。
 makeは、うまく使うとプログラムのビルド以外にもWebの更新や日常のバックアップやファイアーウォールのルール変更やその他色々なことに活用できる。IDE全盛の昨今ではいささか古色蒼然としてきたツールだけど、まだまだ使い勝手はあるので使ってみよう。
 ここではごく基本的なコンセプトと使い方だけをさらっとやる。詳しくはブックマークにあるMakeの解説サイトを参照して欲しい。

Makefileに依存関係を書く

 makeの基本的な考え方は以下の通りだ。
 成果物←ソースという関係(依存関係という)を記述しておき、成果物より新しい(更新された)ソースがあるかどうか調べ、あった場合は成果物を作り直す。ソースは複数の場合もある。
 さらに、別の場所でソース自身が成果物として書かれていて、そのソースが指定されている場合もある。この場合、元の成果物を作る時は、直接のソースだけではなく、ソースのソース・ソースのソースのソース……と再帰的に調べて構築していく。
 例えばfd.imgはkernel.binに依存している。kernel.binはipl.oとsetup.oに依存している。ipl.oはipl.sに依存している。だから、最新版のfd.imgを作る場合はipl.sが更新されているかまで遡って調べる必要がある。makeはそれをちゃんとやってくれる。(ちゃんとMakefileが書かれていれば、だが)

 具体的な例を挙げる。ipl.sからipl.oが作られるという関係は、Makefileというファイル(拡張子なし)を作って以下のように書く。

# Makefile

ipl.o: ipl.s
	as -o ipl.o ipl.s

 4行目行頭の空白はタブだ。ここはタブでないといけないので注意しよう。
 コロンで区切られた部分が依存関係を記述している。makeはこの依存関係を見て、ipl.oが存在しないかもしくはipl.oよりipl.sの方が新しかった場合、次の行のコマンドを実行する。そうして、最新のipl.oが生成される。
 上まで書いたところでmakeを実行すると、実際にipl.oが生成されるはずだ。ただ、ipl.oが存在していてソースを何もいじっていない場合は「`ipl.o' is up to date」等と表示されるかもしれない。これは、ipl.oが既にあって更新する必要がないというメッセージだ。実際に生成されるところを確かめたければipl.oを削除した上で再実行してみよう。

 さて、カーネルのビルドにはipl.oだけではなくsetup.oも必要になる。これも同じようにMakefileに書こう。それにカーネル全体をビルドできるようにもしたい。

# Makefile

kernel: ipl.o setup.o ipl.ls
	ld -T ipl.ls -o kernel.exe ipl.o setup.o
	objcopy -S -O binary kernel.exe kernel.bin
	dd if=/dev/zero of=fd.img count=2880
	dd if=kernel.bin of=fd.img conv=notrunc

ipl.o: ipl.s
	as -o ipl.o ipl.s
setup.o: setup.s
	as -o setup.o setup.s

 kernelという依存関係は、ファイルと全然関係のない名前だけの依存関係だ。これで例えば「make kernel」とするとkernelのソースが調べられ、更新されていればコマンドが実行される。コマンドの部分は今まで手入力していたものだ。
 一応これで動く。一応動くんだけど……。

ipl.o: ipl.s
	as -o ipl.o ipl.s
setup.o: setup.s
	as -o setup.o setup.s

 この辺りが何かかっこ悪い気がする。依存関係の中に同じファイル名が何度も出てきたり、ipl.oとsetup.oがほとんど同じ事を実行しているのに別々にコマンドが書かれていたりする。これをまとめる方法はないか……。
 実はある。依存関係の中では、成果物やソースの名前を示す自動変数というものが使える。自動変数を使うと以下のように書き直せる。

ipl.o: ipl.s
	as -o $@ $^
setup.o: setup.s
	as -o $@ $^

 で、こうするとコマンド部分が冗長だとはっきり感じられる。なんで同じ事を2回も書かなきゃならないんだと。これもサフィックスルール(拡張子規則)というものでまとめられる。つまり、.oファイルと.sファイルの関係があったらこのコマンドを使う、と決められるのだ。

.s.o:
	as -o $@ $^

ipl.o: ipl.s
setup.o: setup.s

 これで随分すっきりした。新しいソースファイルが増えても、kernelの依存関係にオブジェクトファイルを追加し、ファイルの末尾に「XXX.o: XXX.s」を追加していけば済む。

Makefileで変数を使う

 makeでビルドするのはとりあえずできた。オブジェクト・ファイルやバイナリが自動で作られるようになった。でも、そういったオブジェクトやバイナリを削除してソースファイルだけ残したくなった。そういうことはできるのか。
 できる。ただしMakefileを書き直さないといけない。

# Makefile

kernel: ipl.o setup.o ipl.ls
	ld -T ipl.ls -o kernel.exe ipl.o setup.o
	objcopy -S -O binary kernel.exe kernel.bin
	dd if=/dev/zero of=fd.img count=2880
	dd if=kernel.bin of=fd.img conv=notrunc

clean:
	-rm -f fd.img ipl.bin ipl.exe ipl.o setup.o

.s.o:
	as -o $@ $^

ipl.o: ipl.s
setup.o: setup.s

 cleanという依存関係を新たに追加した。これで「make clean」とするとオブジェクト・ファイルやバイナリが削除されてきれいになる。通常「make」するだけでは、cleanは呼び出されない。
 ただ一つ面倒なことがある。ソースファイルが新たに追加になるたびにkernelとcleanの両方にオブジェクト・ファイル名を追加する必要が生じてしまう。これはなんというかカッコ悪い。
 そこで変数を使う。変数は文字通り値を入れておけるものだ。オブジェクト・ファイル名は変数として一まとめに定義しておき、kernelとcleanではその変数を参照するようにする。(なお、ワイルドカードを使う方法もあるが、危ないのと説明上の都合で今回は使わない)

# Makefile

OBJS = ipl.o setup.o

kernel: $(OBJS) ipl.ls
	ld -T ipl.ls -o kernel.exe ipl.o setup.o
	objcopy -S -O binary kernel.exe kernel.bin
	dd if=/dev/zero of=fd.img count=2880
	dd if=kernel.bin of=fd.img conv=notrunc

clean:
	-rm -f fd.img ipl.bin ipl.exe $(OBJS)

.s.o:
	as -o $@ $^

ipl.o: ipl.s
setup.o: setup.s

 「OBJS = ...」の部分が変数定義で、変数の中身を指定している。その後に出てくる「$(OBJS)」が変数を参照している箇所で、OBJSの中身に置き換えられる。
 これで、ソースが追加されてもOBJSにオブジェクト・コードを追加して依存関係を書くだけで済むようになる。

次回予告

 実行中のマシンをモニタリングする方法について少しやる。実は私もあまり知らない(汗)。

参考資料

GNU Make 第3版

GNU Make 第3版