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中初始化和注销函数是如何被注册和调用的。