上一篇文章提到了這段代碼:
arch_initcall_sync(of_platform_default_populate_init);
它的功能是完成 device_node 到 platform_device 的轉換。這篇文章就來大概的分析一下,它是怎樣被調用的。
arch_initcall_sync 定義如下:
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
根據 LDS 檔案的定義,會将這些 data 存儲在指定的位置:
#define INIT_CALLS_LEVEL(level) \
__initcall##level##_start = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
#define INIT_CALLS \
__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) \
__initcall_end = .;
在核心中,想要調用到這些資料,就會用到
__initcall##level##_start = .;
這個辨別。
資料的位置已經搞定了,那麼核心又是怎樣調用的呢?接下來找到這些函數調用。
調用流程如下:
start_kernel
-->rest_init
-->kernel_thread(kernel_init, NULL, CLONE_FS);
-->kernel_init
-->kernel_init_freeable
-->do_basic_setup
-->do_initcalls
-->do_initcall_level
do_initcall_level 代碼如下:
static void __init do_initcall_level(int level)
{
initcall_entry_t *fn;
strcpy(initcall_command_line, saved_command_line);
parse_args(initcall_level_names[level],
initcall_command_line, __start___param,
__stop___param - __start___param,
level, level,
NULL, &repair_env_string);
trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
根據下表,分别調用 do_one_initcall 來執行每一個 initcall。
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
表中的 xxx_start 恰恰就是前面所提到的存儲辨別,那麼這些調用就聯系到了一起,實作函數的調用。
分析到這裡,相信關于 xxx_initcall 調用的雲霧就已經撥開了吧!