GNU/Linux >> Tutoriales Linux >  >> Linux

Explorando el kernel de Linux:Los secretos de Kconfig/kbuild

El sistema de configuración/construcción del kernel de Linux, también conocido como Kconfig/kbuild, existe desde hace mucho tiempo, desde que el código del kernel de Linux migró a Git. Sin embargo, como infraestructura de apoyo, rara vez está en el centro de atención; incluso los desarrolladores del kernel que lo usan en su trabajo diario nunca piensan realmente en ello.

Para explorar cómo se compila el kernel de Linux, este artículo se sumergirá en el proceso interno de Kconfig/kbuild, explicará cómo se producen el archivo .config y los archivos vmlinux/bzImage e presentará un truco inteligente para el seguimiento de dependencias.

Kconfig

El primer paso para construir un kernel es siempre la configuración. Kconfig ayuda a que el kernel de Linux sea altamente modular y personalizable. Kconfig ofrece al usuario muchos objetivos de configuración:

Creo que menuconfig es el más popular de estos objetivos. Los objetivos son procesados ​​por diferentes programas host, que son proporcionados por el núcleo y construidos durante la construcción del núcleo. Algunos objetivos tienen una GUI (para comodidad del usuario), mientras que la mayoría no. Las herramientas y el código fuente relacionados con Kconfig residen principalmente en scripts/kconfig/ en la fuente del núcleo. Como podemos ver en scripts/kconfig/Makefile , hay varios programas anfitriones, incluido conf , mconf y nconf . Excepto conf , cada uno de ellos es responsable de uno de los objetivos de configuración basados ​​en GUI, por lo tanto, conf trata con la mayoría de ellos.

Lógicamente, la infraestructura de Kconfig consta de dos partes:una implementa un nuevo lenguaje para definir los elementos de configuración (consulte los archivos de Kconfig en el código fuente del kernel) y la otra analiza el lenguaje de Kconfig y se ocupa de las acciones de configuración.

La mayoría de los objetivos de configuración tienen aproximadamente el mismo proceso interno (que se muestra a continuación):

La terminal de Linux

  • Los 7 mejores emuladores de terminal para Linux
  • 10 herramientas de línea de comandos para el análisis de datos en Linux
  • Descargar ahora:hoja de referencia de SSH
  • Hoja de trucos de comandos avanzados de Linux
  • Tutoriales de línea de comandos de Linux

Tenga en cuenta que todos los elementos de configuración tienen un valor predeterminado.

El primer paso lee el archivo Kconfig en la raíz de origen para construir una base de datos de configuración inicial; luego actualiza la base de datos inicial leyendo un archivo de configuración existente de acuerdo con esta prioridad:

.config

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

/etc/kernel-config

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

ARCH_DEFCONFIG

arch/$(ARCH)/defconfig

Si está realizando una configuración basada en GUI a través de menuconfig o configuración basada en línea de comandos a través de oldconfig , la base de datos se actualiza según su personalización. Finalmente, la base de datos de configuración se vuelca en el archivo .config.

Pero el archivo .config no es el forraje final para la construcción del kernel; esta es la razón por la que syncconfig el objetivo existe. configuración de sincronización solía ser un objetivo de configuración llamado silentoldconfig , pero no hace lo que dice el nombre anterior, por lo que se le cambió el nombre. Además, debido a que es para uso interno (no para usuarios), se eliminó de la lista.

Aquí hay una ilustración de lo que syncconfig hace:

configuración de sincronización toma .config como entrada y genera muchos otros archivos, que se dividen en tres categorías:

  • auto.conf y tristate.conf se utilizan para el procesamiento de texto de archivos MAKE. Por ejemplo, puede ver declaraciones como esta en el archivo MAKE de un componente: 
    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
  • autoconf.h se utiliza en archivos fuente en lenguaje C.
  • Archivos de encabezado vacíos en include/config/ se utilizan para el seguimiento de la dependencia de configuración durante kbuild, que se explica a continuación.

