fs_initcall、early_initcall、__init宏

来源:互联网 发布:mac机械键盘推荐 编辑:程序博客网 时间:2024/06/04 00:30

fs_initcall、early_initcall...分析

在内核代码中经常可以看到类似fs_initcall\early_initcall\late_initcall这样的宏,这些宏有什么作用?如何实现的?下面来具体分析

具体定义

include/linux/init.h中可以找到这些宏的定义

/* * Early initcalls run before initializing SMP. * * Only for built-in code, not modules. */#define early_initcall(fn)      __define_initcall(fn, early)/* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. * Keep main.c:initcall_level_names[] in sync. */#define pure_initcall(fn)       __define_initcall(fn, 0)#define core_initcall(fn)       __define_initcall(fn, 1)#define core_initcall_sync(fn)      __define_initcall(fn, 1s)#define postcore_initcall(fn)       __define_initcall(fn, 2)#define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)#define arch_initcall(fn)       __define_initcall(fn, 3)#define arch_initcall_sync(fn)      __define_initcall(fn, 3s)#define subsys_initcall(fn)     __define_initcall(fn, 4)#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)#define fs_initcall(fn)         __define_initcall(fn, 5)#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)#define rootfs_initcall(fn)     __define_initcall(fn, rootfs)#define device_initcall(fn)     __define_initcall(fn, 6)#define device_initcall_sync(fn)    __define_initcall(fn, 6s)#define late_initcall(fn)       __define_initcall(fn, 7)#define late_initcall_sync(fn)      __define_initcall(fn, 7s)

可以看到它们利用了__define_initcall宏:

/* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined * by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection. * * The `id' arg to __define_initcall() is needed so that multiple initcalls * can point at the same handler without causing duplicate-symbol build errors. */#define __define_initcall(fn, id) \    static initcall_t __initcall_##fn##id __used \    __attribute__((__section__(".initcall" #id ".init"))) = fn; \    LTO_REFERENCE_INITCALL(__initcall_##fn##id)

从上面的注释信息可以看出:initcalls这类函数会被分别放置到不同的subsections中,subsections中的先后顺序由linker来决定(但是subsections之间的顺序是能够确定的,由vmlinux.lds.S指定);此外id的作用是防止duplicate-symbol错误,如果没有这个参数,那么两类不同的initcall使用同一个fn的话会发生symbol重复定义的错误。

__define_initcall宏的作用是定义了一个initcall_t类型(函数指针)的变量,并将它放在名为.initcall#id.init的subsection中。该变量指向函数fn。
这样一来的话,这些subsection中存储的都是些函数指针,可以通过这些值来调用对应的函数。那么何时调用这些函数?

调用时机

首先,我们需要理解几个变量:

static initcall_t *initcall_levels[] __initdata = {    __initcall0_start,    __initcall1_start,    __initcall2_start,    __initcall3_start,    __initcall4_start,    __initcall5_start,    __initcall6_start,    __initcall7_start,    __initcall_end,};

这几个__initcall开头的变量值实际上是在link阶段就被确定了。在内核的链接脚本中会记录这几个subsection的起始、结束地址,分别存在上述变量中。这样的话就可以在程序中通过这些变量来访问这些subsections。

在内核启动初始化过程中会有下列流程:

start_kernel --> rest_init

在rest_init函数中会创建一个内核线程,执行kernel_init

kernel_init --> kernel_init_freeable --> do_pre_smp_initcalls                              |---> do_basic_setup --> do_initcalls

early_initcall需要在SMP初始化之前被调用,其在do_pre_smp_initcalls函数中被调用:

static void __init do_pre_smp_initcalls(void){    initcall_t *fn;    for (fn = __initcall_start; fn < __initcall0_start; fn++)        do_one_initcall(*fn);}

这个for循环只对early_initcall定义的函数进行调用,do_one_initcall函数中会调用fn指向的函数。

同理,在do_initcalls函数中会对其他initcall函数进行调用。

module_init

我们现在知道了上面的initcall是在内核初始化过程中被调用的,因此使用initcall定义的函数一定是built-in的。

在模块编程的时候会使用到module_init,它也有两种定义,在include/linux/module.h中:

#ifndef MODULE......#define module_init(x)  __initcall(x);......#else /* MODULE */....../* Each module must use one module_init(). */#define module_init(initfn)                 \    static inline initcall_t __inittest(void)       \    { return initfn; }                  \    int init_module(void) __attribute__((alias(#initfn)));......#endif

对于built-in的模块,其module_init对应的是__initcall,最终对应的是device_initcall(在include/linux/init.h中):

#define __initcall(fn) device_initcall(fn)

