Den Linux-Kernel erkunden: Die Geheimnisse von Kconfig

Den Linux-Kernel erkunden: Die Geheimnisse von Kconfig

Erhalten Sie ein umfassendes Verständnis der Funktionsweise des Linux-Konfigurations-/Build-Systems.

Das Linux-Kernel-Konfigurations-/Build-System (auch bekannt als Kconfig/kBuild) gibt es schon seit langer Zeit, seitdem der Linux-Kernel-Code auf Git migriert wurde. Als unterstützende Infrastruktur wurde ihr jedoch wenig Aufmerksamkeit geschenkt; selbst Kernel-Entwickler, die sie in ihrer täglichen Arbeit verwenden, denken nie wirklich darüber nach.

Um zu untersuchen, wie der Linux-Kernel kompiliert wird, geht dieser Artikel auf die internen Vorgänge von Kconfig/kBuild ein, erklärt, wie .config-Dateien und vmlinux/bzImage-Dateien generiert werden, und stellt einen cleveren Trick zur Abhängigkeitsverfolgung vor.

Kconfig

Der erste Schritt beim Erstellen eines Kernels ist immer die Konfiguration. Kconfig trägt dazu bei, den Linux-Kernel hochgradig modular und anpassbar zu gestalten. Kconfig stellt dem Benutzer eine Reihe von Konfigurationszielen zur Verfügung:

Konfiguration Aktualisieren Sie die aktuelle Konfiguration mit einem zeilenorientierten Programm
nconfig Aktualisieren Sie die aktuelle Konfiguration mit einem menübasierten Ncurses-Programm
Menükonfiguration Aktualisieren Sie die aktuelle Konfiguration über ein menübasiertes Verfahren
xconfig Aktualisieren Sie die aktuelle Konfiguration mithilfe eines Qt-basierten Frontends
gconfig Aktualisieren Sie die aktuelle Konfiguration mithilfe des GTK+-basierten Frontends
alteKonfiguration Aktualisiert die aktuelle Konfiguration auf der Grundlage der bereitgestellten .config-Datei
lokalemodconfig Aktuelle Konfiguration aktualisieren deaktivierte Module, die nicht geladen werden
lokaleyesconfig Aktualisiert die aktuelle Konfiguration und konvertiert lokale Mods in den Kern
defconfig Holen Sie sich eine neue Konfiguration mit Standardeinstellungen aus Defconfig, bereitgestellt von Arch
Savedefconfig Speichern Sie die aktuelle Konfiguration als ./defconfig (minimale Konfiguration).
allenichtkonfigurieren Neue Konfiguration mit „Nein“-Antwort auf alle Optionen
Verbündeter Neue Konfiguration, bei der alle Optionen mit „ja“ übernommen werden
allesmodconfig Wählen Sie nach Möglichkeit neue Konfigurationsmodule aus
alledefconfig Eine neue Konfiguration, bei der alle Symbole auf die Standardwerte eingestellt sind
randconfig Neue Konfiguration mit zufälligen Antworten auf alle Optionen
ListeNeueKonfiguration Neue Optionen auflisten
altedefconfig Wie oldconfig, setzt aber neue Symbole als Standard ohne Nachfrage
kvmconfig Aktivieren Sie zusätzliche Optionen für die KVM-Client-Kernel-Unterstützung
xenconfig Aktivieren Sie Xen Dom0 und andere vom Gastkernel unterstützte Optionen
winzigeKonfiguration Konfigurieren Sie den kleinstmöglichen Kernel

Ich glaube, dass Menuconfig das beliebteste dieser Ziele ist. Ziele werden von verschiedenen Hostprogrammen verwaltet, die vom Kernel bereitgestellt und während des Kernel-Build-Prozesses generiert werden. Einige Ziele verfügen über eine GUI (zur Benutzerfreundlichkeit), die meisten jedoch nicht. Die kconfig-bezogenen Tools und der Quellcode befinden sich hauptsächlich in scripts/kconfig/ im Kernel-Quellcode. Wir können mit scripts/kconfig/makefile beginnen, dort gibt es mehrere Hostprogramme, darunter CONF, mconf und nconf. Mit Ausnahme von conf ist jedes von ihnen für eines der GUI-basierten Konfigurationsziele verantwortlich, sodass conf sich um die meisten davon kümmert.

Logischerweise besteht die Kconfig-Infrastruktur aus zwei Teilen: einem, der die neue Sprache zum Definieren von Konfigurationselementen implementiert (siehe die Kconfig-Dateien unter dem Kernel-Quellcode), und einem anderen, der die Kconfig-Sprache analysiert und Konfigurationsvorgänge handhabt.