Después de la configuración, sabremos qué archivos y piezas de código no están compilados.

kconstruir

Construcción por componentes, llamada marca recursiva , es una forma común para GNU make para gestionar un gran proyecto. Kbuild es un buen ejemplo de make recursivo. Al dividir los archivos fuente en diferentes módulos/componentes, cada componente es administrado por su propio archivo MAKE. Cuando comienza a compilar, un archivo MAKE superior invoca el archivo MAKE de cada componente en el orden correcto, compila los componentes y los recopila en el ejecutivo final.

Kbuild se refiere a diferentes tipos de archivos MAKE:

  • Archivo Make es el archivo MAKE superior ubicado en la raíz de origen.
  • .config es el archivo de configuración del kernel.
  • arch/$(ARCH)/Makefile es el archivo MAKE arch, que es el complemento del archivo MAKE superior.
  • scripts/Makefile.* describe reglas comunes para todos los archivos MAKE de kbuild.
  • Finalmente, hay alrededor de 500 kbuild makefiles .

El archivo make superior incluye el archivo make arch, lee el archivo .config, desciende a los subdirectorios, invoca make en el archivo MAKE de cada componente con la ayuda de rutinas definidas en scripts/Makefile.* , crea cada objeto intermedio y vincula todos los objetos intermedios en vmlinux. El documento del kernel Documentation/kbuild/makefiles.txt describe todos los aspectos de estos makefiles.

Como ejemplo, veamos cómo se produce vmlinux en x86-64:

Todos los .o los archivos que van a vmlinux primero van a su propio incorporado.a , que se indica mediante variables KBUILD_VMLINUX_INIT , KBUILD_VMLINUX_MAIN , KBUILD_VMLINUX_LIBS , luego se recopilan en el archivo vmlinux.

Eche un vistazo a cómo se implementa la creación recursiva en el kernel de Linux, con la ayuda del código simplificado del archivo 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

La receta make recursiva se expande, por ejemplo:

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

Esto significa hacer irá a scripts/Makefile.build para continuar el trabajo de construcción de cada incorporado.a . Con la ayuda de scripts/link-vmlinux.sh , el archivo vmlinux finalmente está bajo la raíz de la fuente.

Comprender vmlinux versus bzImage

Es posible que muchos desarrolladores del kernel de Linux no tengan clara la relación entre vmlinux y bzImage. Por ejemplo, aquí está su relación en x86-64:

La fuente raíz vmlinux se elimina, se comprime y se coloca en piggy.S , luego vinculado con otros objetos del mismo nivel en arch/x86/boot/compressed/vmlinux . Mientras tanto, se genera un archivo llamado setup.bin en arch/x86/boot. . Puede haber un tercer archivo opcional que tenga información de reubicación, según la configuración de CONFIG_X86_NEED_RELOCS .

Un programa host llamado build , proporcionada por el núcleo, crea estas dos (o tres) partes en el archivo bzImage final.

Seguimiento de dependencia

Kbuild rastrea tres tipos de dependencias:

  1. Todos los archivos de requisitos previos (ambos *.c y *.h )
  2. CONFIG_ opciones utilizadas en todos los archivos de requisitos previos
  3. Dependencias de la línea de comandos utilizadas para compilar el objetivo

El primero es fácil de entender, pero ¿qué pasa con el segundo y el tercero? Los desarrolladores del kernel a menudo ven fragmentos de código como este:

#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif

Cuando CONFIG_SMP cambios, este fragmento de código debe volver a compilarse. La línea de comando para compilar un archivo de origen también es importante, porque diferentes líneas de comando pueden generar diferentes archivos de objetos.

Cuando un .c El archivo usa un archivo de encabezado a través de un #include directiva, necesita escribir una regla como esta:

main.o: defs.h
	recipe...

