GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Erkundung des Linux-Kernels:Die Geheimnisse von Kconfig/kbuild

Das Linux-Kernel-Config/Build-System, auch bekannt als Kconfig/kbuild, gibt es schon seit langem, seit der Linux-Kernel-Code auf Git migriert wurde. Als unterstützende Infrastruktur steht sie jedoch selten im Rampenlicht; sogar Kernel-Entwickler, die es in ihrer täglichen Arbeit verwenden, denken nie wirklich darüber nach.

Um zu untersuchen, wie der Linux-Kernel kompiliert wird, taucht dieser Artikel in den internen Prozess von Kconfig/kbuild ein, erklärt, wie die .config-Datei und die vmlinux/bzImage-Dateien erstellt werden, und stellt einen intelligenten Trick für die Abhängigkeitsverfolgung vor.

Kconfig

Der erste Schritt beim Erstellen eines Kernels ist immer die Konfiguration. Kconfig hilft dabei, den Linux-Kernel hochgradig modular und anpassbar zu machen. Kconfig bietet dem Benutzer viele Konfigurationsziele:

config Aktuelle Konfiguration mit einem zeilenorientierten Programm aktualisieren
nconfig Aktuelle Konfiguration mit einem ncurses-Menü-basierten Programm aktualisieren
Menükonfiguration Aktuelle Konfiguration mit einem menübasierten Programm aktualisieren
xconfig Aktuelle Konfiguration mit einem Qt-basierten Frontend aktualisieren
gconfig Aktuelle Konfiguration mit einem GTK+-basierten Frontend aktualisieren
alte Konfiguration Aktuelle Konfiguration mit einer bereitgestellten .config-Datei als Basis aktualisieren
localmodconfig Aktuelle Konfiguration aktualisieren, Module werden nicht geladen
localyesconfig Aktuelle Konfiguration aktualisieren und lokale Mods in Core umwandeln
defconfig Neue Konfiguration mit Standard von Arch-supplied defconfig
savedefconfig Aktuelle Konfiguration speichern als ./defconfig (minimale Konfiguration)
allnoconfig Neue Konfiguration, bei der alle Optionen mit 'nein' beantwortet werden
allyesconfig Neue Konfiguration, bei der alle Optionen mit 'Ja' akzeptiert werden
allmodconfig Neue Konfigurationsauswahlmodule, wenn möglich
alldefconfig Neue Konfiguration mit allen Symbolen auf Standard gesetzt
randconfig Neue Konfiguration mit einer zufälligen Antwort auf alle Optionen
listnewconfig Neue Optionen auflisten
olddefconfig Dasselbe wie oldconfig, aber setzt neue Symbole ohne Aufforderung auf ihren Standardwert
kvmconfig Zusätzliche Optionen für die KVM-Gast-Kernel-Unterstützung aktivieren
xenconfig Zusätzliche Optionen für xen dom0 und Gast-Kernel-Unterstützung aktivieren
tinyconfig Konfiguriere den kleinstmöglichen Kernel

Ich denke menuconfig ist das beliebteste dieser Ziele. Die Targets werden von verschiedenen Host-Programmen verarbeitet, die vom Kernel bereitgestellt und während der Kernel-Erstellung erstellt werden. Einige Ziele haben eine GUI (zur Bequemlichkeit des Benutzers), die meisten nicht. Kconfig-bezogene Tools und Quellcode befinden sich hauptsächlich unter scripts/kconfig/ in der Kernelquelle. Wie wir in scripts/kconfig/Makefile sehen können , gibt es mehrere Host-Programme, einschließlich conf , mconf und nconf . Außer conf , jeder von ihnen ist für eines der GUI-basierten Konfigurationsziele verantwortlich, also conf befasst sich mit den meisten von ihnen.

Logischerweise besteht die Infrastruktur von Kconfig aus zwei Teilen:Einer implementiert eine neue Sprache, um die Konfigurationselemente zu definieren (siehe die Kconfig-Dateien unter den Kernel-Quellen), und der andere parst die Kconfig-Sprache und befasst sich mit Konfigurationsaktionen.

Die meisten Konfigurationsziele haben ungefähr denselben internen Prozess (siehe unten):

Das Linux-Terminal

  • Die 7 besten Terminalemulatoren für Linux
  • 10 Befehlszeilentools für die Datenanalyse unter Linux
  • Jetzt herunterladen:SSH-Spickzettel
  • Spickzettel für fortgeschrittene Linux-Befehle
  • Linux-Befehlszeilen-Tutorials

Beachten Sie, dass alle Konfigurationselemente einen Standardwert haben.

