2012年2月13日月曜日

LinuxカーネルのMakefileを解析する その6

今回は、Linuxカーネルのmakeシステムの肝とも言える、drivers/, kernel/, fs/ などといったディレクトリを階層的に降りていきながら、各階層に置かれたMakefile(またはKbuild)ファイルをparseする仕組みを解明してみたいと思います。

ちなみに、各ディレクトリ階層に置く Makefileの書き方は
Documentation/kbuild/makefile.txt
に解説がありますが、ここでもちょこっと解説しておきます。

各階層のオブジェクトはMakefile の中で obj-y += hoge.o のようにobj-y変数に追加します。
また、 obj-y += piyo/ のようにディレクトリを追加すると、サブディレクトリに降りるという意味になります。
オブジェクトはディレクトリごとに built-in.o というファイルにまとめられます。

例で示した方がいいかもしれないですね。
以下のようにディレクトリ階層があって、各階層ごとに cのソースと Makefileがあったとする。


hoge
├── Makefile
├── hoge.c
└── piyo
    ├── Makefile
    ├── fuga
    │   ├── Makefile
    │   ├── fuga1.c
    │   └── fuga2.c
    └── piyo.c


Makefileにはその階層の オブジェクトと一つ下のディレクトリを記載します。

hoge/Makefileの内容:

obj-y := hoge.o  piyo/


hoge/piyo/Makefileの内容:

obj-y := piyo.o fuga/


hoge/piyo/fuga/Makefileの内容:

obj-y := fuga1.o fuga2.o


みたいに書いておく。


このとき、 各階層の.o は .c ファイルをコンパイルして作られます。

さらに、
hoge/piyo/fuga/fuga1.o と hoge/piyo/fuga/fuga2.oは ld -r でまとめられて、
hoge/piyo/fuga/build-in.o になります。

さらに、
hoge/piyo/fuga/built-in.o と hoge/piyo/piyo.o がまとめられて hoge/piyo/built-in.oになる。

さらに、
hoge/piyo/built-in.o と hoge/hoge.o がまとめられて、hoge/built-in.o になる。

hoge/built-in.o は他のオブジェクトとトップレベルでリンクされて カーネルイメージになるという具合です。


今回解読するファイルはトップのMakefileとscripts/Makefile.build, scripts/Makefile.lib あたりです。


まず、トップのMakefileの500行目付近にある

init-y    := init/
drivers-y := drivers/ sound/ firmware/
net-y     := net/
libs-y    := lib/
core-y    := usr/

に注目します。これが、これから降りていこうとするトップ階層のディレクトリ達です。

さらに700行目付近に以下のように書いてあります。


core-y        += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
             $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
             $(net-y) $(net-m) $(libs-y) $(libs-m)))

vmlinux-alldirs    := $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \
             $(init-n) $(init-) \
             $(core-n) $(core-) $(drivers-n) $(drivers-) \
             $(net-n)  $(net-)  $(libs-n)    $(libs-))))

init-y        := $(patsubst %/, %/built-in.o, $(init-y))
core-y        := $(patsubst %/, %/built-in.o, $(core-y))
drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y        := $(patsubst %/, %/built-in.o, $(net-y))
libs-y1        := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2        := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y        := $(libs-y1) $(libs-y2)

  ...

vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds


これをふまえて、

$ make vmlinux
としたときに何が起きるかを見てみます。


vmlinuxの構築ルールは900行目付近にあります。


vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
ifdef CONFIG_HEADERS_CHECK
    $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_SAMPLES
    $(Q)$(MAKE) $(build)=samples
endif
ifdef CONFIG_BUILD_DOCSRC
    $(Q)$(MAKE) $(build)=Documentation
endif
    $(call vmlinux-modpost)
    $(call if_changed_rule,vmlinux__)
    $(Q)rm -f .old_version


vmlinuxはリンカースクリプト$(vmlinux-lds)の他、$(vmlinux-init) $(vmlinux-main)といったオブジェクトに依存しています。
これらをリンクすることで vmlinuxができます。
$(vmlinux-init) $(vmlinux-main) はほとんどは、トップ階層のディレクトリに /built-in.o をつけたものです。
で、*/built-in.o はどうやって作るかというと、

# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs) ;


の行です。$(vmlinux-dirs) というディレクトリにだけ依存しています。
$(vmlinux-dirs) の構築ルールは以下です。

PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
 $(Q)$(MAKE) $(build)=$@

前回説明したように、build というのは
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
のことでした。

ここからは、Makefile.build を呼び出して、ディレクトリを降りていく処理です。
例えば、
$ make drivers
とやると、 drivers/ ディレクトリを降りて行ってくれます。

ここからは scripts/Makefile.build の解析です。

例えば、 make -f scripts/Makefile.build obj=drivers

とした場合です。

src := $(obj)

objの値(drivers)がsrcにコピーされる。

40行目付近で、

# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

とありまして、$(obj)/Makefile がinclude されます。

ここを抜けてきたときに、obj-y にリンク対象のオブジェクトファイルと、サブディレクトリが入っています。
その下に

include scripts/Makefile.lib

とありまして、Makefile.lib の中で obj-y を加工します。
関係ある部分だけを抜き出すと、、

__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
...
obj-y  := $(patsubst %/, %/built-in.o, $(obj-y))
...
subdir-ym := $(sort $(subdir-y) $(subdir-m))
...
# tell kbuild to descend
subdir-obj-y := $(filter %/built-in.o, $(obj-y))

...
obj-y  := $(addprefix $(obj)/,$(obj-y))
obj-m  := $(addprefix $(obj)/,$(obj-m))
lib-y  := $(addprefix $(obj)/,$(lib-y))
subdir-obj-y := $(addprefix $(obj)/,$(subdir-obj-y))


最終的に、
obj-y には同階層のオブジェクト群と一つ下の階層のbuilt-in.o
subdir-obj-y は一つ下の階層のbuilt-in.o
subdir-ym は一つ下の階層のディレクトリリスト
が入ります。


で、デフォルトターゲット __build の生成ルールを見ると以下のようになっています。

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
  $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
  $(subdir-ym) $(always)
 @:
ルールは @: なので何もしませんので、依存関係だけを書いています。
重要なのは
__build: $(builtin-target)
の部分です。

$(builtin-target)  は $(obj)/built-in.o のことです。
$(builtin-target) を生成するルールは以下です。

$(builtin-target): $(obj-y) FORCE
 $(call if_changed,link_o_target)


$(obj-y) を全部集めて、ld -r でまとめて1個のbuilt-in.oにするということです。

一つ下の階層のbuilt-in.o を作るためのルールは以下の部分です。

# To build objects in subdirs, we need to descend into the directories
$(sort $(subdir-obj-y)): $(subdir-ym) ;
...

PHONY += $(subdir-ym)
$(subdir-ym):
 $(Q)$(MAKE) $(build)=$@


$(subdir-ym) ディレクトリ以下にさらに降りていくために、再帰的に Makefile.build を呼び出しています。
$@ には、drivers/gpic, drivers/pci のように一つ下の階層のディレクトリ名が入っています。

このように、再帰的make を使うことでディレクトリ階層を降りていきます。

0 件のコメント:

コメントを投稿