Der interne Ablauf ist für die meisten Konfigurationsziele ungefähr gleich (wie unten gezeigt):

Beachten Sie, dass alle Konfigurationselemente einen Standardwert haben.

Im ersten Schritt werden die Kconfig-Dateien im Quellstamm gelesen, um die anfängliche Konfigurationsdatenbank zu erstellen. Anschließend werden die vorhandenen Konfigurationsdateien gelesen, um die anfängliche Datenbank entsprechend dieser Priorität zu aktualisieren:

  • .config
  • /lib/Modul/$(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“ oder eine befehlszeilenbasierte Konfiguration über „oldconfig“ durchführen, wird die Datenbank mit Ihren Anpassungen aktualisiert. Speichern Sie abschließend die Konfigurationsdatenbank in einer CONFIG-Datei.

Allerdings sind .config-Dateien nicht das Endprodukt eines Kernel-Builds; aus diesem Grund existiert das Syncconfig-Ziel. syncconfig war früher eine Datei mit dem Namen silentoldconfig, aber sie hatte nicht den Zweck, den der alte Name versprach, deshalb wurde sie umbenannt. Da es außerdem für den internen Gebrauch (und nicht für Benutzer) bestimmt ist, wurde es aus der Liste entfernt.

Hier ist ein Beispiel dafür, was syncconfig macht:

syncconfig verwendet .config als Eingabe und gibt eine Reihe anderer Dateien aus, die in drei Kategorien fallen:

auto.conf und tristate.conf werden zum Generieren von Dateien für die Textverarbeitung verwendet. Beispielsweise könnte in der Makefile einer Komponente eine Anweisung wie diese vorkommen:

obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o

autoconf.h wird in Quelldateien der Sprache C verwendet.

Die leere Header-Datei include/config/ wird zur Verfolgung der Konfigurationsabhängigkeiten während kbuild verwendet, was weiter unten erläutert wird.

Nach der Profilerstellung wissen wir, welche Dateien und Codesegmente nicht kompiliert wurden.

KBuild

Der komponentenweise Aufbau, rekursives Make genannt, ist ein gängiger Ansatz in GNU. Erstellen und verwalten Sie ein großes Projekt. KBuild ist ein gutes Beispiel für rekursives Make. Durch die Aufteilung der Quelldateien in verschiedene Module/Komponenten wird jede Komponente durch ein eigenes Makefile verwaltet. Wenn Sie einen Build starten, ruft das Makefile der obersten Ebene das Makefile jeder Komponente in der richtigen Reihenfolge auf, erstellt die Komponenten und sammelt sie in der endgültigen ausführbaren Datei.

KBuild bezieht sich auf verschiedene Arten von Makefiles:

  • Das Makefile befindet sich ganz oben im Makefile im Quellstamm.
  • .config ist die Kernel-Konfigurationsdatei.
  • ARCH/$(ARCH)/Makefile ist das Arch-Makefile, das eine Ergänzung zum Top-Makefile darstellt.
  • scripts/Makefile* beschreibt allgemeine Regeln für alle kbuild-Makefiles.
  • Am Ende sind es etwa 500 Kbuildmakefiles.

Das oberste Makefile enthält das Archmakefile, das die .config-Datei liest, in das Unterverzeichnis geht, make aufruft und jedes Komponenten-Makefile mit Hilfe der darin definierten Routinen implementiert. scripts/Makefile*, erstellt jedes Zwischenobjekt und verknüpft alle Zwischenobjekte in vmlinux. Die Kerndatei Documentation/kbuild/makefiles.txt beschreibt alle Aspekte dieser Makefiles.

Sehen wir uns beispielsweise an, wie vmlinux auf x86-64 gestartet wird:

(Abbildung basierend auf dem Blog von Richard Y. Steven. Aktualisiert und mit Genehmigung des Autors verwendet.)

Alle .o-Dateien, die in vmlinux gehen, gehen zuerst in ihre eigene integrierte .a-Datei, die durch die Variable dargestellt wird. KBUILD_VMLINUX_INIT, KBUILD_VMLINUX_Main, KBUILD_VMLINUX_LIBS und sammeln Sie sie dann in der vmlinux-Datei.

Sehen wir uns an, wie rekursives Make im Linux-Kernel mithilfe eines vereinfachten Makefile-Codes implementiert wird:

# Im obersten Makefile
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps)
        +$(Aufruf if_changed,link-vmlinux)
