module_init()与core_initcall()与early_initcall()的关系

mk.*_*k.. 29 init linux-device-driver linux-kernel

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

module_init()
core_initcall()
early_initcall()
Run Code Online (Sandbox Code Playgroud)
  1. 我应该在什么情况下使用它们?
  2. 另外,有没有其他的init方式?

eep*_*epp 48

它们确定内置模块的初始化顺序.司机大部分时间都会使用device_initcall(或module_init参见下文).早期初始化(early_initcall)通常由体系结构特定代码使用,以在任何真实驱动程序初始化之前初始化硬件子系统(电源管理,DMA等).

以下理解的技术内容

看看init/main.c.通过在代码中做了一些具体的架构,初始化后arch/<arch>/bootarch/<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();
}
Run Code Online (Sandbox Code Playgroud)

最后致电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);
}
Run Code Online (Sandbox Code Playgroud)

您可以看到上面的名称及其相关索引:early0,core是1等.每个__initcall*_start条目都指向一个接一个调用的函数指针数组.这些函数指针是实际模块和内置的初始化函数,你指定的人module_init,early_initcall等等.

什么决定哪个函数指针进入哪个__initcall*_start数组?链接器使用module_init*_initcall宏中的提示执行此操作.对于内置模块,这些宏将函数指针分配给特定的ELF部分.

用例子 module_init

考虑内置模块(配置为yin .config),module_init只需像这样展开(include/linux/init.h):

#define module_init(x)  __initcall(x);
Run Code Online (Sandbox Code Playgroud)

然后我们遵循这个:

#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)             __define_initcall(fn, 6)
Run Code Online (Sandbox Code Playgroud)

所以,现在,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
Run Code Online (Sandbox Code Playgroud)

这意味着,到目前为止,我们有:

static initcall_t __initcall_my_func6 __used
__attribute__((__section__(".initcall6.init"))) = my_func;
Run Code Online (Sandbox Code Playgroud)

哇,很多GCC的东西,但它只是意味着创建了一个新的符号__initcall_my_func6,它被放入名为的ELF部分.initcall6.init,正如你所看到的,指向指定的函数(my_func).将所有函数添加到此部分最终会创建完整的函数指针数组,所有函数指针都存储在.initcall6.initELF部分中.

初始化示例

再看看这个块:

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
    do_one_initcall(*fn);
Run Code Online (Sandbox Code Playgroud)

让我们取6级,它代表所有用它初始化的内置模块module_init.它从它开始__initcall6_start,它的值是在.initcall6.init段中注册的第一个函数指针的地址,并以__initcall7_start(排除)结束,每次递增的大小为*fn(initcall_ta void*,即a ,32位或64-位取决于架构).

do_one_initcall 将简单地调用当前条目指向的函数.

在特定的初始化部分中,确定在另一个之前调用初始化函数的原因仅仅是Makefile中文件的顺序,因为链接器将__initcall_*在各自的ELF init中一个接一个地连接符号.部分.

这个事实实际上是在内核中使用的,例如设备驱动程序(drivers/Makefile):

# GPIO must come after pinctrl as gpios may need to mux pins etc
obj-y                           += pinctrl/
obj-y                           += gpio/
Run Code Online (Sandbox Code Playgroud)

tl; dr:Linux内核初始化机制非常漂亮,虽然突出了GCC依赖.


The*_*ist 18

module_init用于标记要用作Linux设备驱动程序入口点的函数.
它被称为

  • do_initcalls()(对于内置驱动程序)
  • 在模块插入时(对于*.ko模块)

可以有只有1module_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. modprobeinsmod*.ko模块.

使用module_init()在设备驱动程序是相当于登记device_initcall().

请记住,在编译期间,链接*.oLinux内核中的各种驱动程序目标文件()的顺序非常重要; 它确定在运行时调用它们的顺序.

*_initcall 在引导期间将按照它们链接的顺序调用相同级别的函数.

例如,更改SCSI驱动程序的链接顺序drivers/scsi/Makefile将更改检测到SCSI控制器的顺序,从而更改磁盘的编号.