module_init源码分析
  qrJHiMhufrJ3 2023年11月02日 29 0


源码分析

        本章节我们一块来看一下module_init(x)这个函数,先分析一下它的源码,再梳理一下它的调用流程,参考代码:linux/include/linux/module.h。

/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);

        注释:如果是常驻的driver,那么会在do_initcalls的时候调到module_init添加的函数。do_initcalls是如何调用过来的我们后面再讲,继续看__initcall的定义(linux/include/linux/init.h):

#define __initcall(fn) device_initcall(fn)

        此处继续看device_initcall(fn),还是在linux/include/linux/init.h中:

#define device_initcall(fn)   __define_initcall(fn, 6)

        此处继续看__define_initcall(fn, id),还是在linux/include/linux/init.h中:

#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

        此处继续看__define_initcall(fn, early),还是在linux/include/linux/init.h中:

#define ___define_initcall(fn, id, __sec)     \
__unique_initcall(fn, id, __sec, __initcall_id(fn))

        继续看___define_initcall(fn, id, .initcall##id),还是在linux/include/linux/init.h中:

#define __unique_initcall(fn, id, __sec, __iid)     \
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))

        在___define_initcall中,____define_initcall的实现:

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ____define_initcall(fn, __stub, __name, __sec) \
__define_initcall_stub(__stub, fn) \
asm(".section \"" __sec "\", \"a\" \n" \
__stringify(__name) ": \n" \
".long " __stringify(__stub) " - . \n" \
".previous \n"); \
static_assert(__same_type(initcall_t, &fn));
#else
#define ____define_initcall(fn, __unused, __name, __sec) \
static initcall_t __name __used \
__attribute__((__section__(__sec))) = fn;
#endif

        从上述代码可知在定义了CONFIG_HAVE_ARCH_PREL32_RELOCATIONS的时候使用____define_initcall第一种实现方式,如果没有CONFIG_HAVE_ARCH_PREL32_RELOCATIONS定义,将采用第二种实现方式,由于未定义CONFIG_HAVE_ARCH_PREL32_RELOCATIONS我们暂时看第二种实现方式。

        GNU编译工具链支持用户自定义section,所以我们阅读Linux源码时,会发现大量使用如下一类用法:

__attribute__((__section__("section-name")))

        __attribute__用来指定变量或结构位域的特殊属性,其后的双括弧中的内容是属性说明,它的语法格式为:__attribute__ ((attribute-list))。它有位置的约束,通常放于声明的尾部且“ ;” 之前。

        这里的attribute-list为__section__(“.initcall6.init”)。通常,编译器将生成的代码存放在.text段中。但有时可能需要其他的段,或者需要将某些函数、变量存放在特殊的段中,section属性就是用来指定将一个函数、变量存放在特定的段中。

  所以这里的意思就是:定义一个名为 __initcall_XXX_init6 的函数指针变量,并初始化为 XXX_init(指向XXX_init);并且该函数指针变量存放于 .initcall6.init 代码段中。

        通过查看链接脚本( arch/$(ARCH)/kernel/vmlinux.lds.S)来了解 .initcall6.init 段。
  可以看到,.init段中包含 INIT_CALLS,它定义在include/asm-generic/vmlinux.lds.h。INIT_CALLS 展开后可得:

#define INIT_CALLS_LEVEL(level)           \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
KEEP(*(.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) = .;

        综上所述,module_init的源码实现可以简化为:

#define module_init(x)  __initcall(x);
|
--> #define __initcall(fn) device_initcall(fn)
|
--> #define device_initcall(fn) __define_initcall(fn, 6)
|
--> #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
|
--> #define ___define_initcall(fn, id, __sec) \
__unique_initcall(fn, id, __sec, __initcall_id(fn))
|
--> #define __unique_initcall(fn, id, __sec, __iid) \
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))

调用流程

        在#define module_init(x)    __initcall(x)的注释中说到do_initcalls调用module_init。实际上完整的调用流程如下:

asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
|
--> void __init __weak arch_call_rest_init(void)
|
--> noinline void __ref rest_init(void)
|
--> static int __ref kernel_init(void *unused)
|
--> static noinline void __init kernel_init_freeable(void)
|
--> static void __init do_basic_setup(void)
|
--> static void __init do_initcalls(void)

        此处就不熟练调用流程的源码了,后续在系统启动流程再详细讲解。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  xWYnr39PTA9E   2023年11月19日   20   0   0 c++
  bWLIE0wKp9lo   2024年05月31日   86   0   0 Linux硬件
  qrJHiMhufrJ3   2023年11月13日   30   0   0 嵌入式Linux学习git
qrJHiMhufrJ3