module_init()加载设备驱动

 咱们知道在写设备驱动的时候一般要为某个设备实现xxx_init函数,并将该函数传入module_init(xxx_init), 当kernel启动以后该设备驱动就能够被内核加载,这一章节将以倒叙的方式详细介绍了内核是如何加载module_init()函数,并最终调用到xxx_init函数的。
       module_init()定义在include/linux/module.h中,
#ifndef MODULE
#define module_init(x)    __initcall(x);
#define module_exit(x)    __exitcall(x);
#else /* MODULE */
/* Each module must use one module_init(). */
#define module_init(initfn)                    \
    static inline initcall_t __maybe_unused __inittest(void)        \
    { return initfn; }                    \
    int init_module(void) __attribute__((alias(#initfn)));

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)                    \
    static inline exitcall_t __maybe_unused __exittest(void)        \
    { return exitfn; }                    \
    void cleanup_module(void) __attribute__((alias(#exitfn)));
#endif
        其中有两部分定义,设备驱动的加载有两种方式,一种是编译进内核,一种是以模块的方式加载,加载方式不一样定义的形式也略有不一样。#ifndef MODULE代表当设备驱动编译进内核时, module_init的定义形式。
#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(fn) device_initcall(fn)
       从__inicall定义能够,module_init()-->__initcall(fn)--->__define_initcall(fn)---> __define_initcall(fn, 6),能够看到在initcall段中启动的部分最终都是经过__define_initcall设置的。这部分的代码能够在include/linux/int.h中找到。
#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn;
       下面分析一下该宏的具体细节,好比,__define_initcall传递过来的参数为(xxx_init,  6), 通过##和#的做用以后,就将xxx_init 链接到以前的字符串中,为__initcall_xxx_init6,除此以外还有一个section的定义" .initcall" #id " .init",因为先后两部分都是字符串,因此#id的做用就是字符串化,组合成.initcall6.init.
       说了这么多,貌似咱们再跟踪代码就跟不下去了,由于你再也找不到代码的下一步调用在哪里了,咱们在代码中搜索一下发如今vmlinux.lds.S中找到了线索,咱们本文是以arm64为前提的因此文件的路径为(./arch/arm64/kernel/vmlinux.lds.S). 在这个连接器脚本中咱们发现了不少段,其中也包含上文提到的initcall段,如今以一张图片来展现各个段的全貌.....
#define __init        __section(.init.text) __cold  __latent_entropy __noinitretpoline
#define __initdata    __section(.init.data)
#define __initconst    __section(.init.rodata)
#define __exitdata    __section(.exit.data)
#define __exit_call    __used __section(.exitcall.exit)
        除此以外发现,以__init标记的函数或者变量都是放置在.init.text段中,__initdata标记的函数或者变量都是放置在.init.data段中....,因此驱动的xxx_init函数都是放置在init.text段中,module_init函数将xxx_init的函数指针放置到了initcall6.init段中,kernel启动过程当中先加载到initcall6.init段中的函数指针而后加载到init.text段中的函数实体。
       接下来分析kernel启动过程时如何加载到initcall6段中的内容的,
start_kernel---->rest_init---->kernel_thread(kernel_init, NULL, CLONE_FS)---->kernel_init_freeable---->do_basic_setup---->do_initcalls-->do_initcall_level---->do_one_initcall
由上述的流程,开机过程会调用start_kernel进而会调用到do_initcalls,
static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}
该函数中会计算initcall_levels数组的大小,并循环调用do_initcall_level。你会发现,initcall_levels的定义就是本.c文件中,而且定义为__initdata,即放置在init.data段中。
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_levels定义为指针数组,即数组中每一个元素都是__initcallx_start的起始地址。跟踪一下代码发现extern initcall_t __initcall_start[]都有相似的地定义,若是这个时候你再看下vmlinux.lds.S便会豁然开朗。最后开始让咱们好好看看do_one_initcall函数吧,这里我把没必要要的内容都去掉了,留下的都是精华,很明显fn就是 __initcall6_start地址了,经过循环initcall6中的全部内容都会被依次加载。
int __init_or_module do_one_initcall(initcall_t fn)
{   .........
    if (initcall_debug)
        ret = do_one_initcall_debug(fn);
    else
        ret = fn();
    ........
}
这里的initcall_debug故名思意就是开启debug相关的功能,具体的功能是每一个驱动模块加载的时间,作系统优化的朋友对这个变量必定时很是熟悉的。