module_init()vs.core_initcall()vs.early_initcall() [英] module_init() vs. core_initcall() vs. early_initcall()
问题描述
在驱动程序中,我经常看到这三种类型的init函数正在使用.
In drivers I often see these three types of init functions being used.
module_init()
core_initcall()
early_initcall()
- 在什么情况下应该使用它们?
- 还有其他初始化方法吗?
推荐答案
它们确定内置模块的初始化顺序.驱动程序大多数时候将使用device_initcall
(或module_init
;请参见下文).特定于体系结构的代码通常使用早期初始化(early_initcall
)来初始化硬件子系统(电源管理,DMA等),然后再初始化任何实际的驱动程序.
They determine the initialization order of built-in modules. Drivers will use device_initcall
(or module_init
; see below) most of the time. Early initialization (early_initcall
) is normally used by architecture-specific code to initialize hardware subsystems (power management, DMAs, etc.) before any real driver gets initialized.
查看 init/main.c
.通过arch/<arch>/boot
和arch/<arch>/kernel
中的代码完成一些特定于体系结构的初始化之后,将调用可移植的start_kernel
函数.最终,在同一文件中,do_basic_setup
被称为:
Look at init/main.c
. After a few architecture-specific initialization done by code in arch/<arch>/boot
and arch/<arch>/kernel
, the portable start_kernel
function will be called. Eventually, in the same file, do_basic_setup
is called:
/*
* 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
为1,依此类推.这些__initcall*_start
条目中的每一个均指向一个函数指针数组,该函数指针彼此调用.这些函数指针是实际的模块和内置的初始化函数,是您使用module_init
,early_initcall
等指定的函数.
You can see the names above with their associated index: early
is 0, core
is 1, etc. Each of those __initcall*_start
entries point to an array of function pointers which get called one after the other. Those function pointers are the actual modules and built-in initialization functions, the ones you specify with module_init
, early_initcall
, etc.
什么决定哪个函数指针进入哪个__initcall*_start
数组?链接器使用module_init
和*_initcall
宏的提示来执行此操作.对于内置模块,这些宏将功能指针分配给特定的ELF部分.
What determines which function pointer gets into which __initcall*_start
array? The linker does this, using hints from the module_init
and *_initcall
macros. Those macros, for built-in modules, assign the function pointers to a specific ELF section.
考虑到内置模块(在.config
中用y
配置),module_init
像这样简单地扩展(
Considering a built-in module (configured with y
in .config
), module_init
simply expands like this (include/linux/init.h
):
#define module_init(x) __initcall(x);
然后我们遵循以下步骤:
and then we follow this:
#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
:
So, now, module_init(my_func)
means __define_initcall(my_func, 6)
. This is _define_initcall
:
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
这意味着,到目前为止,我们有:
which means, so far, we have:
static initcall_t __initcall_my_func6 __used
__attribute__((__section__(".initcall6.init"))) = my_func;
哇,有很多GCC内容,但这仅意味着创建了一个新符号__initcall_my_func6
,该符号放在名为.initcall6.init
的ELF部分中,并且如您所见,它指向指定的函数(.initcall6.init
ELF节中.
Wow, lots of GCC stuff, but it only means that a new symbol is created, __initcall_my_func6
, that's put in the ELF section named .initcall6.init
, and as you can see, points to the specified function (my_func
). Adding all the functions to this section eventually creates the complete array of function pointers, all stored within the .initcall6.init
ELF section.
再次查看以下内容:
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
让我们以6级表示,它代表所有用module_init
初始化的内置模块.它从__initcall6_start
开始,其值是在.initcall6.init
节中注册的第一个函数指针的地址,并以__initcall7_start
结尾(不包括),每次以*fn
的大小递增(这是一个initcall_t
,这是void*
,根据架构,它是32位或64位).
Let's take level 6, which represents all the built-in modules initialized with module_init
. It starts from __initcall6_start
, its value being the address of the first function pointer registered within the .initcall6.init
section, and ends at __initcall7_start
(excluded), incrementing each time with the size of *fn
(which is an initcall_t
, which is a void*
, which is 32-bit or 64-bit depending on the architecture).
do_one_initcall
将仅调用当前条目指向的函数.
do_one_initcall
will simply call the function pointed to by the current entry.
在特定的初始化部分中,确定为什么要在另一个函数之前调用初始化函数的原因仅仅是Makefile中文件的顺序,因为链接器将在各自的ELF初始化中依次连接__initcall_*
符号.部分.
Within a specific initialization section, what determines why an initialization function is called before another is simply the order of the files within the Makefiles since the linker will concatenate the __initcall_*
symbols one after the other in their respective ELF init. sections.
这个事实实际上是在内核中使用的,例如使用设备驱动程序( drivers/Makefile
):
This fact is actually used in the kernel, e.g. with device drivers (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依赖.
tl;dr: the Linux kernel initialization mechanism is really beautiful, albeit highlight GCC-dependent.
这篇关于module_init()vs.core_initcall()vs.early_initcall()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!