はじめに
linux kernel1を読んでいると、**_initcall
(core_initcall
とか__initcall
とかが色々なところで定義されている。例えば、sysenterの初期設定(IA32_SYSENTER_(CS|EIP|ESP)
)をしているarch/i386/kernel/sysenter.cでは、
extern const char vsyscall_int80_start, vsyscall_int80_end; extern const char vsyscall_sysenter_start, vsyscall_sysenter_end; static int __init sysenter_setup(void) { void *page = (void *)get_zeroed_page(GFP_ATOMIC); __set_fixmap(FIX_VSYSCALL, __pa(page), PAGE_READONLY_EXEC); if (!boot_cpu_has(X86_FEATURE_SEP)) { memcpy(page, &vsyscall_int80_start, &vsyscall_int80_end - &vsyscall_int80_start); return 0; } memcpy(page, &vsyscall_sysenter_start, &vsyscall_sysenter_end - &vsyscall_sysenter_start); on_each_cpu(enable_sep_cpu, NULL, 1, 1); return 0; } __initcall(sysenter_setup);
と定められていて、__initcall
が起点になっているようだ。他にも、 https://gist.github.com/knknkn1162/0a766148463c1ccb972ae2999cca1bfd のような場所で定義されている。
__initcall
// include/linux/init.h typedef int (*initcall_t)(void); #define __define_initcall(level,fn) static initcall_t __initcall_##fn __attribute_used__ __attribute__((__section__(".initcall" level ".init"))) = fn #define device_initcall(fn) __define_initcall("6",fn) #define __initcall(fn) device_initcall(fn)
とあるので、__initcall(sysenter_setup);
は以下のような形になる:
static initcall_t __initcall_sysenter_setup __attribute_used__ __attribute__((__section__(".initcall6.init"))) = sysenter_setup
つまり、関数ポインタが.initcall6.init
のsectionに格納されているようだ。
__attribute_used__
は以下の通り:
// include/linux/compiler.h #if __GNUC__ > 3 # include <linux/compiler-gcc+.h> /* catch-all for GCC 4, 5, etc. */ #elif __GNUC__ == 3 # include <linux/compiler-gcc3.h> #elif __GNUC__ == 2 # include <linux/compiler-gcc2.h> #else # error Sorry, your compiler is too old/not recognized. #endif
// include/linux/compiler-gcc+.h #define __attribute_used__ __attribute__((__used__)) // tells the compiler that this function is used
.initcall6.init
はarch/i386/kernel/vmlinux.lds.S
を見ると、uninitialized dataの部分に2、以下のように定められている:
_edata= . // skip .init.data : { *(.init.data) } . = ALIGN(16); __setup_start = .; .init.setup : { *(.init.setup) } __setup_end = .; __initcall_start = .; .initcall.init : { *(.initcall1.init) *(.initcall2.init) *(.initcall3.init) *(.initcall4.init) *(.initcall5.init) *(.initcall6.init) *(.initcall7.init) } __initcall_end = .; // skip
定義されている場所はわかったがこれはどこで使われているのか?
__initcall_start
~ __initcall_end
これは、init() -> do_basic_setup -> do_initcalls関数で以下のように使われている(ちなみに、initはstart_kernel -> rest_init -> kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
で定義されている):
static void __init do_initcalls(void) { initcall_t *call; // typedef int (*initcall_t)(void); int count = preempt_count(); for (call = __initcall_start; call < __initcall_end; call++) { char *msg; // skip (*call)(); // skip } // skip }
call関数は勿論、callbackなので、__initcall_start
と__initcall_end
間にある関数ポインタ達が順番に呼ばれる。initcall${number}.init
のようになっていたのは、呼ばれる優先順位の意味だった。
-
versionはいつもと同じく、2.6.11。 http://cstmize.hatenablog.jp/entry/2019/04/14/linux_kernel%E3%81%A7%E3%81%AEFPU%2C_MMX%2C_SSE%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 のreferenceも参照のこと↩
-
Linux Kernelにおけるkernel dataの位置関係は、Understanding the Linux kernel Figure 2-13. にある。↩