GNU/Linux >> Linux Esercitazione >  >> Linux

Esplorazione del kernel Linux:i segreti di Kconfig/kbuild

Il sistema di configurazione/build del kernel Linux, noto anche come Kconfig/kbuild, esiste da molto tempo, da quando il codice del kernel Linux è migrato su Git. Come infrastruttura di supporto, tuttavia, è raramente sotto i riflettori; anche gli sviluppatori del kernel che lo usano nel loro lavoro quotidiano non ci pensano mai davvero.

Per esplorare come viene compilato il kernel Linux, questo articolo approfondirà il processo interno di Kconfig/kbuild, spiegherà come vengono prodotti il ​​file .config e i file vmlinux/bzImage e introdurrà un trucco intelligente per il monitoraggio delle dipendenze.

Kconfig

Il primo passo nella creazione di un kernel è sempre la configurazione. Kconfig aiuta a rendere il kernel Linux altamente modulare e personalizzabile. Kconfig offre all'utente molti target di configurazione:

config Aggiorna la configurazione corrente utilizzando un programma orientato alla riga
nconfig Aggiorna la configurazione corrente utilizzando un programma basato su menu di ncurses
menuconfig Aggiorna la configurazione corrente utilizzando un programma basato su menu
xconfig Aggiorna la configurazione corrente utilizzando un frontend basato su Qt
gconfig Aggiorna la configurazione corrente utilizzando un frontend basato su GTK+
oldconfig Aggiorna la configurazione corrente utilizzando un .config fornito come base
localmodconfig Aggiorna la configurazione corrente disabilitando i moduli non caricati
configurazione locale Aggiorna la configurazione corrente convertendo le mod locali in core
defconfig Nuova configurazione con default da defconfig fornito da Arch
savedefconfig Salva la configurazione corrente come ./defconfig (configurazione minima)
allnoconfig Nuova configurazione in cui tutte le opzioni ricevono una risposta con 'no'
allyesconfig Nuova configurazione in cui tutte le opzioni vengono accettate con 'yes'
allmodconfig Nuova configurazione per selezionare i moduli quando possibile
alldefconfig Nuova configurazione con tutti i simboli impostati come predefiniti
randconfig Nuova configurazione con una risposta casuale a tutte le opzioni
listnewconfig Elenca nuove opzioni
olddefconfig Come oldconfig ma imposta i nuovi simboli al loro valore predefinito senza chiedere
kvmconfig Abilita opzioni aggiuntive per il supporto del kernel guest KVM
xenconfig Abilita opzioni aggiuntive per xen dom0 e il supporto del kernel guest
tinyconfig Configura il kernel più piccolo possibile

