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 #define module_init(x) __initcall(x) #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 #define early_initcall(fn) __define_initcall(fn, early) #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.h和include/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 #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void ) \ { return initfn; } \ int init_module (void ) __copy (initfn) __attribute__ ((alias(#initfn))) ; #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.o和module.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_module和cleanup_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 mod = (void *)info->sechdrs[info->index.mod].sh_addr; kmemleak_load_module(mod, info); return mod;
至此,我们就搞清了动态加载的module中初始化和注销函数是如何被注册和调用的。