linux-kernel linux-device-driver init

linux kernel - module_init() vs. core_initcall() vs. early_initcall()



linux-kernel linux-device-driver (2)

En los controladores, a menudo veo que se usan estos tres tipos de funciones de inicio.

module_init() core_initcall() early_initcall()

  1. ¿Bajo qué circunstancias debo usarlos?
  2. Además, ¿hay otras formas de init?

Ellos determinan el orden de inicialización de los módulos incorporados. Los controladores usarán device_initcall (o module_init ; consulte a continuación) la mayor parte del tiempo. La inicialización temprana ( early_initcall ) normalmente es utilizada por el código específico de la arquitectura para inicializar los subsistemas de hardware (administración de energía, DMA, etc.) antes de que se inicialice cualquier controlador real.

Material técnico para entender a continuación

Mira en init/main.c Después de algunas inicializaciones específicas de la arquitectura hechas por código en arch/<arch>/boot and arch/<arch>/kernel , se llamará a la start_kernel portable start_kernel . Finalmente, en el mismo archivo, se llama do_basic_setup :

/* * Ok, the machine is now initialized. None of the devices * have been touched yet, but the CPU subsystem is up and * running, and memory and process management works. * * Now we can finally start doing some real work.. */ static void __init do_basic_setup(void) { cpuset_init_smp(); usermodehelper_init(); shmem_init(); driver_init(); init_irq_proc(); do_ctors(); usermodehelper_enable(); do_initcalls(); }

que termina con una llamada a do_initcalls :

static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, }; /* Keep these in sync with initcalls in include/linux/init.h */ static char *initcall_level_names[] __initdata = { "early", "core", "postcore", "arch", "subsys", "fs", "device", "late", }; static void __init do_initcall_level(int level) { extern const struct kernel_param __start___param[], __stop___param[]; initcall_t *fn; strcpy(static_command_line, saved_command_line); parse_args(initcall_level_names[level], static_command_line, __start___param, __stop___param - __start___param, level, level, &repair_env_string); for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn); } static void __init do_initcalls(void) { int level; for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level); }

Puede ver los nombres de arriba con su índice asociado: __initcall*_start es 0, core es 1, etc. Cada una de esas entradas __initcall*_start apunta a una matriz de punteros de función que se llaman una tras otra. Esos punteros de función son los módulos reales y las funciones de inicialización integradas, las que especifica con module_init , early_initcall , etc.

¿Qué determina qué función obtiene el puntero en qué __initcall*_start array? El enlazador hace esto, usando sugerencias de las module_init y *_initcall . Esas macros, para módulos integrados, asignan los punteros a una sección ELF específica.

Ejemplo con module_init

Considerando un módulo incorporado (configurado con y en .config ), module_init simplemente se expande así ( include/linux/init.h ):

#define module_init(x) __initcall(x);

Y luego seguimos esto:

#define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall(fn, 6)

Entonces, ahora, module_init(my_func) significa __define_initcall(my_func, 6) . Esto es _define_initcall :

#define __define_initcall(fn, id) / static initcall_t __initcall_##fn##id __used / __attribute__((__section__(".initcall" #id ".init"))) = fn

lo que significa, hasta ahora, tenemos:

static initcall_t __initcall_my_func6 __used __attribute__((__section__(".initcall6.init"))) = my_func;

Wow, muchas cosas de GCC, pero solo significa que se crea un nuevo símbolo, __initcall_my_func6 , que se coloca en la sección ELF llamada .initcall6.init , y como puede ver, apunta a la función especificada ( my_func ). Agregar todas las funciones a esta sección eventualmente crea la matriz completa de punteros de función, todos almacenados dentro de la sección .initcall6.init ELF.

Ejemplo de inicialización

Mira de nuevo este trozo:

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn);

Tomemos el nivel 6, que representa todos los módulos module_init inicializados con module_init . Comienza desde __initcall6_start , su valor es la dirección del primer puntero a función registrado dentro de la sección .initcall6.init , y termina en __initcall7_start (excluido), incrementando cada vez con el tamaño de *fn (que es un initcall_t , que es un void* , que es de 32 bits o de 64 bits dependiendo de la arquitectura).

do_one_initcall simplemente llamará a la función apuntada por la entrada actual.

Dentro de una sección de inicialización específica, lo que determina por qué se llama a una función de inicialización antes que a otra es simplemente el orden de los archivos dentro de los Makefiles, ya que el enlazador concatenará los símbolos __initcall_* uno tras otro en su respectivo inicio ELF. secciones

Este hecho se usa realmente en el kernel, por ejemplo, con controladores de dispositivo ( drivers/Makefile ):

# GPIO must come after pinctrl as gpios may need to mux pins etc obj-y += pinctrl/ obj-y += gpio/

tl; dr: el mecanismo de inicialización del kernel de Linux es realmente hermoso, aunque destaca GCC-dependiente.


module_init se usa para marcar una función que se usará como punto de entrada de un controlador de dispositivo Linux.
Se llama

  • durante do_initcalls() (para un controlador incorporado)
    o
  • en el momento de inserción del módulo (para un módulo *.ko )

Puede haber SÓLO 1 module_init() por módulo de controlador.

Las *_initcall() se usan generalmente para establecer los punteros de función para inicializar varios subsistemas.

do_initcalls() contiene la invocación de la lista de varias llamadas de inicio y el orden relativo en el que se llaman durante el arranque del kernel de Linux.

  1. early_initcall()
  2. core_initcall()
  3. postcore_initcall()
  4. arch_initcall()
  5. subsys_initcall()
  6. fs_initcall()
  7. device_initcall()
  8. late_initcall()
    Fin de los módulos incorporados.
  9. insmod o insmod de módulos *.ko .

El uso de module_init() en un controlador de dispositivo es equivalente a registrar un device_initcall() .

Tenga en cuenta que durante la compilación, el orden de vincular los diversos archivos de objeto de controlador ( *.o ) dentro del kernel de Linux es significativo; determina el orden en que se llaman en tiempo de ejecución.

*_initcall funciones del mismo nivel.
se llamará durante el arranque en el orden en que están vinculados

Por ejemplo, cambiar el orden de enlace de los controladores SCSI en drivers/scsi/Makefile cambiará el orden en que se detectan los controladores SCSI y, por lo tanto, la numeración de los discos.