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相关的功能,具体的功能是每个驱动模块加载的时间,做系统优化的朋友对这个变量一定时非常熟悉的。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值