Der erste Schritt liest die Kconfig-Datei unter Quellstamm, um eine Anfangskonfigurationsdatenbank zu erstellen; dann aktualisiert es die anfängliche Datenbank, indem es eine vorhandene Konfigurationsdatei gemäß dieser Priorität liest:

.config

/lib/modules/$(shell,uname -r)/.config

/etc/kernel-config

/boot /config-$(shell,uname -r)

ARCH_DEFCONFIG

arch/$(ARCH)/defconfig

Wenn Sie eine GUI-basierte Konfiguration über menuconfig durchführen oder befehlszeilenbasierte Konfiguration über oldconfig , wird die Datenbank gemäß Ihrer Anpassung aktualisiert. Schließlich wird die Konfigurationsdatenbank in die .config-Datei ausgegeben.

Aber die .config-Datei ist nicht das letzte Futter für die Kernel-Erstellung; Aus diesem Grund ist die syncconfig Ziel existiert. syncconfig war früher ein Konfigurationsziel namens silentoldconfig , aber es macht nicht das, was der alte Name sagt, also wurde es umbenannt. Da es für den internen Gebrauch bestimmt ist (nicht für Benutzer), wurde es aus der Liste gestrichen.

Hier ist eine Illustration dessen, was syncconfig tut:

syncconfig nimmt .config als Eingabe und gibt viele andere Dateien aus, die in drei Kategorien fallen:

  • auto.conf &tristate.conf werden für die Makefile-Textverarbeitung verwendet. Beispielsweise können Sie Anweisungen wie diese im Makefile einer Komponente sehen: 
    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
  • autoconf.h wird in Quelldateien der Sprache C verwendet.
  • Leere Header-Dateien unter include/config/ werden für das Tracking der Konfigurationsabhängigkeit während kbuild verwendet, was unten erklärt wird.

Nach der Konfiguration wissen wir, welche Dateien und Codeteile nicht kompiliert sind.

kbuild

Komponentenweises Erstellen, genannt rekursives Make , ist ein gängiger Weg für GNU make um ein großes Projekt zu leiten. Kbuild ist ein gutes Beispiel für rekursives Make. Durch die Aufteilung der Quelldateien in verschiedene Module/Komponenten wird jede Komponente von ihrem eigenen Makefile verwaltet. Wenn Sie mit dem Erstellen beginnen, ruft ein Top-Makefile das Makefile jeder Komponente in der richtigen Reihenfolge auf, erstellt die Komponenten und sammelt sie in der endgültigen Ausführung.

Kbuild bezieht sich auf verschiedene Arten von Makefiles:

  • Makefile ist das oberste Makefile, das sich im Quellverzeichnis befindet.
  • .config ist die Kernel-Konfigurationsdatei.
  • arch/$(ARCH)/Makefile ist das Arch-Makefile, das die Ergänzung zum Top-Makefile ist.
  • scripts/Makefile.* beschreibt allgemeine Regeln für alle kbuild-Makefiles.
  • Schließlich gibt es etwa 500 kbuild-Makefiles .

Das oberste Makefile enthält das Arch-Makefile, liest die .config-Datei, steigt in Unterverzeichnisse ab und ruft make auf auf dem Makefile jeder Komponente mit Hilfe von Routinen, die in scripts/Makefile.* definiert sind , baut jedes Zwischenobjekt auf und verknüpft alle Zwischenobjekte mit vmlinux. Das Kernel-Dokument Documentation/kbuild/makefiles.txt beschreibt alle Aspekte dieser Makefiles.

Sehen wir uns als Beispiel an, wie vmlinux auf x86-64 erstellt wird:

Alle .o Dateien, die in vmlinux aufgenommen werden, gehen zuerst in ihre eigene built-in.a , was über Variablen KBUILD_VMLINUX_INIT angegeben wird , KBUILD_VMLINUX_MAIN , KBUILD_VMLINUX_LIBS , werden dann in der vmlinux-Datei gesammelt.

Sehen Sie sich mit Hilfe des vereinfachten Makefile-Codes an, wie rekursives make im Linux-Kernel implementiert ist:

# 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

Das rekursive Make-Rezept wird erweitert, zum Beispiel:

make -f scripts/Makefile.build obj=init need-builtin=1

Das bedeutet machen geht in scripts/Makefile.build um die Arbeit zum Erstellen jedes built-in.a fortzusetzen . Mit Hilfe von scripts/link-vmlinux.sh , die vmlinux-Datei befindet sich schließlich im Quellverzeichnis.

Vmlinux vs. bzImage verstehen

Vielen Linux-Kernel-Entwicklern ist die Beziehung zwischen vmlinux und bzImage möglicherweise nicht klar. Hier ist zum Beispiel ihre Beziehung in x86-64:

Das Quell-Root-vmlinux wird entfernt, komprimiert und in piggy.S abgelegt , dann mit anderen Peer-Objekten in arch/x86/boot/compressed/vmlinux verknüpft . In der Zwischenzeit wird unter arch/x86/boot eine Datei namens setup.bin erstellt . Abhängig von der Konfiguration von CONFIG_X86_NEED_RELOCS kann es eine optionale dritte Datei mit Umzugsinformationen geben .

Ein Host-Programm namens build , bereitgestellt vom Kernel, baut diese zwei (oder drei) Teile in die endgültige bzImage-Datei ein.

Abhängigkeitsverfolgung

Kbuild verfolgt drei Arten von Abhängigkeiten:

  1. Alle erforderlichen Dateien (sowohl *.c und *.h )
  2. CONFIG_ Optionen, die in allen vorausgesetzten Dateien verwendet werden
  3. Befehlszeilenabhängigkeiten, die zum Kompilieren des Ziels verwendet werden

Das erste ist leicht zu verstehen, aber was ist mit dem zweiten und dritten? Kernel-Entwickler sehen oft Codeteile wie diese:

#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif

Wenn CONFIG_SMP Änderungen, sollte dieser Codeabschnitt neu kompiliert werden. Die Befehlszeile zum Kompilieren einer Quelldatei ist ebenfalls wichtig, da unterschiedliche Befehlszeilen zu unterschiedlichen Objektdateien führen können.

Wenn eine .c file verwendet eine Header-Datei über ein #include Direktive müssen Sie eine Regel wie diese schreiben:

main.o: defs.h
	recipe...

Wenn Sie ein großes Projekt verwalten, benötigen Sie viele dieser Regeln. sie alle zu schreiben, wäre mühsam und langweilig. Glücklicherweise können die meisten modernen C-Compiler diese Regeln für Sie schreiben, indem sie sich das #include ansehen Zeilen in der Quelldatei. Für die GNU Compiler Collection (GCC) muss lediglich ein Befehlszeilenparameter hinzugefügt werden:-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)

Dies würde ein .d erzeugen Datei mit Inhalt wie:

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 \
 ...

Dann das Wirtsprogramm fixdep kümmert sich um die anderen beiden Abhängigkeiten, indem es das depfile nimmt und Befehlszeile als Eingabe, dann Ausgabe einer ..cmd Datei in Makefile-Syntax, die die Befehlszeile und alle Voraussetzungen (einschließlich der Konfiguration) für ein Ziel aufzeichnet. Es sieht so aus:

# 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 \
  ...

Eine ..cmd -Datei wird während des rekursiven Erstellens eingefügt, liefert alle Abhängigkeitsinformationen und hilft bei der Entscheidung, ob ein Ziel neu erstellt werden soll oder nicht.

Das Geheimnis dahinter ist das fixdep parst das depfile (.d Datei), parsen Sie dann alle darin enthaltenen Abhängigkeitsdateien, durchsuchen Sie den Text nach allen CONFIG_ Zeichenfolgen, konvertieren Sie sie in die entsprechende leere Header-Datei und fügen Sie sie den Voraussetzungen des Ziels hinzu. Jedes Mal, wenn sich die Konfiguration ändert, wird auch die entsprechende leere Header-Datei aktualisiert, sodass kbuild diese Änderung erkennen und das davon abhängige Ziel neu erstellen kann. Da auch die Befehlszeile aufgezeichnet wird, ist es einfach, die letzten und aktuellen Kompilierungsparameter zu vergleichen.

Vorausblick

Kconfig/kbuild blieb lange Zeit gleich, bis Anfang 2017 der neue Betreuer Masahiro Yamada hinzukam, und jetzt wird kbuild wieder aktiv weiterentwickelt. Seien Sie nicht überrascht, wenn Sie bald etwas anderes als in diesem Artikel sehen.


Linux
  1. Linux – Einen benutzerdefinierten Linux-Kernel konfigurieren, kompilieren und installieren?

  2. Linux – Wie kann man feststellen, welches Modul den Kernel verschmutzt?

  3. Linux – Teilnahme an der Kernel-Mailingliste?

  4. Linux – Warum kann der Kernel Init nicht ausführen?

  5. Linux – Proprietäre oder geschlossene Teile des Kernels?

Wie der Linux-Kernel mit Interrupts umgeht

Wie man einen Linux-Kernel im 21. Jahrhundert kompiliert

Kontinuierliche Integrationstests für den Linux-Kernel

So überprüfen Sie die Kernel-Version in Linux

Untersuchen des Linux /proc-Dateisystems

Linux – Dump Page Table Layout (Kernel-Konfiguration)?