GNU/Linux >> Tutoriels Linux >  >> Linux

Explorer le noyau Linux :les secrets de Kconfig/kbuild

Le système de configuration/construction du noyau Linux, également connu sous le nom de Kconfig/kbuild, existe depuis longtemps, depuis que le code du noyau Linux a migré vers Git. En tant qu'infrastructure de soutien, cependant, elle est rarement sous les feux de la rampe; même les développeurs du noyau qui l'utilisent dans leur travail quotidien n'y pensent jamais vraiment.

Pour explorer la façon dont le noyau Linux est compilé, cet article plongera dans le processus interne Kconfig/kbuild, expliquera comment le fichier .config et les fichiers vmlinux/bzImage sont produits, et présentera une astuce intelligente pour le suivi des dépendances.

Kconfig

La première étape de la construction d'un noyau est toujours la configuration. Kconfig contribue à rendre le noyau Linux hautement modulaire et personnalisable. Kconfig propose à l'utilisateur de nombreuses cibles de configuration :

config Mettre à jour la configuration actuelle à l'aide d'un programme orienté ligne
nconfig Mettre à jour la configuration actuelle à l'aide d'un programme basé sur un menu ncurses
menuconfig Mettre à jour la configuration actuelle à l'aide d'un programme basé sur des menus
xconfig Mettre à jour la configuration actuelle en utilisant une interface basée sur Qt
gconfig Mettre à jour la configuration actuelle en utilisant une interface basée sur GTK+
ancienneconfig Mettre à jour la configuration actuelle en utilisant un .config fourni comme base
localmodconfig Mettre à jour la configuration actuelle désactivant les modules non chargés
localyesconfig Mettre à jour la configuration actuelle en convertissant les mods locaux en core
defconfig Nouvelle configuration avec la valeur par défaut de defconfig fournie par Arch
savedefconfig Enregistrer la configuration actuelle sous ./defconfig (config minimale)
allnoconfig Nouvelle configuration où toutes les options sont répondues par "non"
allyesconfig Nouvelle configuration où toutes les options sont acceptées avec 'oui'
allmodconfig Nouvelle configuration sélectionnant les modules lorsque cela est possible
alldefconfig Nouvelle configuration avec tous les symboles définis par défaut
randconfig Nouvelle configuration avec une réponse aléatoire à toutes les options
listenouvelleconfig Liste des nouvelles options
olddefconfig Identique à oldconfig mais définit les nouveaux symboles sur leur valeur par défaut sans invite
kvmconfig Activer des options supplémentaires pour la prise en charge du noyau invité KVM
xenconfig Activer des options supplémentaires pour xen dom0 et la prise en charge du noyau invité
petiteconfig Configurer le plus petit noyau possible

