модуль init () против core initcall () против раннего initcall()
в драйверах я часто вижу, что используются эти три типа функций инициализации.
module_init()
core_initcall()
early_initcall()
- при каких обстоятельствах я должен использовать их?
- кроме того, есть ли другие способы 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.
-
early_initcall()
core_initcall()
postcore_initcall()
arch_initcall()
subsys_initcall()
fs_initcall()
device_initcall()
-
late_initcall()
конец встроенных модулей -
modprobe
илиinsmod
of*.ko
модули.
используя module_init()
в драйвере устройства есть эквивалент регистрации a device_initcall()
.
имейте в виду, что во время компиляции порядок связывания различных объектных файлов драйвера (*.o
) в ядре Linux является существенным; он определяет порядок, в котором они вызываются во время выполнения.
*_initcall
функции тот же уровень
будет вызываться во время загрузки в порядке связанный.
например, изменение порядка ссылок драйверов SCSI в drivers/scsi/Makefile
изменит порядок обнаружения контроллеров SCSI и, следовательно, нумерацию дисков.