这样,在内核初始化过程中就会调用到模块的初始化函数。

对于load的模块,我们暂时先不关心,以后分析模块子系统时再看。

链接脚本上相关内容

上面提到过各个subsections之间的顺序在链接脚本中指定的,我们现在就来看看链接脚本中有关的内容。在arch/x86/kernel/vmlinux.lds.S中有以下内容:

......#include <asm-generic/vmlinux.lds.h>......SECTIONS{  ......  INIT_DATA_SECTION(16)  ......}

进入include/asm-generic/vmlinux.lds.h文件,我们可以看到INIT_DATA_SECTION的定义:

#define INIT_DATA_SECTION(initsetup_align)              \    .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {       \        ......        INIT_CALLS                      \        ......    }

INIT_CALLS的定义如下:

#define INIT_CALLS_LEVEL(level)                                         \                VMLINUX_SYMBOL(__initcall##level##_start) = .;          \                *(.initcall##level##.init)                              \                *(.initcall##level##s.init)                             \#define INIT_CALLS                                                      \                VMLINUX_SYMBOL(__initcall_start) = .;                   \                *(.initcallearly.init)                                  \                INIT_CALLS_LEVEL(0)                                     \                INIT_CALLS_LEVEL(1)                                     \                INIT_CALLS_LEVEL(2)                                     \                INIT_CALLS_LEVEL(3)                                     \                INIT_CALLS_LEVEL(4)                                     \                INIT_CALLS_LEVEL(5)                                     \                INIT_CALLS_LEVEL(rootfs)                                \                INIT_CALLS_LEVEL(6)                                     \                INIT_CALLS_LEVEL(7)                                     \                VMLINUX_SYMBOL(__initcall_end) = .;

这个宏中的顺序就是在最终内核镜像中subsections之间的顺序,并且可以看到对于每个subsection都导出了xxx_start值,而整个initcall的范围是_initcall_start、_initcall_end,这些变量在上面处理initcall的时候会被使用到。

__init分析

__init也是在include/linux/init.h中定义的:

#define __init          __section(.init.text) __cold notrace

通过__init定义的函数表示是在内核初始化期间被调用的,当初始化完成之后会将他们释放掉。它指明将该函数放到.init.text section中。那么这个section放置在什么地方?

arch/x86/kernel/vmlinux.lds.S中有以下内容:

......#include <asm-generic/vmlinux.lds.h>......SECTIONS{  ......  INIT_TEXT_SECTION(PAGE_SIZE)  ......  INIT_DATA_SECTION(16)  ......}

INIT_DATA_SECTION我们在上面已经分析过了,这里还有INIT_TEXT_SECTION,这个宏在vmlinux.lds.h中定义:

......#define INIT_TEXT                                                       \        *(.init.text)                                                   \        MEM_DISCARD(init.text)......#define INIT_TEXT_SECTION(inittext_align)                               \        . = ALIGN(inittext_align);                                      \        .init.text : AT(ADDR(.init.text) - LOAD_OFFSET) {               \                VMLINUX_SYMBOL(_sinittext) = .;                         \                INIT_TEXT                                               \                VMLINUX_SYMBOL(_einittext) = .;                         \        }......

可以看到上述是对.init.text section如何放置的描述,并且导出了_sinittext、_einittext分别表示此section的起始、结束地址。

链接脚本展开后最终的效果如下:

* SECTIONS* {*      . = START;*      __init_begin = .;*      HEAD_TEXT_SECTION*      INIT_TEXT_SECTION(PAGE_SIZE)*      INIT_DATA_SECTION(...)*      PERCPU_SECTION(CACHELINE_SIZE)*      __init_end = .;**      _stext = .;*      TEXT_SECTION = 0*      _etext = .;**      _sdata = .;*      RO_DATA_SECTION(PAGE_SIZE)*      RW_DATA_SECTION(...)*      _edata = .;**      EXCEPTION_TABLE(...)*      NOTES**      BSS_SECTION(0, 0, 0)*      _end = .;**      STABS_DEBUG*      DWARF_DEBUG**      DISCARDS                // must be the last* }

我们现在只关注和init section相关的,即被_init_begin和_init_end所包围的内容,这篇我们关注的initcalls和_init可以看到都包括在其中。

在上面我们提到了内核初始化尾声创建了一个kernel_init内核线程,其中会执行free_initmem函数:

void free_initmem(void){    free_init_pages("unused kernel",            (unsigned long)(&__init_begin),            (unsigned long)(&__init_end));}

可以看到它会将_init_begin和_init_end之间的内存释放掉。

0 0
原创粉丝点击