Al administrar un proyecto grande, necesita muchos de estos tipos de reglas; escribirlos todos sería tedioso y aburrido. Afortunadamente, la mayoría de los compiladores de C modernos pueden escribir estas reglas mirando el #include líneas en el archivo fuente. Para GNU Compiler Collection (GCC), solo es cuestión de agregar un parámetro de línea de 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)

Esto generaría un .d archivo con contenido como:

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

Entonces el programa anfitrión fixdep se encarga de las otras dos dependencias tomando el depfile y la línea de comando como entrada, luego genera un ..cmd archivo en sintaxis de archivo MAKE, que registra la línea de comando y todos los requisitos previos (incluida la configuración) para un objetivo. Se ve así:

# 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 El archivo se incluirá durante la creación recursiva, proporcionando toda la información de dependencia y ayudando a decidir si reconstruir un objetivo o no.

El secreto detrás de esto es que fixdep analizará el depfile (.d archivo), luego analice todos los archivos de dependencia dentro, busque el texto para todos los CONFIG_ cadenas, conviértalas en el archivo de encabezado vacío correspondiente y agréguelas a los requisitos previos del destino. Cada vez que cambie la configuración, también se actualizará el archivo de encabezado vacío correspondiente, de modo que kbuild pueda detectar ese cambio y reconstruir el destino que depende de él. Debido a que la línea de comando también se registra, es fácil comparar los últimos parámetros de compilación con los actuales.

Kconfig/kbuild permaneció igual durante mucho tiempo hasta que el nuevo mantenedor, Masahiro Yamada, se unió a principios de 2017, y ahora kbuild está nuevamente en desarrollo activo. No se sorprenda si pronto ve algo diferente a lo que se encuentra en este artículo.


Linux
  1. Explorando el sistema de archivos /proc de Linux

  2. Linux:¿configurar, compilar e instalar un kernel de Linux personalizado?

  3. Linux:¿cómo determinar qué módulo contamina el kernel?

  4. Linux:¿por qué el kernel no puede ejecutar Init?

  5. Linux – ¿Diseño de tabla de página de volcado (configuración del kernel)?

Cómo el kernel de Linux maneja las interrupciones

Cómo compilar un kernel de Linux en el siglo XXI

Cómo verificar la versión del kernel en Linux

Linux:¿partes propietarias o cerradas del kernel?

¿Qué hace exactamente make oldconfig en el archivo MAKE del kernel de Linux?

¿Cuál es la fuente actual del kernel de Linux?

    config Actualice la configuración actual utilizando un programa orientado a la línea
    nconfig Actualice la configuración actual utilizando un programa basado en menú ncurses
    configuración del menú Actualice la configuración actual utilizando un programa basado en menú
    xconfig Actualice la configuración actual utilizando una interfaz basada en Qt
    gconfig Actualice la configuración actual utilizando una interfaz basada en GTK+
    configuración antigua Actualice la configuración actual utilizando un .config proporcionado como base
    localmodconfig Actualizar configuración actual deshabilitando módulos no cargados
    localyesconfig Actualizar la configuración actual convirtiendo mods locales en núcleo
    defconfig Nueva configuración con la configuración predeterminada de Arch-supplied defconfig
    configuración guardada Guardar configuración actual como ./defconfig (configuración mínima)
    sin configuración Nueva configuración donde todas las opciones se responden con 'no'
    aliadosconfig Nueva configuración donde todas las opciones se aceptan con 'sí'
    todasmodconfig Nueva configuración seleccionando módulos cuando sea posible
    alldefconfig Nueva configuración con todos los símbolos predeterminados
    randconfig Nueva configuración con una respuesta aleatoria a todas las opciones
    listarnuevaconfiguración Lista de nuevas opciones
    olddefconfig Igual que oldconfig pero establece nuevos símbolos a su valor predeterminado sin preguntar
    kvmconfig Habilite opciones adicionales para la compatibilidad con el kernel invitado de KVM
    xenconfig Habilite opciones adicionales para xen dom0 y compatibilidad con kernel invitado
    configuración pequeña Configure el núcleo más pequeño posible