# Variablenzuweisungen
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)
exportiere KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
exportiere KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
exportiere KBUILD_VMLINUX_LIBS := $(libs-y1)
exportiere KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
init-y := init/
drivers-y := Treiber/ Sound/ Firmware/
Netz-y := Netz/
libs-y := Bibliothek/
Kern-y := usr/
virt-y := virt/
# In entsprechendes Built-in umwandeln.a
init-y := $(patsubst %/, %/built-in.a, $(init-y))
Kern-y := $(patsubst %/, %/built-in.a, $(Kern-y))
Treiber-y := $(patsubst %/, %/built-in.a, $(Treiber-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))
# Richten Sie die Abhängigkeit ein. vmlinux-deps sind alles Zwischenobjekte, vmlinux-dirs
# sind falsche Ziele, also kommt jedes Mal diese Regel, das Rezept von vmlinux-dirs
# wird ausgeführt. Siehe „4.6 Falsche Ziele“ von „info make“
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
# Die Variable vmlinux-dirs ist der Verzeichnisteil jedes integrierten.a
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
           $(Kern-y) $(Kern-m) $(Treiber-y) $(Treiber-m) \
           $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
# Der Eintrag von rekursivem make
$(vmlinux-dirs):
        $(Q)$(MAKE) $(build)=$@ need-builtin=1

Rekursive Rezepterweiterung, zum Beispiel:

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

Dies bedeutet, dass Make in „scripts/Makefile.build“ geht und mit dem Erstellen aller integrierten Elemente fortfährt. Mithilfe von scripts/link-vmlinux.sh werden die vmlinux-Dateien schließlich im Quellenstammverzeichnis lokalisiert.

vmlinux und bzImage verstehen

Vielen Linux-Kernel-Entwicklern ist die Beziehung zwischen vmlinux und bzImage möglicherweise nicht klar. So sehen die Zusammenhänge beispielsweise in x86-64 aus:

Die Quellstammdatei vmlinux wird entfernt, komprimiert, in piggy.S eingefügt und dann werden andere Peer-Objekte in arch/x86/boot/compressed/vmlinux verknüpft. Gleichzeitig wird eine Datei mit dem Namen setup.bin in arch/x86/boot generiert. Abhängig von config_x86_RELOCS kann es optional eine dritte Datei mit Umzugsinformationen geben.

Der Kernel stellt einen Befehl namens „Build“ bereit, der diese zwei (oder drei) Teile in die endgültige bzImage-Datei einbaut.

Abhängigkeitsverfolgung

KBuild verfolgt drei Arten von Abhängigkeiten:

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

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

#ifdef CONFIG_SMP
__boot_cpu_id = CPU;
#endif

Wenn CONFIG_SMP geändert wird, sollte dieser Code neu kompiliert werden. Auch die Befehlszeile, die Sie zum Kompilieren Ihrer Quelldateien verwenden, ist wichtig, da unterschiedliche Befehlszeilen zu unterschiedlichen Objektdateien führen können.

Wenn die .C-Datei über die Direktive #include eingebunden wird, müssen Sie eine Regel wie diese schreiben:

main.o: defs.h
Rezept...

Beim Management eines großen Projekts benötigen Sie viele dieser Regeln. Sie alle können sehr mühsam sein. Glücklicherweise können die meisten modernen C-Compiler dies erkennen, indem sie sich die #include-Zeilen in Ihren Quelldateien ansehen. Für die GNU Compiler Collection (GCC) fügen Sie einfach ein Befehlszeilenargument hinzu: -MD depfile

# In Skripten/Makefile.lib
c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
         -include $(srctree)/include/linux/compiler_types.h \
         $(__c_flags) $(modkern_cflags) \
         $(Basisname_flags) $(Modname_flags)

Dadurch wird eine D-Datei mit folgendem Inhalt generiert:

init_task.o: init/init_task.c include/linux/kconfig.h \
include/generiert/autoconf.h include/linux/init_task.h \
include/linux/rcupdate.h include/linux/types.h \
...

Das Hostprogramm fixdep kümmert sich dann um die anderen beiden Abhängigkeiten, indem es sie abruft. Die Depfile verwendet eine Befehlszeile als Eingabe und gibt dann eine CMD-Datei in Makefile-Syntax aus, die die Befehlszeile des Ziels und alle Voraussetzungen (einschließlich der Konfiguration) aufzeichnet. Es sieht so aus:

