module_init() 与 core_initcall() 与 early_initcall() [英] module_init() vs. core_initcall() vs. early_initcall()

查看:20
本文介绍了module_init() 与 core_initcall() 与 early_initcall()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在驱动程序中,我经常看到这三种类型的初始化函数被使用.

In drivers I often see these three types of init functions being used.

module_init()
core_initcall()
early_initcall()

  1. 我应该在什么情况下使用它们?
  2. 另外,还有其他初始化方式吗?

推荐答案

它们决定了内置模块的初始化顺序.大多数情况下,驱动程序将使用 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//bootarch//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_initearly_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 简单地扩展成这样(include/linux/init.h):

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);

然后我们按照这个:

#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 部分中,并且如您所见,指向指定的函数 (my_func).将所有函数添加到此部分最终会创建完整的函数指针数组,所有这些都存储在 .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 中文件的顺序,因为链接器将一个接一个地连接 __initcall_* 符号他们各自的 ELF init.部分.

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() 与 core_initcall() 与 early_initcall()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