Je pense que menuconfig est la plus populaire de ces cibles. Les cibles sont traitées par différents programmes hôtes, qui sont fournis par le noyau et construits lors de la construction du noyau. Certaines cibles ont une interface graphique (pour la commodité de l'utilisateur) alors que la plupart n'en ont pas. Les outils liés à Kconfig et le code source résident principalement sous scripts/kconfig/ dans la source du noyau. Comme nous pouvons le voir dans scripts/kconfig/Makefile , il existe plusieurs programmes hôtes, dont conf , mconf , et nconf . Sauf pour conf , chacun d'eux est responsable de l'une des cibles de configuration basées sur l'interface graphique, donc, conf traite la plupart d'entre eux.

Logiquement, l'infrastructure de Kconfig comporte deux parties :l'une implémente un nouveau langage pour définir les éléments de configuration (voir les fichiers Kconfig sous la source du noyau), et l'autre analyse le langage Kconfig et gère les actions de configuration.

La plupart des cibles de configuration ont à peu près le même processus interne (illustré ci-dessous) :

Le Terminal Linux

  • Les 7 meilleurs émulateurs de terminaux pour Linux
  • 10 outils de ligne de commande pour l'analyse de données sous Linux
  • Télécharger maintenant :Aide-mémoire SSH
  • Aide-mémoire des commandes Linux avancées
  • Tutoriels de ligne de commande Linux

Notez que tous les éléments de configuration ont une valeur par défaut.

La première étape lit le fichier Kconfig sous la racine source pour construire une base de données de configuration initiale ; puis il met à jour la base de données initiale en lisant un fichier de configuration existant selon cette priorité :

.config

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

/etc/kernel-config

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

ARCH_DEFCONFIG

arch/$(ARCH)/defconfig

Si vous effectuez une configuration basée sur l'interface graphique via menuconfig ou configuration en ligne de commande via oldconfig , la base de données est mise à jour en fonction de votre personnalisation. Enfin, la base de données de configuration est déversée dans le fichier .config.

Mais le fichier .config n'est pas le fourrage final pour la construction du noyau; c'est pourquoi le syncconfig cible existe. syncconfig était une cible de configuration appelée silentoldconfig , mais il ne fait pas ce que l'ancien nom indique, il a donc été renommé. De plus, comme il est à usage interne (pas pour les utilisateurs), il a été supprimé de la liste.

Voici une illustration de ce que syncconfig fait :

syncconfig prend .config en entrée et génère de nombreux autres fichiers, qui se répartissent en trois catégories :

  • auto.conf et tristate.conf sont utilisés pour le traitement de texte makefile. Par exemple, vous pouvez voir des déclarations comme celle-ci dans le makefile d'un composant : 
    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
  • autoconf.h est utilisé dans les fichiers sources en langage C.
  • Fichiers d'en-tête vides sous include/config/ sont utilisés pour le suivi de la dépendance à la configuration pendant kbuild, qui est expliqué ci-dessous.

Après la configuration, nous saurons quels fichiers et morceaux de code ne sont pas compilés.

kbuild

Construction par composant, appelée make récursif , est un moyen courant pour GNU make pour gérer un grand projet. Kbuild est un bon exemple de make récursif. En divisant les fichiers sources en différents modules/composants, chaque composant est géré par son propre makefile. Lorsque vous commencez à construire, un makefile supérieur invoque le makefile de chaque composant dans le bon ordre, construit les composants et les collecte dans l'exécutif final.

Kbuild fait référence à différents types de makefiles :

  • Makefile est le premier makefile situé à la racine source.
  • .config est le fichier de configuration du noyau.
  • arch/$(ARCH)/Makefile est le makefile arch, qui est le complément du makefile supérieur.
  • scripts/Makefile.* décrit les règles communes pour tous les makefiles de kbuild.
  • Enfin, il existe environ 500 kbuild makefiles .

Le makefile supérieur inclut le makefile arch, lit le fichier .config, descend dans les sous-répertoires, invoque make sur le makefile de chaque composant à l'aide de routines définies dans scripts/Makefile.* , construit chaque objet intermédiaire et relie tous les objets intermédiaires dans vmlinux. Le document du noyau Documentation/kbuild/makefiles.txt décrit tous les aspects de ces makefiles.

A titre d'exemple, regardons comment vmlinux est produit sur x86-64 :

Tous les .o les fichiers qui vont dans vmlinux vont d'abord dans leur propre built-in.a , qui est indiqué via les variables KBUILD_VMLINUX_INIT , KBUILD_VMLINUX_MAIN , KBUILD_VMLINUX_LIBS , puis sont collectés dans le fichier vmlinux.

Découvrez comment la création récursive est implémentée dans le noyau Linux, à l'aide d'un code makefile simplifié :

# 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

La recette make récursive est développée, par exemple :

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

Cela signifie faire ira dans scripts/Makefile.build pour continuer le travail de construction de chaque built-in.a . Avec l'aide de scripts/link-vmlinux.sh , le fichier vmlinux est enfin sous la racine source.

Comprendre vmlinux par rapport à bzImage

De nombreux développeurs de noyaux Linux peuvent ne pas être clairs sur la relation entre vmlinux et bzImage. Par exemple, voici leur relation en x86-64 :

La racine source vmlinux est dépouillée, compressée, placée dans piggy.S , puis lié à d'autres objets homologues dans arch/x86/boot/compressed/vmlinux . Pendant ce temps, un fichier appelé setup.bin est produit sous arch/x86/boot . Il peut y avoir un troisième fichier facultatif contenant des informations de relocalisation, selon la configuration de CONFIG_X86_NEED_RELOCS .

Un programme hôte appelé build , fourni par le noyau, construit ces deux (ou trois) parties dans le fichier bzImage final.

Suivi des dépendances

Kbuild suit trois types de dépendances :

  1. Tous les fichiers prérequis (les deux *.c et *.h )
  2. CONFIG_ options utilisées dans tous les fichiers prérequis
  3. Dépendances de la ligne de commande utilisées pour compiler la cible

Le premier est facile à comprendre, mais qu'en est-il du deuxième et du troisième ? Les développeurs du noyau voient souvent des morceaux de code comme celui-ci :

#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif

Lorsque CONFIG_SMP modifications, ce morceau de code doit être recompilé. La ligne de commande pour compiler un fichier source est également importante, car différentes lignes de commande peuvent entraîner différents fichiers objet.

Lorsqu'un .c file utilise un fichier d'en-tête via un #include directive, vous devez écrire une règle comme celle-ci :

main.o: defs.h
	recipe...

Lors de la gestion d'un grand projet, vous avez besoin de beaucoup de ces types de règles; les écrire tous serait fastidieux et ennuyeux. Heureusement, la plupart des compilateurs C modernes peuvent écrire ces règles pour vous en regardant le #include lignes dans le fichier source. Pour la collection de compilateurs GNU (GCC), il suffit d'ajouter un paramètre de ligne de commande :-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)