# Die zum Kompilieren des Ziels verwendete Befehlszeile
cmd_init/init_task.o := gcc -Wp,-MD,init/.init_task.od -nostdinc …
...
# Die Abhängigkeitsdateien
deps_init/init_task.o := \
$(Platzhalter include/config/posix/timers.h) \
$(Platzhalter include/config/arch/task/struct/on/stack.h) \
$(Platzhalter 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 \
...

In den rekursiven Build-Prozess wird eine CMD-Datei eingebunden, die alle Abhängigkeitsinformationen bereitstellt und bei der Entscheidung hilft, ob das Ziel neu erstellt werden soll.

Das Geheimnis dahinter ist, dass Fixdep die Depfile (.d-Datei) analysiert, dann alle darin enthaltenen Abhängigkeitsdateien analysiert, nach dem Text aller Config_Strings sucht, sie in entsprechende leere Header-Dateien konvertiert und sie zu den Voraussetzungen des Ziels hinzufügt. Bei jeder Änderung der Konfiguration wird auch die entsprechende leere Header-Datei aktualisiert, sodass kbuild diese Änderung erkennen und die davon abhängigen Ziele neu erstellen kann. Da auch die Befehlszeile aufgezeichnet wird, ist es einfach, die letzten Kompilierungsparameter mit den aktuellen Kompilierungsparametern zu vergleichen.

Ein Blick in die Zukunft

Kconfig/kbuild blieb lange Zeit unverändert, bis Anfang 2017 ein neuer Betreuer, Masahiro Yamada, dazukam, und jetzt wird KBuild wieder aktiv weiterentwickelt. Seien Sie nicht überrascht, wenn Sie schnell etwas anderes sehen als in diesem Artikel.

Zusammenfassen

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Lernwert für Ihr Studium oder Ihre Arbeit hat. Vielen Dank für Ihre Unterstützung von 123WORDPRESS.COM. Wenn Sie mehr darüber erfahren möchten, schauen Sie sich bitte die folgenden Links an

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung der Speicherverwaltungsarchitektur des Linux-Kernels
  • Implementierung und Analyse des Linux-Kernel- und Benutzerbereichs
  • Detaillierte Erläuterung des Auslöse- und Ausführungszeitpunkts der Linux-Kernel-Prozessplanungsfunktion schedule()
  • Linux verwendet Sysctl-Befehle zum Anpassen von Kernel-Parametern
  • Methode zur Anpassung der Linux-Kernel-Parameter
  • Detaillierte Erklärung der Bootparameter des Linux-Kernels
  • Ein kurzer Vortrag über Linux-Kernel-Timer
  • Detaillierte Erläuterung der Beispiele für Kernel-Linked-List in Linux
  • Eine kurze Erläuterung der Bedeutung des Festlegens von Kernelparametern unter Linux bei der Installation von ORACLE
  • Gerätetreiber des Linux-Kernels – Zusammenfassung der grundlegenden Hinweise zum Linux-Kernel

<<:  Lassen Sie uns das Ereignisobjekt in js genauer verstehen

>>:  Detaillierte Erläuterung von acht Möglichkeiten zur Optimierung der MySQL-Datenbank (klassische Pflichtlektüre)

Artikel empfehlen

js, um den Popup-Effekt zu erzielen

In diesem Artikelbeispiel wird der spezifische Co...

Implementierung der Formularübermittlung in HTML

Formularübermittlungscode 1. Quellcode-Analyse &l...

Analysieren Sie den Unterschied zwischen berechnet und beobachtet in Vue

Inhaltsverzeichnis 1. Einführung in die Computert...

So verwenden Sie jconsole zum Überwachen von Remote-Tomcat-Diensten

Was ist JConsole JConsole wurde in Java 5 eingefü...

Layout im Vue.js-Stil Allgemeine Fähigkeiten zur Flutter-Geschäftsentwicklung

Korrespondenz zwischen Flutter und CSS im Shadow-...

Sollte ich beim Erstellen einer Website die Kodierung UTF-8 oder GB2312 verwenden?

Beim Öffnen ausländischer Websites werden häufig ...

So implementieren Sie einen variablen Ausdrucksselektor in Vue

Inhaltsverzeichnis Definieren der HTML-Struktur E...

Integrationspraxis des Vue+Element-Hintergrundverwaltungsframeworks

Inhaltsverzeichnis Vue+ElementUI-Hintergrundverwa...

MySQL SHOW PROCESSLIST unterstützt den gesamten Prozess der Fehlerbehebung

1. SHOW PROCESSLIST-Befehl SHOW PROCESSLIST zeigt...

Abfrageprozess und Optimierungsmethode der (JOIN/ORDER BY)-Anweisung in MySQL

Die EXPLAIN-Anweisung wird im MySQL-Abfrageanweis...