модуль init () против core initcall () против раннего initcall()

в драйверах я часто вижу, что используются эти три типа функций инициализации.

module_init()
core_initcall()
early_initcall()
  1. при каких обстоятельствах я должен использовать их?
  2. кроме того, есть ли другие способы init?

2 ответов


они определяют порядок инициализации встроенных модулей. Драйверы будут использовать device_initcall (или module_init; см. ниже) большую часть времени. Ранняя инициализация (early_initcall) обычно используется специфичным для архитектуры кодом для инициализации аппаратных подсистем (power management, DMAs и т. д.) до инициализации любого реального драйвера.

технический материал для понимания ниже

посмотреть init/main.c. После нескольких архитектурно-специфических инициализаций, выполненных код arch/<arch>/boot и arch/<arch>/kernel портативный start_kernel функция будет вызываться. В конце концов, в том же файле,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();
}

который заканчивается вызовом 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);
}

вы можете увидеть имена выше с их связанным индексом:early 0, то core is 1, etc. Каждый из этих __initcall*_start записи указывают на массив указателей функций, которые вызываются один за другим. Эти указатели функций являются фактическими модулями и встроенными функции инициализации, которые вы указываете с помощью module_init, early_initcall, etc.

что определяет, какой указатель функции попадает в какой __initcall*_start массив? Компоновщик делает это, используя подсказки из module_init и *_initcall макросы. Эти макросы для встроенных модулей назначают указатели функций определенному разделу ELF.

пример module_init

учитывая встроенный модуль (настроенный с y на .config), module_init просто расширяется, как это (include/linux/init.h):

#define module_init(x)  __initcall(x);

и затем мы следуем этому:

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

Итак, module_init(my_func) означает __define_initcall(my_func, 6). Это _define_initcall:

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

что означает, что до сих пор мы имеем:

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

Вау, много вещей GCC, но это означает только то, что создается новый символ,__initcall_my_func6, это помещено в раздел ELF под названием .initcall6.init, и, как вы можете видеть, указывает на указанную функцию (my_func). Добавление всех функций в этот раздел в конечном итоге создает полный массив указателей функций, все они хранятся в .initcall6.init секция ELF.

пример инициализации

Посмотрите еще раз на этот кусок:

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

давайте возьмем уровень 6, который представляет все встроенные модули, инициализированные с module_init. Она начинается с __initcall6_start, его значение является адресом первого указателя функции, зарегистрированного в и заканчивается на __initcall7_start (исключенные), увеличивая каждый раз с размер *fn (который является initcall_t, который является void*, который 32-разрядный или 64-разрядный в зависимости от архитектуры).

do_one_initcall просто вызовет функцию, на которую указывает текущая запись.

в определенном разделе инициализации то, что определяет, почему функция инициализации вызывается перед другим, - это просто порядок файлов в Makefiles, так как компоновщик объединит __initcall_* символы один за другим в их соответствующий эльф init. разделы.

этот факт используется в ядре, например, с драйверами устройств (drivers/Makefile):

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

tl; dr: механизм инициализации ядра Linux действительно красив,хотя и зависит от GCC.


module_init используется для обозначения функции, которая будет использоваться в качестве точки входа драйвера устройства Linux.
Она называется

  • во время do_initcalls() (драйвер встроенный)
    или
  • во время вставки модуля (для *.ko модуль)

здесь можно только 1 module_init() на водителя модуль.


на *_initcall() функции обычно используются для установки указателей функций для инициализации различных подсистем.

do_initcalls() в исходном коде ядра Linux код содержит вызов списка различных initcalls и относительный порядок, в котором они вызываются во время загрузки ядра 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()
    конец встроенных модулей
  9. modprobe или insmod of *.ko модули.

используя module_init() в драйвере устройства есть эквивалент регистрации a device_initcall().

имейте в виду, что во время компиляции порядок связывания различных объектных файлов драйвера (*.o) в ядре Linux является существенным; он определяет порядок, в котором они вызываются во время выполнения.

*_initcall функции тот же уровень
будет вызываться во время загрузки в порядке связанный.

например, изменение порядка ссылок драйверов SCSI в drivers/scsi/Makefile изменит порядок обнаружения контроллеров SCSI и, следовательно, нумерацию дисков.