Penso menuconfig è il più popolare di questi obiettivi. I target vengono elaborati da diversi programmi host, forniti dal kernel e compilati durante la creazione del kernel. Alcuni target hanno una GUI (per comodità dell'utente), mentre la maggior parte no. Gli strumenti e il codice sorgente relativi a Kconfig risiedono principalmente in scripts/kconfig/ nel sorgente del kernel. Come possiamo vedere da scripts/kconfig/Makefile , esistono diversi programmi host, incluso conf , mconf e nconf . Fatta eccezione per conf , ognuno di essi è responsabile di una delle destinazioni di configurazione basate sulla GUI, quindi conf si occupa della maggior parte di loro.

Logicamente, l'infrastruttura di Kconfig ha due parti:una implementa un nuovo linguaggio per definire gli elementi di configurazione (vedi i file Kconfig sotto il sorgente del kernel), e l'altro analizza il linguaggio Kconfig e si occupa delle azioni di configurazione.

La maggior parte delle destinazioni di configurazione ha più o meno lo stesso processo interno (mostrato di seguito):

Il terminale Linux

  • I 7 migliori emulatori di terminale per Linux
  • 10 strumenti da riga di comando per l'analisi dei dati in Linux
  • Scarica ora:cheat sheet SSH
  • Cheat sheet sui comandi avanzati di Linux
  • Esercitazioni sulla riga di comando di Linux

Tieni presente che tutti gli elementi di configurazione hanno un valore predefinito.

Il primo passo legge il file Kconfig sotto la radice del sorgente per costruire un database di configurazione iniziale; quindi aggiorna il database iniziale leggendo un file di configurazione esistente secondo questa priorità:

.config

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

/etc/kernel-config

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

ARCH_DEFCONFIG

arch/$(ARCH)/defconfig

Se stai eseguendo una configurazione basata su GUI tramite menuconfig o configurazione basata su riga di comando tramite oldconfig , il database viene aggiornato in base alla tua personalizzazione. Infine, il database di configurazione viene scaricato nel file .config.

Ma il file .config non è il foraggio finale per la creazione del kernel; questo è il motivo per cui syncconfig l'obiettivo esiste. configurazione di sincronizzazione era una destinazione di configurazione chiamata silentoldconfig , ma non fa quello che dice il vecchio nome, quindi è stato rinominato. Inoltre, poiché è per uso interno (non per utenti), è stato eliminato dall'elenco.

Ecco un'illustrazione di cosa syncconfig fa:

configurazione di sincronizzazione prende .config come input e genera molti altri file, che rientrano in tre categorie:

  • auto.conf e tristate.conf vengono utilizzati per l'elaborazione del testo del makefile. Ad esempio, potresti vedere affermazioni come questa nel makefile di un componente: 
    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
  • autoconf.h viene utilizzato nei file sorgente in linguaggio C.
  • Svuota i file di intestazione in include/config/ vengono utilizzati per il monitoraggio della dipendenza dalla configurazione durante kbuild, come spiegato di seguito.

Dopo la configurazione, sapremo quali file e parti di codice non sono stati compilati.

kbuild

Costruzione a componenti, chiamata make ricorsivo , è un modo comune per GNU make per gestire un grande progetto. Kbuild è un buon esempio di make ricorsivo. Dividendo i file sorgente in diversi moduli/componenti, ogni componente è gestito dal proprio makefile. Quando inizi a creare, un makefile superiore richiama il makefile di ogni componente nell'ordine corretto, crea i componenti e li raccoglie nell'esecutivo finale.

Kbuild si riferisce a diversi tipi di makefile:

  • Creafile è il makefile in alto che si trova nella radice di origine.
  • .config è il file di configurazione del kernel.
  • arch/$(ARCH)/Makefile è il makefile arch, che è il supplemento al makefile superiore.
  • script/makefile.* descrive le regole comuni per tutti i makefile di kbuild.
  • Infine, ci sono circa 500 kbuild makefile .

Il makefile in alto include il makefile arch, legge il file .config, scende nelle sottodirectory, invoca make sul makefile di ogni componente con l'aiuto delle routine definite in scripts/Makefile.* , crea ogni oggetto intermedio e collega tutti gli oggetti intermedi in vmlinux. Documentazione del kernel Documentation/kbuild/makefiles.txt descrive tutti gli aspetti di questi makefile.

Ad esempio, diamo un'occhiata a come viene prodotto vmlinux su x86-64:

Tutti i .o i file che entrano in vmlinux vanno prima nel loro built-in.a , indicato tramite le variabili KBUILD_VMLINUX_INIT , KBUILD_VMLINUX_MAIN , KBUILD_VMLINUX_LIBS , quindi vengono raccolti nel file vmlinux.

Dai un'occhiata a come il make ricorsivo viene implementato nel kernel Linux, con l'aiuto del codice makefile semplificato:

# 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 ricetta ricorsiva make viene ampliata, ad esempio:

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

Ciò significa fare andrà in scripts/Makefile.build per continuare il lavoro di costruzione di ogni built-in.a . Con l'aiuto di scripts/link-vmlinux.sh , il file vmlinux è finalmente nella radice del sorgente.

Capire vmlinux e bzImage

Molti sviluppatori di kernel Linux potrebbero non essere chiari sulla relazione tra vmlinux e bzImage. Ad esempio, ecco la loro relazione in x86-64:

La radice di origine vmlinux viene rimossa, compressa e inserita in piggy.S , quindi collegato con altri oggetti peer in arch/x86/boot/compressed/vmlinux . Nel frattempo, un file chiamato setup.bin viene prodotto in arch/x86/boot . Potrebbe esserci un terzo file facoltativo con informazioni sul trasferimento, a seconda della configurazione di CONFIG_X86_NEED_RELOCS .

Un programma host chiamato build , fornito dal kernel, compila queste due (o tre) parti nel file bzImage finale.

Tracciamento delle dipendenze

Kbuild tiene traccia di tre tipi di dipendenze:

  1. Tutti i file dei prerequisiti (entrambi *.c e *.h )
  2. CONFIG_ opzioni utilizzate in tutti i file dei prerequisiti
  3. Dipendenze della riga di comando utilizzate per compilare la destinazione

Il primo è facile da capire, ma per quanto riguarda il secondo e il terzo? Gli sviluppatori del kernel vedono spesso pezzi di codice come questo:

#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif

Quando CONFIG_SMP modifiche, questo pezzo di codice dovrebbe essere ricompilato. Anche la riga di comando per la compilazione di un file sorgente è importante, poiché righe di comando diverse possono generare file oggetto diversi.

Quando un .c file utilizza un file di intestazione tramite un #include direttiva, devi scrivere una regola come questa:

main.o: defs.h
	recipe...

Quando si gestisce un grande progetto, sono necessarie molte di queste regole; scriverli tutti sarebbe noioso e noioso. Fortunatamente, la maggior parte dei moderni compilatori C può scrivere queste regole per te guardando il #include righe nel file di origine. Per GNU Compiler Collection (GCC), è solo questione di aggiungere un parametro da riga di comando:-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)

Ciò genererebbe un .d file con contenuti come:

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

Quindi il programma host fixdep si occupa delle altre due dipendenze prendendo il depfile e riga di comando come input, quindi emettendo un ..cmd file nella sintassi makefile, che registra la riga di comando e tutti i prerequisiti (inclusa la configurazione) per una destinazione. Si presenta così:

# 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 il file verrà incluso durante la creazione ricorsiva, fornendo tutte le informazioni sulla dipendenza e aiutando a decidere se ricostruire un target o meno.

Il segreto dietro questo è che fixdep analizzerà il depfile (.d file), quindi analizza tutti i file di dipendenza all'interno, cerca nel testo tutti i CONFIG_ stringhe, convertirle nel corrispondente file di intestazione vuoto e aggiungerle ai prerequisiti della destinazione. Ogni volta che la configurazione cambia, verrà aggiornato anche il corrispondente file di intestazione vuoto, in modo che kbuild possa rilevare quella modifica e ricostruire la destinazione che dipende da essa. Poiché viene registrata anche la riga di comando, è facile confrontare gli ultimi parametri di compilazione e quelli correnti.

Guardando avanti

Kconfig/kbuild è rimasto lo stesso per molto tempo fino a quando il nuovo manutentore, Masahiro Yamada, si è unito all'inizio del 2017, e ora kbuild è di nuovo in fase di sviluppo attivo. Non sorprenderti se vedrai presto qualcosa di diverso da quello che c'è in questo articolo.


Linux
  1. Test di integrazione continui per il kernel Linux

  2. Esplorazione del filesystem Linux /proc

  3. Linux:come determinare quale modulo contamina il kernel?

  4. Linux:perché il kernel non può eseguire Init?

  5. Linux:parti proprietarie o chiuse del kernel?

Analizza il kernel Linux con ftrace

Come il kernel Linux gestisce gli interrupt

Come compilare un kernel Linux nel 21° secolo

Come controllare la versione del kernel in Linux

Cosa fa esattamente oldconfig nel makefile del kernel di Linux?

Qual è l'attuale sorgente del kernel Linux?