Linuxカーネルのconfig/buildシステムは、Kconfig / kbuildとも呼ばれ、LinuxカーネルコードがGitに移行されて以来、長い間使用されてきました。ただし、サポートインフラストラクチャとして、注目されることはめったにありません。日常業務でそれを使用するカーネル開発者でさえ、実際にそれについて考えることはありません。
Linuxカーネルがどのようにコンパイルされるかを調べるために、この記事ではKconfig / kbuildの内部プロセスについて詳しく説明し、.configファイルとvmlinux / bzImageファイルがどのように生成されるかを説明し、依存関係を追跡するための賢いトリックを紹介します。
Kconfig
カーネルを構築する最初のステップは、常に構成です。 Kconfigは、Linuxカーネルを高度にモジュール化してカスタマイズできるようにするのに役立ちます。 Kconfigは、ユーザーに多くの構成ターゲットを提供します:
config | 行指向プログラムを利用して現在の構成を更新する |
nconfig | ncursesメニューベースのプログラムを利用して現在の設定を更新する |
menuconfig | メニューベースのプログラムを利用して現在の構成を更新する |
xconfig | Qtベースのフロントエンドを利用して現在の構成を更新する |
gconfig | GTK+ベースのフロントエンドを利用して現在の構成を更新する |
oldconfig | 提供された.configをベースとして使用して現在の構成を更新します |
localmodconfig | ロードされていないモジュールを無効にする現在の構成を更新します |
localyesconfig | ローカルmodをコアに変換する現在の構成を更新 |
defconfig | Archが提供するdefconfigからのデフォルトの新しい構成 |
savedefconfig | 現在の構成を./defconfig(最小構成)として保存します |
allnoconfig | すべてのオプションが「いいえ」で回答される新しい構成 |
allyesconfig | すべてのオプションが「yes」で受け入れられる新しい構成 |
allmodconfig | 可能な場合はモジュールを選択する新しい構成 |
alldefconfig | すべてのシンボルがデフォルトに設定された新しい構成 |
randconfig | すべてのオプションにランダムに回答する新しい構成 |
listnewconfig | 新しいオプションを一覧表示する |
olddefconfig | oldconfigと同じですが、プロンプトを表示せずに新しいシンボルをデフォルト値に設定します |
kvmconfig | KVMゲストカーネルサポートの追加オプションを有効にする |
xenconfig | xendom0およびゲストカーネルサポートの追加オプションを有効にする |
tinyconfig | 可能な限り小さなカーネルを構成する |
menuconfigだと思います これらのターゲットの中で最も人気があります。ターゲットは、カーネルによって提供され、カーネルの構築中に構築されるさまざまなホストプログラムによって処理されます。一部のターゲットには(ユーザーの便宜のために)GUIがありますが、ほとんどのターゲットにはありません。 Kconfig関連のツールとソースコードは、主に scripts / kconfig /の下にあります。 カーネルソースで。 scripts / kconfig / Makefileからわかるように 、 confを含むいくつかのホストプログラムがあります 、 mconf 、および nconf 。 confを除く 、それぞれがGUIベースの構成ターゲットの1つを担当するため、 conf それらのほとんどを扱います。
論理的には、Kconfigのインフラストラクチャには2つの部分があります。1つは構成アイテムを定義するための新しい言語を実装し(カーネルソースの下のKconfigファイルを参照)、もう1つはKconfig言語を解析して構成アクションを処理します。
ほとんどの構成ターゲットには、ほぼ同じ内部プロセスがあります(以下を参照)。
Linuxターミナル
- Linux用の上位7つのターミナルエミュレータ
- Linuxでのデータ分析のための10個のコマンドラインツール
- 今すぐダウンロード:SSHチートシート
- 高度なLinuxコマンドのチートシート
- Linuxコマンドラインチュートリアル
すべての構成アイテムにはデフォルト値があることに注意してください。
最初のステップでは、ソースルートの下にあるKconfigファイルを読み取り、初期構成データベースを構築します。次に、この優先度に従って既存の構成ファイルを読み取ることにより、初期データベースを更新します。
.config
/ lib / modules / $(shell、uname -r)/。config
/ etc / kernel-config
/ boot / config-$(shell、uname -r)
ARCH_DEFCONFIG
arch / $(ARCH)/ defconfig
menuconfigを介してGUIベースの構成を行っている場合 またはoldconfigによるコマンドラインベースの構成 、データベースはカスタマイズに応じて更新されます。最後に、構成データベースが.configファイルにダンプされます。
ただし、.configファイルはカーネル構築の最終的な飼料ではありません。これがsyncconfigの理由です ターゲットが存在します。 syncconfig 以前はsilentoldconfigと呼ばれる構成ターゲットでした 、しかし、それは古い名前が言うことをしません、それでそれは改名されました。また、(ユーザー向けではなく)内部使用のため、リストから削除されました。
これは、 syncconfigの図です。 する:
syncconfig .configを入力として受け取り、他の多くのファイルを出力します。これらのファイルは3つのカテゴリに分類されます。
- auto.conf&tristate.conf makefileテキスト処理に使用されます。たとえば、コンポーネントのmakefileに次のようなステートメントが表示される場合があります。
obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
- autoconf.h C言語のソースファイルで使用されます。
- include / config /の下の空のヘッダーファイル kbuild中の構成依存関係の追跡に使用されます。これについては、以下で説明します。
構成後、コンパイルされていないファイルとコードがわかります。
kbuild
recursive makeと呼ばれるコンポーネントごとの構築 、はGNU make
の一般的な方法です 大規模なプロジェクトを管理します。 Kbuildは再帰的なmakeの良い例です。ソースファイルを異なるモジュール/コンポーネントに分割することにより、各コンポーネントは独自のmakefileによって管理されます。ビルドを開始すると、最上位のmakefileが各コンポーネントのmakefileを適切な順序で呼び出し、コンポーネントをビルドして、最終的なエグゼクティブに収集します。
Kbuildはさまざまな種類のmakefileを参照します:
- Makefile ソースルートにある最上位のmakefileです。
- .config はカーネル構成ファイルです。
- arch / $(ARCH)/ Makefile は、最上位のmakefileの補足であるarchmakefileです。
- scripts/Makefile。* すべてのkbuildmakefileに共通のルールについて説明します。
- 最後に、約500のkbuildメイクファイルがあります 。
一番上のmakefileには、arch makefileが含まれ、.configファイルを読み取り、サブディレクトリに下降し、 makeを呼び出します。 scripts/Makefile。*で定義されたルーチンを使用して、各コンポーネントのmakefileで 、各中間オブジェクトを構築し、すべての中間オブジェクトをvmlinuxにリンクします。カーネルドキュメントDocumentation/kbuild / makefiles.txtには、これらのmakefileのすべての側面が記載されています。
例として、vmlinuxがx86-64でどのように生成されるかを見てみましょう:
すべての.o vmlinuxに入るファイルは、最初に独自の組み込み.aに入る 、変数 KBUILD_VMLINUX_INITで示されます 、 KBUILD_VMLINUX_MAIN 、 KBUILD_VMLINUX_LIBS 、その後、vmlinuxファイルに収集されます。
簡略化されたmakefileコードを使用して、Linuxカーネルに再帰makeがどのように実装されているかを見てみましょう。
# In top Makefile
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps)
+$(call if_changed,link-vmlinux)
# Variable assignments
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
core-y := usr/
virt-y := virt/
# Transform to corresponding built-in.a
init-y := $(patsubst %/, %/built-in.a, $(init-y))
core-y := $(patsubst %/, %/built-in.a, $(core-y))
drivers-y := $(patsubst %/, %/built-in.a, $(drivers-y))
net-y := $(patsubst %/, %/built-in.a, $(net-y))
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
virt-y := $(patsubst %/, %/built-in.a, $(virt-y))
# Setup the dependency. vmlinux-deps are all intermediate objects, vmlinux-dirs
# are phony targets, so every time comes to this rule, the recipe of vmlinux-dirs
# will be executed. Refer "4.6 Phony Targets" of `info make`
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
# Variable vmlinux-dirs is the directory part of each built-in.a
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
# The entry of recursive make
$(vmlinux-dirs):
$(Q)$(MAKE) $(build)=$@ need-builtin=1
再帰的なmakeレシピが拡張されます。例:
make -f scripts/Makefile.build obj=init need-builtin=1
これは作るを意味します scripts / Makefile.buildに移動します 各ビルトイン.aを構築する作業を継続する 。 scripts / link-vmlinux.shの助けを借りて 、vmlinuxファイルは最終的にソースルートの下にあります。
vmlinuxとbzImageを理解する
多くのLinuxカーネル開発者は、vmlinuxとbzImageの関係について明確でない場合があります。たとえば、x86-64でのそれらの関係は次のとおりです。
ソースルートのvmlinuxは削除され、圧縮され、 piggy.Sに配置されます。 、次に他のピアオブジェクトとリンクして arch / x86 / boot / compressed / vmlinux 。一方、setup.binというファイルは、 arch / x86 / bootの下に作成されます。 。 CONFIG_X86_NEED_RELOCS の構成によっては、再配置情報を含むオプションの3番目のファイルが存在する場合があります。 。
ビルドと呼ばれるホストプログラム 、カーネルによって提供され、これらの2つ(または3つ)の部分を最終的なbzImageファイルに構築します。
Kbuildは3種類の依存関係を追跡します:
- すべての前提条件ファイル(両方* .c および*.h )
- CONFIG _ すべての前提条件ファイルで使用されるオプション
- ターゲットのコンパイルに使用されるコマンドラインの依存関係
最初のものは理解しやすいですが、2番目と3番目はどうですか?カーネル開発者は、次のようなコードをよく目にします。
#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif
CONFIG_SMPの場合 変更があった場合は、このコードを再コンパイルする必要があります。コマンドラインが異なるとオブジェクトファイルも異なる可能性があるため、ソースファイルをコンパイルするためのコマンドラインも重要です。
.cの場合 fileは、 #includeを介してヘッダーファイルを使用します ディレクティブの場合、次のようなルールを作成する必要があります:
main.o: defs.h
recipe...
大規模なプロジェクトを管理する場合、これらの種類のルールが多数必要になります。それらをすべて書くのは退屈で退屈でしょう。幸い、最近のほとんどのCコンパイラは、 #include を調べることで、これらのルールを作成できます。 ソースファイルの行。 GNUコンパイラコレクション(GCC)の場合、コマンドラインパラメータを追加するだけです: -MD depfile
# In scripts/Makefile.lib
c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
-include $(srctree)/include/linux/compiler_types.h \
$(__c_flags) $(modkern_cflags) \
$(basename_flags) $(modname_flags)
これにより、 .dが生成されます 次のようなコンテンツを含むファイル:
init_task.o: init/init_task.c include/linux/kconfig.h \
include/generated/autoconf.h include/linux/init_task.h \
include/linux/rcupdate.h include/linux/types.h \
...
次に、ホストプログラム fixdep depfile を使用して、他の2つの依存関係を処理します コマンドラインを入力として、。
# The command line used to compile the target
cmd_init/init_task.o := gcc -Wp,-MD,init/.init_task.o.d -nostdinc ...
...
# The dependency files
deps_init/init_task.o := \
$(wildcard include/config/posix/timers.h) \
$(wildcard include/config/arch/task/struct/on/stack.h) \
$(wildcard include/config/thread/info/in/task.h) \
...
include/uapi/linux/types.h \
arch/x86/include/uapi/asm/types.h \
include/uapi/asm-generic/types.h \
...
。
この背後にある秘密は、 fixdep depfileを解析します ( .d file)、次に、内部のすべての依存関係ファイルを解析し、テキストですべての CONFIG _を検索します。 文字列を、対応する空のヘッダーファイルに変換し、ターゲットの前提条件に追加します。構成が変更されるたびに、対応する空のヘッダーファイルも更新されるため、kbuildはその変更を検出し、それに依存するターゲットを再構築できます。コマンドラインも記録されるため、最後のコンパイルパラメータと現在のコンパイルパラメータを簡単に比較できます。
Kconfig / kbuildは、新しいメンテナである山田正博が2017年の初めに参加するまで長い間同じままでしたが、現在、kbuildは再び活発に開発されています。この記事の内容とは異なるものがすぐに表示されても驚かないでください。