inicio del módulo() vs. inicio del núcleo() vs. inicio temprano()


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

module_init()
core_initcall()
early_initcall()
  1. ¿En qué circunstancias debo usarlos?
  2. También, ¿hay otras formas de iniciarlo?
Author: Mateusz Piotrowski, 2013-09-04

2 answers

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

Material técnico para la comprensión a continuación

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

/*
 * 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);
}

Puedes ver los nombres anteriores con su índice asociado: early es 0, core es 1, etc. Cada una de esas entradas __initcall*_start apuntan a una matriz de punteros de función que se llaman uno tras otro. Esos punteros de función son los módulos reales y las funciones de inicialización incorporadas, las que se especifican con module_init, early_initcall, sucesivamente.

¿Qué determina qué puntero de función entra en qué matriz __initcall*_start? El enlazador hace esto, usando sugerencias de las macros module_init y *_initcall. Esas macros, para módulos incorporados, asignan los punteros de función 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: {[48]]}

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

Así que, 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:{[48]]}

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 pone en la sección ELF llamada .initcall6.init, y como puedes 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 esto chunk:

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 integrados inicializados con module_init. Comienza desde __initcall6_start, su valor es la dirección del primer puntero de función registrado dentro de la sección .initcall6.init, y termina en __initcall7_start (excluido), incrementándose cada vez con el tamaño de *fn (que es un initcall_t, que es un void*, que es de 32 bits o 64 bits dependiendo de la arquitectura).

do_one_initcall simplemente llamará a la función señalada por la corriente entrada.

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 concatena los símbolos __initcall_* uno tras otro en su respectivo inicio ELF. apartado.

Este hecho se utiliza realmente en el núcleo, por ejemplo, con los 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 destacar GCC-dependiente.

 42
Author: eepp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-04 06:18:54

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

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

Solo puede haber 1 module_init() por módulo de conductor.


Las funciones *_initcall() se utilizan generalmente para establecer los punteros de función para inicializar varios subsistema.

do_initcalls() dentro del código fuente del kernel de Linux 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 integrados
  9. modprobe o insmod de *.ko módulos.

Utilizando 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 vinculación de los diversos archivos objeto del controlador(*.o) dentro del núcleo Linux es significativo; determina el orden en el que se llaman en tiempo de ejecución.

*_initcall funciones de la mismo nivel
se llamarán 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 el que se detectan los controladores SCSI y, por lo tanto, la numeración de los discos.

 15
Author: TheCodeArtist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-22 09:39:58