Cela générerait un .d fichier avec un contenu comme :

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

Puis le programme hôte fixdep prend soin des deux autres dépendances en prenant le depfile et la ligne de commande en entrée, puis la sortie d'un ..cmd file dans la syntaxe makefile, qui enregistre la ligne de commande et tous les prérequis (y compris la configuration) pour une cible. Il ressemble à ceci :

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

Un ..cmd sera inclus lors de la création récursive, fournissant toutes les informations de dépendance et aidant à décider de reconstruire ou non une cible.

Le secret derrière cela est que fixdep analysera le depfile (.d file), puis analysez tous les fichiers de dépendance à l'intérieur, recherchez le texte pour tous les CONFIG_ chaînes, convertissez-les dans le fichier d'en-tête vide correspondant et ajoutez-les aux prérequis de la cible. Chaque fois que la configuration change, le fichier d'en-tête vide correspondant sera également mis à jour, afin que kbuild puisse détecter ce changement et reconstruire la cible qui en dépend. Étant donné que la ligne de commande est également enregistrée, il est facile de comparer les paramètres de compilation précédents et actuels.

Regarder vers l'avenir

Kconfig/kbuild est resté le même pendant longtemps jusqu'à ce que le nouveau responsable, Masahiro Yamada, l'ait rejoint début 2017, et maintenant kbuild est à nouveau en cours de développement actif. Ne soyez pas surpris si vous voyez bientôt quelque chose de différent de ce qui est dans cet article.


Linux
  1. 30 choses que vous ne saviez pas sur le noyau Linux

  2. Analyser le noyau Linux avec ftrace

  3. Tests d'intégration continue pour le noyau Linux

  4. Explorer le système de fichiers Linux /proc

  5. Linux - Configurer, compiler et installer un noyau Linux personnalisé ?

Comment le noyau Linux gère les interruptions

Comment compiler un noyau Linux au 21e siècle

Comment vérifier la version du noyau sous Linux

Linux - Parties propriétaires ou fermées du noyau ?

Que fait exactement make oldconfig dans le makefile du noyau Linux ?

Quelle est la source actuelle du noyau Linux ?