はじめに
この記事の目的
Linux のカーネルモジュールをカーネルのソースの外でビルドする際、いろいろな資料にはたいてい次のような Makefile が紹介されています。(この Makefile は、諸々の雑多な部分を省略しています。)
obj-m := sample.o
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD)
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean
しかしながらこの Makefile がどのように動くのかの説明はあまり見かけません。
「この書き方が定番なのでこの通りにしておけ」みたいな感じですね。
しかし、実際のところ「変数 obj-m
って一体何者? この Makefile では使われていないのに」とか疑問がわきます。
そこでこの記事では、上記のような Makefile は実際のところどのように解釈されて実行されるのかを追っていきます。
カーネルモジュールの Makefile は二度読まれる
結論から先に述べると、上記のような Makefile を使ってカーネルモジュールをカーネルのソースの外でビルドする際、実はこの Makefile は二度読まれます。
一度目はカーネルのソースの外側で make
を実行した時に最初に読まれる場合です。
この一度目の呼び出しはカーネルのソース外で呼び出されることからカーネルツリー外(out of kernel tree)呼び出しとこの記事では表します。
二度目はこの Makefile のルールで定義されている make -C $(KERNEL_SRC) M=$(PWD)
が実行された時です。
この二度目の呼び出しは -C $(KERNEL_SRC)
で指定されたカーネルソースの内側で呼び出されることからカーネルツリー内(in kernel tree)呼び出しとこの記事では表します。
カーネルモジュールの Makefile が少しわかりにくいのは、カーネルツリー外(out of kernel tree)呼び出しで参照される部分とカーネルツリー内(in kernel tree)呼び出しで参照される部分が一つの Makefile 内に混在しているからです。どの文がどちらに属しているかをコメント付きで説明すると次のようになります。
#
# For in kernel tree variables
#
obj-m := sample.o
#
# For out of kernel tree variables
#
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
#
# For out of kernel tree rules
#
all:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD)
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean
make コマンド実行してから二度目の Makefile 呼び出しまで
一度目のカーネルモジュールの Makefile の読み込み
カーネルモジュールのディレクトリで make
コマンドを実行した際、カーネルモジュールの Makefile が読み込まれます。
カーネルツリーに移動して再度 make コマンド実行する
カーネルモジュールの Makefile で定義されている all や clean で指定されているルールは、カーネルツリー外で make
コマンドを実行する際に適応されます。
このルールが適応されると、-C $(KERNEL_SRC)
で指定された Linux のカーネルツリーにディレクトリを移動してから make
コマンドを再度呼び出します。その際変数 M
にカーネルモジュールのあるディレクトリ(正確にはこの Makefile があるディレクトリ)を指定します。
変数 KBUILD_EXTMOD の設定
Linux のカーネルツリーにある Makefile には次のような記述があります。
# Use make M=dir or set the environment variable KBUILD_EXTMOD to specify the
# directory of external module to build. Setting M= takes precedence.
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
$(if $(word 2, $(KBUILD_EXTMOD)), \
$(error building multiple external modules is not supported))
$(foreach x, % :, $(if $(findstring $x, $(KBUILD_EXTMOD)), \
$(error module directory path cannot contain '$x')))
# Remove trailing slashes
ifneq ($(filter %/, $(KBUILD_EXTMOD)),)
KBUILD_EXTMOD := $(shell dirname $(KBUILD_EXTMOD).)
endif
export KBUILD_EXTMOD
これは make
コマンドが実行された際にコマンドラインで変数 M
が指定された場合は、その変数 M
の内容を変数 KBUILD_EXTMOD
に設定することを示しています。
したがってこの例では変数 KBUILD_EXTMOD
にはカーネルモジュールのあるディレクトリが設定されます。
変数 build-dir の設定
変数 KBUILD_EXTMOD
が空でない場合は、次のように各種変数が初期化されます。
ifeq ($(KBUILD_EXTMOD),)
:
(中略)
:
else # KBUILD_EXTMOD
###
# External module support.
# When building external modules the kernel used as basis is considered
# read-only, and no consistency checks are made and the make
# system is not used on the basis kernel. If updates are required
# in the basis kernel ordinary make commands (without M=...) must be used.
# We are always building only modules.
KBUILD_BUILTIN :=
KBUILD_MODULES := 1
build-dir := $(KBUILD_EXTMOD)
compile_commands.json: $(extmod_prefix)compile_commands.json
PHONY += compile_commands.json
clean-dirs := $(KBUILD_EXTMOD)
clean: rm-files := $(KBUILD_EXTMOD)/Module.symvers $(KBUILD_EXTMOD)/modules.nsdeps \
$(KBUILD_EXTMOD)/compile_commands.json $(KBUILD_EXTMOD)/.thinlto-cache
PHONY += prepare
# now expand this into a simple variable to reduce the cost of shell evaluations
prepare: CC_VERSION_TEXT := $(CC_VERSION_TEXT)
prepare:
@if [ "$(CC_VERSION_TEXT)" != "$(CONFIG_CC_VERSION_TEXT)" ]; then \
echo >&2 "warning: the compiler differs from the one used to build the kernel"; \
echo >&2 " The kernel was built by: $(CONFIG_CC_VERSION_TEXT)"; \
echo >&2 " You are using: $(CC_VERSION_TEXT)"; \
fi
PHONY += help
help:
@echo ' Building external modules.'
@echo ' Syntax: make -C path/to/kernel/src M=$$PWD target'
@echo ''
@echo ' modules - default target, build the module(s)'
@echo ' modules_install - install the module'
@echo ' clean - remove generated files in module directory only'
@echo ''
endif # KBUILD_EXTMOD
変数 build-dir
には、変数 KBUILD_EXTMOD
に設定されているカーネルモジュールのあるディレクトリが設定されます。
build-dir のビルド
変数 build-dir
で指定されたサブディレクトリをビルドする際のルールは次のようになっています。
PHONY += $(build-dir)
$(build-dir): prepare
$(Q)$(MAKE) $(build)=$@ need-builtin=1 need-modorder=1 $(single-goals)
ここで使用されている変数 build
は $(KERNEL_SRC)/scripts/Kbuild.include
で次のように定義されています。
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj
したがって、このルールの処理は次のように解釈され、再び make
コマンドが実行されます。
その際に使用される Makefile は $(KERNEL_SRC)/scripts/Makefile.build
です。
ここで変数 KERNEL_SRC
は Linux のカーネルツリーのディレクトリを、変数 M
はカーネルモジュールのあるディレクトリを示します。
make -f $(KERNEL_SRC)/scripts/Makefile.build obj=$(M) need-builtin=1 need-modorder=1 $(single-goals)
カーネルツリー内ビルド時の変数の初期化
$(KERNEL_SRC)/scripts/Makefile.build
の最初のほうには次のようにビルド時に使用する各種変数が初期化されています。
変数 src
には変数 obj
で指定されたディレクトリが設定されます。
src := $(obj)
PHONY := $(obj)/
$(obj)/:
# Init all relevant variables used in kbuild files so
# 1) they have correct type
# 2) they do not inherit any value from the environment
obj-y :=
obj-m :=
lib-y :=
lib-m :=
always-y :=
always-m :=
targets :=
subdir-y :=
subdir-m :=
EXTRA_AFLAGS :=
EXTRA_CFLAGS :=
EXTRA_CPPFLAGS :=
EXTRA_LDFLAGS :=
asflags-y :=
ccflags-y :=
rustflags-y :=
cppflags-y :=
ldflags-y :=
subdir-asflags-y :=
subdir-ccflags-y :=
二度目のカーネルモジュールの Makefile の読み込み
ビルド時に使用する各種変数を初期化した後、次のように変数 src
(=コマンドラインから変数 obj
で指定された値)で指定されたディレクトリを検索して、Kbuild または Makefile を include します。つまり、ここで二度目のカーネルモジュールの Makefile の読み込みが行われます。
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
include $(or $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Makefile)
この時にカーネルモジュールの Makefile に 変数 obj-m
などが設定されていた場合、それらの値が反映されます。
ここでやっと、カーネルモジュールの Makefile で定義された変数 obj-m
が利用されます。
どうやら変数 obj-m
だけでななく、変数 subdir-m
や 変数lib-m
なども使えそうです。
おそらく変数 EXTRA_CFLAGS
も使えるでしょう。
targets-for-modules の設定
上で紹介した変数 obj-m
の内容は、次のように活用されて変数 targets-for-modules
に設定されます。
targets-for-modules := $(foreach x, o mod $(if $(CONFIG_TRIM_UNUSED_KSYMS), usyms), \
$(patsubst %.o, %.$x, $(filter %.o, $(obj-m))))
ifdef need-modorder
targets-for-modules += $(obj)/modules.order
endif
例えば、obj-m := sample.o
ならば、変数 targets-for-modules
には sample.o sample.mod modules.order
が設定されます。
カーネルモジュールのビルド
対象のカーネルモジュールの依存関係は次のようになっています。ここで変数 targets-for-modules
で示されたファイルたちが指定されます。
# Build
# ---------------------------------------------------------------------------
$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
ターゲットが $(obj)/%.o
でそのソースが $(src)/%.c
だった場合のビルドルールは次のようになっています。
# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
$(call if_changed_rule,cc_o_c)
$(call cmd,force_checksrc)
if_chenged_rule
は $(KERNEL_SRC)/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 $(if-changed-cond),$(rule_$(1)),@:)
ここで、条件が成立した時に適応されるルール rule_cc_o_c
は $(KERNEL_SRC)/scripts/Makefile.build
で次のように定義されています。
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,gen_ksymdeps)
$(call cmd,check_local_export)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,gen_objtooldep)
$(call cmd,gen_symversions_c)
$(call cmd,record_mcount)
endef
cmd_and_fixdep
は $(KERNEL_SRC)/scripts/Kbuild.include
で次のように定義されています。
cmd_and_fixdep = \
$(cmd); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
rm -f $(depfile)
cmd
は $(KERNEL_SRC)/scripts/Kbuild.include
で次のように定義されています。
cmd = @set -e; $(echo-cmd) $($(quiet)redirect) $(delete-on-interrupt) $(cmd_$(1))
つまり、$(call cmd_and_fixdep,cc_o_cc)
では cmd_cc_o_c
が実行され、その後に fixdep コマンドを実行しています。
cmd_cc_o_c
は $(KERNEL_SRC)/scripts/Makefile.build
で次のように定義されています。
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \
$(cmd_ld_single_m) \
$(cmd_objtool)
やっと C Compiler ($(CC)
) までたどり着きました。
この記事ではこれ以上深追いしませんので、興味のある方は自力で調べてください。