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