序文
組み込み Linux の開発プロセスにおいて、カーネルのコンパイルは決して避けては通れないテーマです。
カーネルのコンパイル システムを明確に理解すると、少なくとも次のことが可能になります。
- カーネル全体の構造を理解する
- コンパイル時間を節約する
- コンパイルエラー時に問題を素早く特定する
- カーネルのブートについて詳しく見る
この記事では、Linux カーネルの .o ファイルのコンパイルから kbuild メカニズムについて説明します。
日にち | カーネルのバージョン | 建築 |
---|---|---|
2022-9-13 | Linux5.4.200 | 腕 |
実験
ターゲットログの拡張
例として、page_alloc.o のコンパイルからこの実験を開始します。
make mm/page_alloc.o
カーネル ルート ディレクトリの Makefile には次のものが含まれます。
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L1733
# Single targets
...
$(build-dirs): prepare
$(Q)$(MAKE) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(single-no-ko)),1) \
need-builtin=1 need-modorder=1
直接見るのはあまり直感的ではありませんが、カーネル バージョン 2.6 の Makefile のステートメントは比較的明確です。
%.o: %.c prepare scripts FORCE
$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
ロジックは比較的明確で、.o
ファイルはc
同じ名前の . ファイルに依存し、コマンドを実行してファイルを生成します。
ヒント: ここではちょっとしたトリックを紹介します。構築ステートメントをコメントアウトし、エラー ログを使用してステートメント展開の構造を逆に観察します。
$(build-dirs): prepare
#$(Q)$(MAKE) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(single-no-ko)),1) \
need-builtin=1 need-modorder=1
コンパイル コマンドを再度実行し、次のようにします。
#@make -f ./scripts/Makefile.build obj=mm \
single-build=1 \
need-builtin=1 need-modorder=1
ログから、実行が make を再度呼び出していることがわかりますmake mm/page_alloc.o
。script/Makefile.build
このルール ファイルを使用すると、渡されるパラメータは obj=mm です。
建てる
最初のセクションのログから、$(build)
展開は次のとおりであることがわかります。
-f ./scripts/Makefile.build obj
それはどこで定義されていますか? グローバル検索後の答えは、scripts/Kbuild.include
ヘッダー ファイルのようなものです。
https://elixir.bootlin.com/linux/v5.4.200/source/scripts/Kbuild.include#L160
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj
このファイルに注目してscripts/Makefile.build
、コンパイルに関連するステートメントを探してください.o
。
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $$(objtool_dep) FORCE
$(call if_changed_rule,cc_o_c) #调用if_changed_rull这个变量
$(call cmd,force_checksrc) #代码检查
$(call if_changed_rule,cc_o_c)
here という変数に注目してくださいif_changed_rull
。この変数も定義されています。scripts/Kbuild.include
# Usage: $(call if_changed_rule,foo)
# Will check if $(cmd_foo) or any of the prerequisites changed,
# and if so will execute $(rule_foo).
if_changed_rule = $(if $(any-prereq)$(cmd-check),$(rule_$(1)),@:)
論理的な判定文であり、条件が真の場合、カンマより前の動作が実行されます。条件が false の場合は、以下を実行します@:
。ここでの目的@:
は、ログ出力を減らすことです。詳細については、提出資料を参照してください: kernel/git/torvalds/linux.git - Linux カーネル ソース ツリー
条件が true の場合、rule_$(1)
展開は ですrule_cc_o_c
。
さて、scripts/Makefile.build
検索の定義に戻りましょうrule_cc_o_c
。
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,gen_ksymdeps)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,objtool)
$(call cmd,modversions_c)
$(call cmd,record_mcount)
endef
OK、これは物語が真実に近づく前の最後のステップです。$(call cmd_and_fixdep,cc_o_c)
私たちが焦点を当てるのは文章です。これは呼び出し関数でもあるため、ヘッダー ファイルに戻ってscripts/Kbuild.include
定義を見つけます。
cmd_and_fixdep = \
$(echo-cmd) $(cmd_$(1)); \
...
経験によれば、cmd_cc_o_c
それを、まだ scripts/Makefile.build ファイルに定義されています。
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
ついに真実が明らかになりました!
要約する
.o
ファイルのコンパイルは次の 4 つの手順に分かれています。
Makefile
---------------
1: %.o: %.c
make -f scripts/Makefile.build obj=mm
scripts/Makefile.build
---------------
2: $(obj)/%.o: $(src)/%.c
$(call if_changed_rule,cc_o_c)
scripts/Makefile.build
---------------
3: rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
scripts/Makefile.build
---------------
4: cmd_cc_o_c
$(CC) $(c_flags) -c -o $@ $<
ここでは、上で繰り返し述べた 2 つのファイルを強調します。
scripts/Makefile.build
: ほぼすべての重要なルールが含まれていますscripts/Kbuild.include
: ヘッダー ファイルと同様、多くの重要な機能が含まれています