Linux Module Initialization

文中代码基于Linux 5.1 rc6版本

Static Initialization (i.e. initcall)

对于bulit-in的Driver,往往使用device_initcall进行初始化。对于Kernel Module,如果用户没有将其编译为module而是编译成了built-in(CONFIG_XXX = y而不是CONFIG_XXX = m),则module_init最终也会展开为device_initcall

1
2
3
4
/* from include/linux/module.h */
#define module_init(x) __initcall(x)
/* from include/linux/init.h */
#define __initcall(fn) device_initcall(fn)

这实际上属于一个initcall系列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall(fn, early)

/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
* Keep main.c:initcall_level_names[] in sync.
*/
#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)

这些宏将会把函数fn放置到ELF Section.initcall##level##.init中(定义在include/asm-generic/vmlinux.lds.h),从0-7总共8个级别,另外还有early和rootfs两个特殊级别。另外,最终声明的函数名会被改为__initcall_##fn##level

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ___define_initcall(fn, id, __sec) \
__ADDRESSABLE(fn) \
asm(".section \"" #__sec ".init\", \"a\" \n" \
"__initcall_" #fn #id ": \n" \
".long " #fn " - . \n" \
".previous \n");
#else
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
#endif

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

这些函数会在Kernel启动时被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
assembly_code -->
start_kernel() -->
arch_call_rest_init() -->
rest_init() -->
kernel_thread(kernel_init, NULL, CLONE_FS) -->
kernel_init() -->
kernel_init_freeable() -->
do_pre_smp_initcalls() -->
do_one_initcall(initcall_from_entry(fn))
do_basic_setup() -->
do_initcalls() -->
do_initcall_level(level) -->
do_one_initcall(initcall_from_entry(fn))

do_pre_smp_initcalls中调用early_initcall注册的initcall,即.initcallearly.init段中的函数:

1
2
3
4
5
6
7
8
static void __init do_pre_smp_initcalls(void)
{
initcall_entry_t *fn;

trace_initcall_level("early");
for (fn = __initcall_start; fn < __initcall0_start; fn++)
do_one_initcall(initcall_from_entry(fn));
}

do_initcall_level(level)中则是调用相应level的initcall。最终的function call位于函数do_one_initcall中:

1
2
3
do_trace_initcall_start(fn);
ret = fn();
do_trace_initcall_finish(fn, ret);

另外,用__init__initdata声明的函数和变量,会在Kernel初始化完毕后被从内存中删除,以节约空间。

Dynamic Initialization

我们知道,Kernel Module的初始化在module_init中进行,注销在module_exit中进行。这些定义在include/linux/module.hinclude/linux/init.h中,每个Module都应该include这两个头文件。

如果Module的代码真的被编译为Kernel Module而不是Built-in,则module_init必然不是在Kernel初始化,而是在Module加载时被调用。我们从Module加载的入口点,系统调用init_module往下探索:

1
2
3
4
SYSCALL(init_module) -->
load_module -->
do_init_module -->
do_one_initcall(mod->init)

调用的是mod->init,但我们并没有看到该变量在哪里设置。

为了解决mod->init的来源问题,我们回到include/linux/module.h

1
2
3
4
5
6
7
8
9
10
11
/* 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) __copy(initfn) __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) __copy(exitfn) __attribute__((alias(#exitfn)));

可以发现,所有module_init注册的函数,都被取了别名init_module

现在考察Module的编译过程,在编译得到module.o后,还需要经过modpost步骤,即所谓的make modules第二阶段,产生module.mod.c,编译到module.mod.o,最后将module.omodule.mod.o链接为module.ko

我们以arch/x86/kvm/kvm-intel.mod.c为例,看一下modpost生成的.mod.c文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <linux/build-salt.h>
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>

BUILD_SALT;

MODULE_INFO(vermagic, VERMAGIC_STRING);
MODULE_INFO(name, KBUILD_MODNAME);

__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};

MODULE_INFO(intree, "Y");

#ifdef CONFIG_RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif

static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=kvm";

MODULE_ALIAS("cpu:type:x86,ven*fam*mod*:feature:*0085*");

可以发现init_modulecleanup_module出现在了这里,作为__this_module的成员,这个__this_module在编译进.ko文件后实际上也就是我们常用的THIS_MODULE变量。

现在再来看加载Module的过程,在load_module --> setup_load_info中将info->index.mod设置为了__this_module(只有该变量位于.git.linkonce.this_module段):

1
2
3
4
5
6
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
if (!info->index.mod) {
pr_warn("%s: No module found in object\n",
info->name ?: "(missing .modinfo name field)");
return -ENOEXEC;
}

然后在load_module --> layout_and_allocate中将info->index.mod赋值给了mod,这样一来在do_one_initcall(mod->init)中调用的mod->init就指向了module_init设置的初始化函数,即__this_module->init

1
2
3
4
/* Module has been copied to its final place now: return it. */
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
kmemleak_load_module(mod, info);
return mod;

至此,我们就搞清了动态加载的module中初始化和注销函数是如何被注册和调用的。