4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Linux カーネルモジュールの Makefile は二度呼ばれる

Last updated at Posted at 2023-06-28

はじめに

この記事の目的

Linux のカーネルモジュールをカーネルのソースの外でビルドする際、いろいろな資料にはたいてい次のような Makefile が紹介されています。(この 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 内に混在しているからです。どの文がどちらに属しているかをコメント付きで説明すると次のようになります。

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 には次のような記述があります。

$(KERNEL_SRC)/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 が空でない場合は、次のように各種変数が初期化されます。

$(KERNEL_SRC)/Makefile
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 で指定されたサブディレクトリをビルドする際のルールは次のようになっています。

$(KERNEL_SRC)/Makefile
PHONY += $(build-dir)
$(build-dir): prepare
	$(Q)$(MAKE) $(build)=$@ need-builtin=1 need-modorder=1 $(single-goals)

ここで使用されている変数 build$(KERNEL_SRC)/scripts/Kbuild.include で次のように定義されています。

$(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 で指定されたディレクトリが設定されます。

$(KERNEL_SRC)/scripts/Makefile.build
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 の読み込みが行われます。

$(KERNEL_SRC)/scripts/Makefile.build
# 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 に設定されます。

$(KERNEL_SRC)/scripts/Makefile.build
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 で示されたファイルたちが指定されます。

$(KERNEL_SRC)/scripts/Makefile.build
# Build
# ---------------------------------------------------------------------------

$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
	 $(if $(KBUILD_MODULES), $(targets-for-modules)) \
	 $(subdir-ym) $(always-y)
	@:

ターゲットが $(obj)/%.o でそのソースが $(src)/%.c だった場合のビルドルールは次のようになっています。

$(KERNEL_SRC)/scripts/Makefile.build
# 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 で次のように定義されています。

$(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 で次のように定義されています。

$(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 で次のように定義されています。

$(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 で次のように定義されています。

$(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 で次のように定義されています。

$(KERNEL_SRC)/scripts/Makefile.build
      cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \
		$(cmd_ld_single_m) \
		$(cmd_objtool)

やっと C Compiler ($(CC)) までたどり着きました。
この記事ではこれ以上深追いしませんので、興味のある方は自力で調べてください。

参考

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?