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



はじめに
------------------------------------------------------------------------------------

### この記事の目的

Linux のカーネルモジュールをカーネルのソースの外でビルドする際、いろいろな資料にはたいてい次のような Makefile が紹介されています。(この 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: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 には次のような記述があります。

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

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

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

```Makefile:$(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``` はカーネルモジュールのあるディレクトリを示します。

```console
make -f $(KERNEL_SRC)/scripts/Makefile.build obj=$(M) need-builtin=1 need-modorder=1 $(single-goals)
```

### カーネルツリー内ビルド時の変数の初期化

```$(KERNEL_SRC)/scripts/Makefile.build``` の最初のほうには次のようにビルド時に使用する各種変数が初期化されています。
変数 ```src``` には変数 ```obj``` で指定されたディレクトリが設定されます。


```Makefile:$(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 の読み込みが行われます。

```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``` に設定されます。

```Makefile:$(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``` で示されたファイルたちが指定されます。

```Makefile:$(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``` だった場合のビルドルールは次のようになっています。

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

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

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

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

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

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

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


参考
------------------------------------------------------------------------------------

 * [「Linux デバイスドライバ 第二版」2002/5/30 Alessandro Rubini (著), Jonathan Corbet (著), & 4 その他](https://www.amazon.co.jp/Linux%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90-%E7%AC%AC2%E7%89%88-Alessandro-Rubini/dp/4873110815)
 * [「Linux デバイスドライバプログラミング」2008/6/28 平田 豊 (著)](https://www.amazon.co.jp/Linux%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E5%B9%B3%E7%94%B0-%E8%B1%8A/dp/4797346426)
 * [「Linux カーネルモジュールのコンパイル」 @Qiita](https://qiita.com/eggman/items/f5da299037b29478dc41)
 * [「Linux カーネルモジュールの Makefile をカーネルツリー内外兼用にする」 @Qiita](https://qiita.com/ikwzm/items/3fdeb8e347e1ceb1f652)