module_init源码分析
源码分析
本章节我们一块来看一下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)
此处就不熟练调用流程的源码了,后续在系统启动流程再详细讲解。